├── .env ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── .stylelintrc.js ├── LICENSE ├── README.md ├── config ├── config.ts ├── keep-alive.ts ├── proxy.ts ├── routes.ts └── utils.ts ├── overrides.less ├── package.json ├── plugin.ts ├── pnpm-lock.yaml ├── public └── static │ ├── favicon.png │ ├── redoc.standalone.js │ ├── swagger-ui-bundle.js │ └── swagger-ui.css ├── scripts ├── build ├── deps ├── package └── version ├── src ├── access.ts ├── app.tsx ├── assets │ ├── images │ │ ├── avatar.png │ │ ├── bg-2.png │ │ ├── gpustack-logo.png │ │ ├── huggingface.png │ │ ├── img.png │ │ ├── img_fallback.png │ │ ├── noise_texture.png │ │ ├── ollama.png │ │ └── small-logo-200x200.png │ └── styles │ │ ├── common.less │ │ ├── driver.less │ │ └── menu.less ├── atoms │ ├── models.ts │ ├── route-cache.ts │ ├── settings.ts │ ├── tab-active.ts │ ├── user.ts │ ├── utils │ │ └── index.ts │ └── watch-request.ts ├── components │ ├── alert-info │ │ ├── block.less │ │ ├── block.tsx │ │ └── index.tsx │ ├── audio-animation │ │ ├── index.less │ │ └── index.tsx │ ├── audio-player │ │ ├── audio-element.tsx │ │ ├── config │ │ │ └── type.ts │ │ ├── index.less │ │ ├── index.tsx │ │ ├── raw-audio-player.tsx │ │ └── simple-audio.tsx │ ├── auto-image │ │ ├── index.less │ │ ├── index.tsx │ │ ├── progress-line.less │ │ ├── single-image.less │ │ └── single-image.tsx │ ├── auto-tooltip │ │ ├── index.tsx │ │ └── title-tip.tsx │ ├── bibtex-viewer │ │ └── index.tsx │ ├── buttons │ │ └── more.tsx │ ├── card-wrapper │ │ ├── index.less │ │ ├── index.tsx │ │ └── simple-card.tsx │ ├── check-buttons │ │ └── index.tsx │ ├── content-wrapper │ │ ├── index.less │ │ └── index.tsx │ ├── copy-button │ │ └── index.tsx │ ├── dark-mask │ │ └── index.tsx │ ├── delete-modal │ │ ├── index.less │ │ └── index.tsx │ ├── divider-line │ │ ├── index.less │ │ └── index.tsx │ ├── drop-down-actions │ │ └── index.tsx │ ├── drop-down-buttons │ │ ├── index.less │ │ └── index.tsx │ ├── echarts │ │ ├── bar-chart.tsx │ │ ├── chart-tooltip.tsx │ │ ├── chart.tsx │ │ ├── config.ts │ │ ├── gauge.tsx │ │ ├── h-bar.tsx │ │ ├── index.ts │ │ ├── line-chart.tsx │ │ ├── mix-line-bar.tsx │ │ ├── scatter.tsx │ │ └── types.ts │ ├── editor-wrap │ │ ├── index.less │ │ └── index.tsx │ ├── editor │ │ └── viewer.tsx │ ├── empty-data │ │ └── index.tsx │ ├── footer │ │ └── index.tsx │ ├── form-buttons │ │ └── index.tsx │ ├── highlight-code │ │ ├── code-viewer-dark.tsx │ │ ├── code-viewer-light.tsx │ │ ├── code-viewer.tsx │ │ ├── index.tsx │ │ ├── styles │ │ │ ├── dark.less │ │ │ ├── index.less │ │ │ └── light.less │ │ └── utils.ts │ ├── icon-font │ │ ├── iconfont │ │ │ ├── iconfont.css │ │ │ ├── iconfont.js │ │ │ ├── iconfont.json │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ └── iconfont.woff2 │ │ └── index.tsx │ ├── image-editor │ │ ├── extract-image-colors.ts │ │ ├── hooks │ │ │ ├── use-drawing.ts │ │ │ └── use-zoom.ts │ │ ├── index.less │ │ ├── index.tsx │ │ ├── invert-worker.ts │ │ ├── offscreen-worker.ts │ │ └── tools-bar.tsx │ ├── label-selector │ │ ├── index.tsx │ │ ├── inner.tsx │ │ ├── label-item.tsx │ │ ├── styles │ │ │ ├── label-item.less │ │ │ └── wrapper.less │ │ └── wrapper.tsx │ ├── lang-select │ │ └── index.tsx │ ├── list-input │ │ ├── hint-input.tsx │ │ ├── index.tsx │ │ ├── list-item.tsx │ │ ├── style │ │ │ └── list-item.less │ │ └── styles │ │ │ └── list-item.less │ ├── logs-viewer │ │ ├── config.ts │ │ ├── logs-list.tsx │ │ ├── logs-pagination.tsx │ │ ├── parse-worker.ts │ │ ├── styles │ │ │ ├── index.less │ │ │ ├── logs-list.less │ │ │ ├── pagination.less │ │ │ └── xterm-viewer.less │ │ ├── use-logs-pagination.ts │ │ ├── use-size.ts │ │ ├── virtual-log-list.tsx │ │ └── xterm-viewer.tsx │ ├── markdown-viewer │ │ ├── code-viewer.tsx │ │ ├── full-markdown.tsx │ │ ├── index.less │ │ ├── index.tsx │ │ └── utils.ts │ ├── modal-footer │ │ └── index.tsx │ ├── overlay-scroller │ │ └── index.tsx │ ├── page-tools │ │ ├── index.less │ │ └── index.tsx │ ├── password-validate │ │ └── index.tsx │ ├── popover │ │ ├── index.less │ │ └── index.tsx │ ├── progress-bar │ │ └── index.tsx │ ├── radio-buttons │ │ ├── index.less │ │ └── index.tsx │ ├── scroller-modal │ │ └── index.tsx │ ├── seal-form │ │ ├── auto-complete.tsx │ │ ├── components │ │ │ ├── label-info.less │ │ │ └── label-info.tsx │ │ ├── config │ │ │ ├── components.ts │ │ │ └── index.ts │ │ ├── field-component.tsx │ │ ├── input-number.tsx │ │ ├── input-search.tsx │ │ ├── password.tsx │ │ ├── row-textarea.tsx │ │ ├── seal-cascader.tsx │ │ ├── seal-input.tsx │ │ ├── seal-select.tsx │ │ ├── seal-slider.tsx │ │ ├── seal-switch.tsx │ │ ├── seal-textarea.tsx │ │ ├── styles │ │ │ ├── cascader.less │ │ │ ├── row-textarea.less │ │ │ └── slider.less │ │ ├── types.ts │ │ └── wrapper │ │ │ ├── index.tsx │ │ │ ├── input.ts │ │ │ ├── select.ts │ │ │ └── slider.ts │ ├── seal-table │ │ ├── components │ │ │ ├── header-prefix.tsx │ │ │ ├── header.tsx │ │ │ ├── layout.tsx │ │ │ ├── pagination.tsx │ │ │ ├── row-children.tsx │ │ │ ├── row-prefix.tsx │ │ │ ├── seal-column.tsx │ │ │ ├── table-body.tsx │ │ │ ├── table-header.tsx │ │ │ ├── table-row.tsx │ │ │ └── table-skeleton.tsx │ │ ├── index.tsx │ │ ├── row-context.ts │ │ ├── styles │ │ │ ├── cell.less │ │ │ ├── header.less │ │ │ ├── index.less │ │ │ ├── row-children.less │ │ │ └── skeleton.less │ │ ├── table-context.ts │ │ └── types.ts │ ├── segment-line │ │ └── index.tsx │ ├── short-cuts │ │ ├── index.less │ │ ├── index.tsx │ │ └── keymap.ts │ ├── simple-overlay │ │ └── index.tsx │ ├── simple-table │ │ ├── cell.tsx │ │ ├── header.tsx │ │ ├── index.less │ │ ├── index.tsx │ │ ├── info-column.tsx │ │ └── row.tsx │ ├── speech-content │ │ ├── audio-player.tsx │ │ ├── index.tsx │ │ ├── speech-item.tsx │ │ └── styles │ │ │ ├── index.less │ │ │ └── slider-progress.less │ ├── status-tag │ │ ├── copy-btn.less │ │ ├── index.less │ │ └── index.tsx │ ├── tags-wrapper │ │ ├── index.less │ │ ├── index.tsx │ │ ├── more-dropdown.tsx │ │ └── theme-tag.tsx │ ├── theme-toggle │ │ ├── index.tsx │ │ └── theme-drop-actions.tsx │ ├── tooltip-list │ │ └── index.tsx │ ├── transition │ │ ├── index.less │ │ └── index.tsx │ ├── type-word-effect │ │ └── index.tsx │ ├── upload-audio │ │ └── index.tsx │ ├── util-bar │ │ ├── index.less │ │ └── index.tsx │ └── version-info │ │ ├── index.less │ │ └── index.tsx ├── config │ ├── breakpoints.ts │ ├── driver-config.ts │ ├── global.d.ts │ ├── hotkeys.ts │ ├── index.ts │ ├── route-cachekey.ts │ ├── theme │ │ ├── dark.ts │ │ ├── index.ts │ │ └── light.ts │ └── types.ts ├── constants │ ├── external-links.ts │ └── index.ts ├── global.less ├── global.tsx ├── hooks │ ├── json-parser-worker.ts │ ├── use-app-utils.ts │ ├── use-body-scroll.ts │ ├── use-broadcast.ts │ ├── use-chunk-fetch.ts │ ├── use-chunk-request.ts │ ├── use-container-scorll.ts │ ├── use-copy-to-clipboard.ts │ ├── use-delete-modal.ts │ ├── use-download-stream.ts │ ├── use-driver.ts │ ├── use-event-source.ts │ ├── use-expanded-row-keys.ts │ ├── use-overlay-scroller.ts │ ├── use-request-token.ts │ ├── use-tab-active.ts │ ├── use-table-fetch.ts │ ├── use-table-row-selection.ts │ ├── use-table-sort.ts │ ├── use-update-chunk-list.ts │ ├── use-user-settings.ts │ └── use-window-resize.ts ├── layouts │ ├── Exception.tsx │ ├── Layout.css │ ├── Logo.tsx │ ├── error-boundary.tsx │ ├── error-result.tsx │ ├── icons.tsx │ ├── index.ts │ ├── index.tsx │ ├── render-links.tsx │ ├── rightRender.tsx │ ├── runtime.tsx │ ├── runtimeConfig.d.ts │ └── types.d.ts ├── locales │ ├── README.md │ ├── check.ts │ ├── en-US.ts │ ├── en-US │ │ ├── apikeys.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── menu.ts │ │ ├── models.ts │ │ ├── playground.ts │ │ ├── resources.ts │ │ ├── shortcuts.ts │ │ ├── usage.ts │ │ └── users.ts │ ├── ja-JP.ts │ ├── ja-JP │ │ ├── apikeys.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── menu.ts │ │ ├── models.ts │ │ ├── playground.ts │ │ ├── resources.ts │ │ ├── shortcuts.ts │ │ ├── usage.ts │ │ └── users.ts │ ├── lang-config-map.tsx │ ├── ru-RU.ts │ ├── ru-RU │ │ ├── apikeys.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── menu.ts │ │ ├── models.ts │ │ ├── playground.ts │ │ ├── resources.ts │ │ ├── shortcuts.ts │ │ ├── usage.ts │ │ └── users.ts │ ├── zh-CN.ts │ └── zh-CN │ │ ├── apikeys.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── menu.ts │ │ ├── models.ts │ │ ├── playground.ts │ │ ├── resources.ts │ │ ├── shortcuts.ts │ │ ├── usage.ts │ │ └── users.ts ├── models │ └── global.ts ├── pages │ ├── 404.tsx │ ├── access │ │ └── index.tsx │ ├── api-keys │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ └── add-apikey.tsx │ │ ├── config │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── index.tsx │ ├── dashboard │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ ├── active-table.tsx │ │ │ ├── dahboard-inner.tsx │ │ │ ├── over-view.less │ │ │ ├── over-view.tsx │ │ │ ├── resource-utilization.tsx │ │ │ ├── system-load.tsx │ │ │ ├── usage-inner │ │ │ │ ├── export-data.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── request-token-inner.tsx │ │ │ │ ├── top-user.tsx │ │ │ │ └── use-usage-data.ts │ │ │ └── usage.tsx │ │ ├── config │ │ │ ├── dashboard-context.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.tsx │ │ └── styles │ │ │ └── index.less │ ├── llmodels │ │ ├── apis │ │ │ ├── evaluateWorker.ts │ │ │ └── index.ts │ │ ├── catalog.tsx │ │ ├── components │ │ │ ├── advance-config.tsx │ │ │ ├── api-access-info.tsx │ │ │ ├── catalog-item.tsx │ │ │ ├── catalog-list.tsx │ │ │ ├── catalog-skelton.tsx │ │ │ ├── column-wrapper.tsx │ │ │ ├── compatible-alert.tsx │ │ │ ├── data-form.tsx │ │ │ ├── deploy-builtin-modal.tsx │ │ │ ├── deploy-dropdown.tsx │ │ │ ├── deploy-modal.tsx │ │ │ ├── file-parts.tsx │ │ │ ├── gpu-card.tsx │ │ │ ├── hf-model-file.tsx │ │ │ ├── hf-model-item.tsx │ │ │ ├── incompatiable-info.tsx │ │ │ ├── instance-item.tsx │ │ │ ├── instances.tsx │ │ │ ├── model-card.tsx │ │ │ ├── model-file-item.tsx │ │ │ ├── model-tag.tsx │ │ │ ├── ollama-tips.tsx │ │ │ ├── search-input.tsx │ │ │ ├── search-model.tsx │ │ │ ├── search-result.tsx │ │ │ ├── separator.tsx │ │ │ ├── table-list.tsx │ │ │ ├── title-wrapper.tsx │ │ │ ├── update-modal.tsx │ │ │ └── view-logs-modal.tsx │ │ ├── config │ │ │ ├── audio-catalog.ts │ │ │ ├── button-actions.ts │ │ │ ├── file-type.ts │ │ │ ├── form-context.ts │ │ │ ├── index.ts │ │ │ ├── llama-config.ts │ │ │ ├── mindie-config.ts │ │ │ ├── types.ts │ │ │ └── vllm-config.ts │ │ ├── download │ │ │ ├── index.tsx │ │ │ └── target-form.tsx │ │ ├── forms │ │ │ ├── catalog.tsx │ │ │ ├── hugging-face.tsx │ │ │ ├── local-path.tsx │ │ │ └── ollama_library.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── use-selector-change.ts │ │ ├── index.tsx │ │ └── style │ │ │ ├── catalog-item.less │ │ │ ├── column-wrapper.less │ │ │ ├── data-form.less │ │ │ ├── deploy-dropdown.less │ │ │ ├── gpu-card.less │ │ │ ├── hf-model-file.less │ │ │ ├── hf-model-item.less │ │ │ ├── instance-item.less │ │ │ ├── model-card.less │ │ │ ├── search-result.less │ │ │ ├── separator.less │ │ │ └── title-wrapper.less │ ├── login │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ ├── login-form.tsx │ │ │ ├── password-form.tsx │ │ │ └── styles.less │ │ ├── index.tsx │ │ └── utils.ts │ ├── playground │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ ├── audio-content.tsx │ │ │ ├── audio-input.tsx │ │ │ ├── canvas-zoom.tsx │ │ │ ├── dynamic-params.tsx │ │ │ ├── empty-models.tsx │ │ │ ├── file-list.tsx │ │ │ ├── ground-embedding.tsx │ │ │ ├── ground-images.tsx │ │ │ ├── ground-left.tsx │ │ │ ├── ground-reranker.tsx │ │ │ ├── ground-stt.tsx │ │ │ ├── ground-tts.tsx │ │ │ ├── image-edit.tsx │ │ │ ├── input-list.tsx │ │ │ ├── message-input.tsx │ │ │ ├── model-parameters.tsx │ │ │ ├── multiple-chat │ │ │ │ ├── active-models.tsx │ │ │ │ ├── content-item.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── message-actions.tsx │ │ │ │ ├── message-body.tsx │ │ │ │ ├── message-content.tsx │ │ │ │ ├── model-item.tsx │ │ │ │ ├── system-message.tsx │ │ │ │ ├── think-content.tsx │ │ │ │ └── think-parser.ts │ │ │ ├── prompt-modal.tsx │ │ │ ├── reference-params.tsx │ │ │ ├── rerank-message.tsx │ │ │ ├── thumb-img.tsx │ │ │ ├── token-usage.tsx │ │ │ ├── upload-file.tsx │ │ │ ├── upload-img.tsx │ │ │ └── view-common-code.tsx │ │ ├── config │ │ │ ├── compare-context.ts │ │ │ ├── embedding-worker.worker.ts │ │ │ ├── index.ts │ │ │ ├── params-config.ts │ │ │ ├── prompt.ts │ │ │ └── types.ts │ │ ├── embedding.tsx │ │ ├── hooks │ │ │ ├── config.ts │ │ │ ├── use-chat-completion.ts │ │ │ ├── use-collapse-layout.tsx │ │ │ ├── use-embedding-worker.ts │ │ │ ├── use-init-meta.ts │ │ │ └── use-text-image.ts │ │ ├── images.tsx │ │ ├── index.tsx │ │ ├── rerank.tsx │ │ ├── speech.tsx │ │ ├── style │ │ │ ├── audio-input.less │ │ │ ├── content-item.less │ │ │ ├── custom-label.less │ │ │ ├── file-list.less │ │ │ ├── ground-left.less │ │ │ ├── input-list.less │ │ │ ├── message-input.less │ │ │ ├── message-item.less │ │ │ ├── model-item.less │ │ │ ├── multiple-chat.less │ │ │ ├── play-ground.less │ │ │ ├── prompt-modal.less │ │ │ ├── reference-params.less │ │ │ ├── rerank-message.less │ │ │ ├── rerank.less │ │ │ ├── speech-to-text.less │ │ │ ├── sys-message.less │ │ │ ├── system-message-wrap.less │ │ │ ├── think-content.less │ │ │ └── thumb-img.less │ │ └── view-code │ │ │ ├── audio.ts │ │ │ ├── embedding.ts │ │ │ ├── image.ts │ │ │ ├── llm.ts │ │ │ ├── rerank.ts │ │ │ └── utils.ts │ ├── profile │ │ ├── components │ │ │ ├── appearance.tsx │ │ │ └── modify-password.tsx │ │ └── index.tsx │ ├── resources │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ ├── add-worker.tsx │ │ │ ├── container-install.tsx │ │ │ ├── gpus.tsx │ │ │ ├── model-files.tsx │ │ │ ├── script-install.tsx │ │ │ ├── styles │ │ │ │ └── installation.less │ │ │ ├── update-labels.tsx │ │ │ └── workers.tsx │ │ ├── config │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── index.tsx │ ├── usage │ │ ├── apis │ │ │ └── index.ts │ │ ├── components │ │ │ ├── active-table.tsx │ │ │ ├── dahboard-inner.tsx │ │ │ ├── over-view.less │ │ │ ├── over-view.tsx │ │ │ ├── resource-utilization.tsx │ │ │ ├── system-load.tsx │ │ │ ├── usage-inner │ │ │ │ ├── index.tsx │ │ │ │ ├── request-token-inner.tsx │ │ │ │ └── top-user.tsx │ │ │ └── usage.tsx │ │ ├── config │ │ │ ├── dashboard-context.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.tsx │ │ └── styles │ │ │ └── index.less │ └── users │ │ ├── apis │ │ └── index.ts │ │ ├── components │ │ └── add-modal.tsx │ │ ├── config │ │ ├── index.ts │ │ └── types.ts │ │ └── index.tsx ├── request-config.ts ├── services │ └── profile │ │ └── apis.ts └── utils │ ├── epub-reader.ts │ ├── excel-reader.ts │ ├── fetch-chunk-data.ts │ ├── index.ts │ ├── load-audio-file.ts │ ├── localstore │ ├── index.ts │ └── store.ts │ ├── pdf-reader.ts │ ├── pptx-reader.ts │ ├── read-html.ts │ └── word-reader.ts ├── tsconfig.json └── typings.d.ts /.env: -------------------------------------------------------------------------------- 1 | PORT=9000 2 | UMI_DEV_SERVER_COMPRESS=none 3 | DID_YOU_KNOW=none 4 | 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/components/icon-font/iconfont/iconfont.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/eslint'), 3 | rules: { 4 | 'react/no-unstable-nested-components': 1, 5 | 'no-unused-vars': 'off', 6 | 'no-undef': 'error', 7 | '@typescript-eslint/no-unused-vars': 'off', 8 | '@typescript-eslint/class-name-casing': 'off' 9 | }, 10 | globals: { 11 | Global: 'readonly', 12 | React: 'readonly' 13 | }, 14 | ignorePatterns: ['public/static/'] 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env.local 3 | /.umirc.local.ts 4 | /config/config.local.ts 5 | /src/.umi 6 | /src/.umi-production 7 | /src/.umi-test 8 | /.umi 9 | /.umi-production 10 | /.umi-test 11 | /dist 12 | /.mfsu 13 | .swc 14 | .DS_Store 15 | .idea -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install max verify-commit $1 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{md,json}": ["prettier --cache --write"], 3 | "*.{js,jsx}": ["max lint --fix --eslint-only", "prettier --cache --write"], 4 | "*.{css,less}": [ 5 | "max lint --fix --stylelint-only", 6 | "prettier --cache --write" 7 | ], 8 | "*.ts?(x)": [ 9 | "max lint --fix --eslint-only", 10 | "prettier --cache --parser=typescript --write" 11 | ], 12 | "src/locales/**/*.ts": ["npx tsx src/locales/check.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com/ 2 | 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .umi 3 | .umi-production 4 | public/static/*.js 5 | public/static/*.css 6 | src/components/icon-font/iconfont/iconfont.js 7 | src/components/icon-font/iconfont/*.css 8 | 9 | 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | singleQuote: true, 4 | trailingComma: 'none', 5 | htmlWhitespaceSensitivity: 'strict', 6 | proseWrap: 'never', 7 | overrides: [{ files: '.prettierrc', options: { parser: 'json' } }], 8 | plugins: [ 9 | 'prettier-plugin-organize-imports', 10 | 'prettier-plugin-packagejson', 11 | 'eslint-plugin-unused-imports' 12 | ], 13 | rules: { 14 | '@typescript-eslint/no-unused-vars': 'off', 15 | 'unused-imports/no-unused-imports-ts': 'off' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/stylelint'), 3 | rules: { 4 | 'selector-class-pattern': null 5 | }, 6 | ignoreFiles: ['public/static/*.css'] 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GPUStack UI 2 | 3 | UI for [GPUStack](https://github.com/gpustack/gpustack). 4 | 5 | ## Installation 6 | 7 | 1. [Nodejs](https://nodejs.org/en) 16.0+(with NPM) 8 | 9 | If you're on Mac 10 | 11 | ``` 12 | brew install node 13 | ``` 14 | 15 | 2. [pnpm](https://pnpm.io/installation#using-npm) 16 | 17 | ``` 18 | npm install -g pnpm 19 | ``` 20 | 21 | 3. Setup 22 | 23 | ``` 24 | git clone https://github.com/gpustack/gpustack-ui/ 25 | ``` 26 | 27 | 4. Install dependencies 28 | 29 | ``` 30 | cd gpustack-ui 31 | pnpm install 32 | ``` 33 | 34 | ## Usage 35 | 36 | 1. Run development server (at http://localhost:9000) 37 | 38 | ``` 39 | npm run dev 40 | ``` 41 | 42 | 2. build release 43 | 44 | ``` 45 | npm run build 46 | ``` 47 | 48 | ## Bugs & Issues 49 | 50 | - Please submit [bugs and issues](https://github.com/gpustack/gpustack/issues) with a label `ui` 51 | 52 | ## Links 53 | 54 | - Official website: https://gpustack.ai/ 55 | - Documents: https://docs.gpustack.ai/latest/overview/ 56 | -------------------------------------------------------------------------------- /config/keep-alive.ts: -------------------------------------------------------------------------------- 1 | export const keepAliveRoutes = { 2 | text2images: '/playground/text-to-image', 3 | speech: '/playground/speech' 4 | }; 5 | 6 | export default [keepAliveRoutes.text2images, keepAliveRoutes.speech]; 7 | -------------------------------------------------------------------------------- /config/proxy.ts: -------------------------------------------------------------------------------- 1 | const proxyTableList = [ 2 | 'cli', 3 | 'v1', 4 | 'auth', 5 | 'v1-openai', 6 | 'version', 7 | 'proxy', 8 | 'update' 9 | ]; 10 | 11 | // @ts-ingore 12 | export default function createProxyTable(target?: string) { 13 | const proxyTable = proxyTableList.reduce( 14 | (obj: Record, api) => { 15 | const newTarget = target || 'http://localhost'; 16 | obj[`/${api}`] = { 17 | target: newTarget, 18 | changeOrigin: true, 19 | secure: false, 20 | ws: true, 21 | log: 'debug', 22 | pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`), 23 | // onProxyRes: (proxyRes: any, req: any, res: any) => { 24 | // console.log('headers=========', { 25 | // res: proxyRes.headers, 26 | // req: req.headers 27 | // }); 28 | // }, 29 | headers: { 30 | origin: newTarget, 31 | Connection: 'keep-alive' 32 | } 33 | }; 34 | return obj; 35 | }, 36 | {} 37 | ); 38 | return proxyTable; 39 | } 40 | -------------------------------------------------------------------------------- /config/utils.ts: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | 3 | export const getBranchInfo = () => { 4 | const latestCommit = child_process 5 | .execSync('git rev-parse HEAD') 6 | .toString() 7 | .trim(); 8 | const versionTag = child_process 9 | .execSync(`git tag --contains ${latestCommit}`) 10 | .toString() 11 | .trim(); 12 | return { version: versionTag || '', commitId: latestCommit.slice(0, 7) }; 13 | }; 14 | -------------------------------------------------------------------------------- /overrides.less: -------------------------------------------------------------------------------- 1 | // only for third party libraries styles 2 | 3 | .simplebar-scrollbar.simplebar-visible::before { 4 | opacity: 1; 5 | } 6 | 7 | .simplebar-scrollbar::before { 8 | background: var(--scrollbar-handle-bg); 9 | width: var(--scrollbar-size); 10 | } 11 | 12 | .simplebar-scrollbar.simplebar-visible.simplebar-hover { 13 | &::before { 14 | background: var(--scrollbar-handle-hover-bg); 15 | } 16 | } 17 | 18 | .simplebar-scrollbar.scrollbar-handle-light { 19 | &::before { 20 | background-color: var(--scrollbar-handle-light-bg); 21 | } 22 | } 23 | 24 | .simplebar-scrollbar.simplebar-visible.simplebar-hover.scrollbar-handle-light { 25 | &::before { 26 | background: var(--scrollbar-handle-light-bg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin.ts: -------------------------------------------------------------------------------- 1 | import { IApi } from '@umijs/max'; 2 | 3 | export default (api: IApi) => { 4 | api.modifyHTML(($) => { 5 | const info = JSON.parse(process.env.VERSION || '{}'); 6 | const env = process.env.NODE_ENV; 7 | 8 | $('html').attr('data-env', env); 9 | 10 | $('html').attr( 11 | 'data-version', 12 | env === 'production' ? info.version || info.commitId : `${info.commitId}` 13 | ); 14 | if (env === 'production') { 15 | $('script[src^="/js/umi"]').first?.().remove?.(); 16 | } 17 | return $; 18 | }); 19 | api.onStart(() => { 20 | console.log('start'); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /public/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/public/static/favicon.png -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | npm run build ${BASE_ARGS:-} -------------------------------------------------------------------------------- /scripts/deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | if [[ -n "${LOCK}" ]]; then 5 | BASE_ARGS="--frozen-lockfile" 6 | fi 7 | 8 | pnpm install ${BASE_ARGS:-} -------------------------------------------------------------------------------- /scripts/package: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | source scripts/version 5 | 6 | version::get_version_vars 7 | 8 | TARBALL=${GIT_VERSION}.tar.gz 9 | echo "Compressing to ${TARBALL}..." 10 | tar -czf ${TARBALL} dist 11 | mv ${TARBALL} dist -------------------------------------------------------------------------------- /src/access.ts: -------------------------------------------------------------------------------- 1 | export default (initialState: { currentUser?: Global.UserInfo }) => { 2 | const canSeeAdmin = !!( 3 | initialState && 4 | initialState.currentUser && 5 | initialState.currentUser.is_admin 6 | ); 7 | return { 8 | canSeeAdmin, 9 | canDelete: true, 10 | canLogin: true 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/avatar.png -------------------------------------------------------------------------------- /src/assets/images/bg-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/bg-2.png -------------------------------------------------------------------------------- /src/assets/images/gpustack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/gpustack-logo.png -------------------------------------------------------------------------------- /src/assets/images/huggingface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/huggingface.png -------------------------------------------------------------------------------- /src/assets/images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/img.png -------------------------------------------------------------------------------- /src/assets/images/img_fallback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/img_fallback.png -------------------------------------------------------------------------------- /src/assets/images/noise_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/noise_texture.png -------------------------------------------------------------------------------- /src/assets/images/ollama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/ollama.png -------------------------------------------------------------------------------- /src/assets/images/small-logo-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/assets/images/small-logo-200x200.png -------------------------------------------------------------------------------- /src/assets/styles/driver.less: -------------------------------------------------------------------------------- 1 | .driver-popover { 2 | color: var(--ant-color-text); 3 | 4 | .driver-popover-title { 5 | font-size: 16px; 6 | font-weight: var(--font-weight-bold); 7 | color: var(--ant-color-text); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/styles/menu.less: -------------------------------------------------------------------------------- 1 | .ant-pro-layout { 2 | .ant-pro-sider { 3 | .ant-layout-sider-children { 4 | border-inline: none; 5 | border-radius: 0; 6 | // padding-inline: 16px; 7 | padding-block-end: 12px; 8 | } 9 | 10 | .umi-plugin-layout-right { 11 | width: 100%; 12 | 13 | .umi-plugin-layout-action { 14 | padding: 12px; 15 | width: 100%; 16 | border-radius: var(--menu-border-radius-base); 17 | 18 | &:hover { 19 | background-color: var(--color-white-1); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/atoms/models.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai'; 2 | 3 | // models expand keys: create, update , delete, 4 | export const modelsExpandKeysAtom = atom([]); 5 | -------------------------------------------------------------------------------- /src/atoms/route-cache.ts: -------------------------------------------------------------------------------- 1 | import { atom, getDefaultStore } from 'jotai'; 2 | 3 | export const routeCacheAtom = atom(new Map()); 4 | 5 | export const setRouteCache = (key: string, value: any) => { 6 | const store = getDefaultStore(); 7 | const cache = store.get(routeCacheAtom); 8 | cache.set(key, value); 9 | store.set(routeCacheAtom, cache); 10 | }; 11 | 12 | export const deleteRouteCache = (key: string) => { 13 | const store = getDefaultStore(); 14 | const cache = store.get(routeCacheAtom); 15 | cache.delete(key); 16 | store.set(routeCacheAtom, cache); 17 | }; 18 | -------------------------------------------------------------------------------- /src/atoms/settings.ts: -------------------------------------------------------------------------------- 1 | import { colorPrimary } from '@/config/theme'; 2 | import { atom } from 'jotai'; 3 | import { atomWithStorage } from 'jotai/utils'; 4 | 5 | type UserSettings = { 6 | theme: 'light' | 'realDark'; 7 | mode: 'light' | 'realDark' | 'auto'; 8 | colorPrimary: string; 9 | isDarkTheme: boolean; 10 | }; 11 | 12 | const defaultSettings: UserSettings = { 13 | theme: 'light', 14 | mode: 'auto', 15 | isDarkTheme: false, 16 | colorPrimary: colorPrimary 17 | }; 18 | 19 | export const getStorageUserSettings = () => { 20 | if (typeof window === 'undefined') return defaultSettings; 21 | try { 22 | const savedSettings = JSON.parse( 23 | localStorage.getItem('userSettings') || '{}' 24 | ); 25 | return { 26 | ...defaultSettings, 27 | ...savedSettings 28 | }; 29 | } catch { 30 | return defaultSettings; 31 | } 32 | }; 33 | 34 | export const userSettingsAtom = atomWithStorage( 35 | 'userSettings', 36 | defaultSettings 37 | ); 38 | 39 | export const userSettingsHelperAtom = atom( 40 | (get) => get(userSettingsAtom), 41 | (get, set, update: Partial) => { 42 | const prev = get(userSettingsAtom); 43 | const newSettings = { 44 | ...prev, 45 | ...(update || {}) 46 | }; 47 | set(userSettingsAtom, { 48 | ...newSettings, 49 | colorPrimary: newSettings.colorPrimary || colorPrimary, 50 | isDarkTheme: newSettings.theme === 'realDark' 51 | }); 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /src/atoms/tab-active.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultStore } from 'jotai'; 2 | import { atomWithStorage } from 'jotai/utils'; 3 | 4 | export const tabActiveAtom = atomWithStorage>( 5 | 'tabActiveStatus', 6 | new Map() 7 | ); 8 | 9 | export const setActiveStatus = (key: string, value: any) => { 10 | const store = getDefaultStore(); 11 | const cache = store.get(tabActiveAtom); 12 | cache.set(key, value); 13 | store.set(tabActiveAtom, cache); 14 | }; 15 | 16 | export const getActiveStatus = (key: string) => { 17 | const store = getDefaultStore(); 18 | const cache = store.get(tabActiveAtom); 19 | return cache.get(key); 20 | }; 21 | -------------------------------------------------------------------------------- /src/atoms/user.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai'; 2 | import { atomWithStorage } from 'jotai/utils'; 3 | 4 | export const userAtom = atomWithStorage('userInfo', null); 5 | 6 | export const GPUStackVersionAtom = atom<{ 7 | version: string; 8 | git_commit: string; 9 | isProduction: boolean; 10 | }>({ 11 | version: '', 12 | git_commit: '', 13 | isProduction: false 14 | }); 15 | 16 | export const UpdateCheckAtom = atom<{ 17 | latest_version: string; 18 | }>({ 19 | latest_version: '' 20 | }); 21 | 22 | export const initialPasswordAtom = atomWithStorage( 23 | 'initialPassword', 24 | '' 25 | ); 26 | -------------------------------------------------------------------------------- /src/atoms/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { getDefaultStore } from 'jotai'; 2 | 3 | export const clearAtomStorage = (atom: any) => { 4 | if (!atom) { 5 | return; 6 | } 7 | const store = getDefaultStore(); 8 | store.set(atom, null); 9 | }; 10 | 11 | export const setAtomStorage = (atom: any, value: any) => { 12 | if (!atom) { 13 | return; 14 | } 15 | const store = getDefaultStore(); 16 | store.set(atom, value); 17 | }; 18 | export const getAtomStorage = (atom: any): any => { 19 | if (!atom) { 20 | return null; 21 | } 22 | const store = getDefaultStore(); 23 | return store.get(atom); 24 | }; 25 | -------------------------------------------------------------------------------- /src/atoms/watch-request.ts: -------------------------------------------------------------------------------- 1 | interface WatchRequest { 2 | id: number; 3 | token: any; 4 | cancel: () => void; 5 | } 6 | 7 | declare global { 8 | interface Window { 9 | __GPUSTACK_WATCH_REQUEST_CLEAR__: { 10 | watchIDValue: number; 11 | requestList: WatchRequest[]; 12 | }; 13 | } 14 | } 15 | 16 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__ = { 17 | watchIDValue: 0, 18 | requestList: [] 19 | }; 20 | 21 | export const updateWatchIDValue = () => { 22 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue = 23 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue + 1; 24 | return window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue; 25 | }; 26 | 27 | export const updateWatchRequest = (watchToken: WatchRequest) => { 28 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList.push(watchToken); 29 | }; 30 | 31 | export const cancelWatchRequest = (n: number) => { 32 | // cancel the before n requests 33 | const requestList = window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList; 34 | 35 | for (let i = 0; i < n; i++) { 36 | requestList[i]?.cancel?.(); 37 | } 38 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList = requestList.slice(n); 39 | }; 40 | 41 | export const clearWatchRequestId = (id: number) => { 42 | const requestList = window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList; 43 | const newRequestList = requestList.filter((item) => item.id !== id); 44 | window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList = newRequestList; 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/alert-info/index.tsx: -------------------------------------------------------------------------------- 1 | import { WarningOutlined } from '@ant-design/icons'; 2 | import { Typography } from 'antd'; 3 | import React from 'react'; 4 | 5 | interface AlertInfoProps { 6 | type: 'danger' | 'warning'; 7 | message: string; 8 | rows?: number; 9 | icon?: React.ReactNode; 10 | ellipsis?: boolean; 11 | style?: React.CSSProperties; 12 | } 13 | 14 | const AlertInfo: React.FC = (props) => { 15 | const { message, type, rows = 1, ellipsis, style } = props; 16 | 17 | return ( 18 | <> 19 | {message ? ( 20 | 39 | 40 | {message} 41 | 42 | ) : null} 43 | 44 | ); 45 | }; 46 | 47 | export default React.memo(AlertInfo); 48 | -------------------------------------------------------------------------------- /src/components/audio-animation/index.less: -------------------------------------------------------------------------------- 1 | .canvas-wrap { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | text-align: center; 6 | width: 100%; 7 | 8 | canvas { 9 | display: block; 10 | width: 100%; 11 | image-rendering: crisp-edges; 12 | } 13 | } 14 | 15 | .scroller-wrapper { 16 | width: 100%; 17 | height: 100%; 18 | overflow: hidden; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/audio-player/audio-element.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const AudioWrapper = styled.div` 5 | width: 100%; 6 | height: 100%; 7 | display: flex; 8 | align-items: center; 9 | justify-content: flex-start; 10 | `; 11 | 12 | const AudioElement: React.FC = (props) => { 13 | return ( 14 |
15 | 16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default AudioElement; 23 | -------------------------------------------------------------------------------- /src/components/audio-player/config/type.ts: -------------------------------------------------------------------------------- 1 | export type AudioEvent = 2 | | 'play' 3 | | 'playing' 4 | | 'pause' 5 | | 'timeupdate' 6 | | 'ended' 7 | | 'loadedmetadata' 8 | | 'audioprocess' 9 | | 'canplay' 10 | | 'ended' 11 | | 'loadeddata' 12 | | 'seeked' 13 | | 'seeking' 14 | | 'volumechange'; 15 | 16 | export interface AudioPlayerProps { 17 | controls?: boolean; 18 | autoplay?: boolean; 19 | url: string; 20 | speed?: number; 21 | ref?: any; 22 | height?: number; 23 | width?: number; 24 | duration?: number; 25 | onPlay?: () => void; 26 | onPlaying?: () => void; 27 | onPause?: () => void; 28 | onTimeUpdate?: () => void; 29 | onEnded?: () => void; 30 | onLoadedMetadata?: (duration: number) => void; 31 | onAudioProcess?: (current: number) => void; 32 | onCanPlay?: () => void; 33 | onLoadedData?: () => void; 34 | onSeeked?: () => void; 35 | onSeeking?: () => void; 36 | onVolumeChange?: () => void; 37 | onReady?: (duration: number) => void; 38 | onAnalyse?: (analyseData: any, frequencyBinCount: any) => void; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/auto-image/index.less: -------------------------------------------------------------------------------- 1 | .toolbar-wrapper { 2 | padding: 0 24px; 3 | color: rgba(255, 255, 255, 65%); 4 | font-size: 16px; 5 | background-color: rgba(0, 0, 0, 10%); 6 | border-radius: 100px; 7 | } 8 | 9 | .toolbar-wrapper .anticon { 10 | padding: 12px; 11 | cursor: pointer; 12 | } 13 | 14 | .toolbar-wrapper .anticon[disabled] { 15 | cursor: not-allowed; 16 | opacity: 0.3; 17 | } 18 | 19 | .toolbar-wrapper .anticon:hover { 20 | opacity: 0.3; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/auto-image/progress-line.less: -------------------------------------------------------------------------------- 1 | .img-wrapper { 2 | position: relative; 3 | display: inline-block; 4 | } 5 | 6 | .img-wrapper .auto-image { 7 | display: block; 8 | } 9 | 10 | .img-wrapper .progress-wrapper { 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | bottom: 0; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | } 20 | 21 | .progress-square { 22 | width: 100%; 23 | height: 100%; 24 | transform: rotate(0deg); 25 | } 26 | 27 | .progress-square-bg { 28 | fill: none; 29 | } 30 | 31 | .progress-square-fg { 32 | fill: none; 33 | stroke-linecap: square; 34 | stroke-dasharray: 400; 35 | stroke-dashoffset: 400; 36 | transition: stroke-dashoffset 0.3s ease; 37 | } 38 | 39 | .progress-text { 40 | position: absolute; 41 | color: black; 42 | font-size: 20px; 43 | font-weight: bold; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/auto-tooltip/title-tip.tsx: -------------------------------------------------------------------------------- 1 | import { OverlayScroller } from '@/components/overlay-scroller'; 2 | import React from 'react'; 3 | 4 | interface TitleTipProps { 5 | isOverflowing: boolean; 6 | showTitle: boolean; 7 | title: React.ReactNode; 8 | children: React.ReactNode; 9 | } 10 | 11 | const TitleTip: React.FC = (props) => { 12 | const { isOverflowing, showTitle, title, children } = props; 13 | 14 | return ( 15 | 16 |
22 | {isOverflowing || showTitle ? title || children : ''} 23 |
24 |
25 | ); 26 | }; 27 | 28 | export default React.memo(TitleTip); 29 | -------------------------------------------------------------------------------- /src/components/buttons/more.tsx: -------------------------------------------------------------------------------- 1 | import { DoubleRightOutlined } from '@ant-design/icons'; 2 | import { useIntl } from '@umijs/max'; 3 | import { Button } from 'antd'; 4 | import React from 'react'; 5 | import styled from 'styled-components'; 6 | 7 | interface MoreButtonProps { 8 | show: boolean; 9 | loadMore: () => void; 10 | loading?: boolean; 11 | } 12 | 13 | const MoreWrapper = styled.div` 14 | display: flex; 15 | justify-content: center; 16 | margin-block: 16px; 17 | opacity: 1; 18 | transition: opacity 0.3s; 19 | &.loading { 20 | opacity: 0; 21 | transition: opacity 0.3s ease-in-out; 22 | } 23 | `; 24 | 25 | const MoreButton: React.FC = (props) => { 26 | const { show, loading, loadMore } = props; 27 | const intl = useIntl(); 28 | return ( 29 | <> 30 | {show ? ( 31 | 32 | 40 | 41 | ) : null} 42 | 43 | ); 44 | }; 45 | 46 | export default MoreButton; 47 | -------------------------------------------------------------------------------- /src/components/card-wrapper/index.less: -------------------------------------------------------------------------------- 1 | .cardWrapper { 2 | border-radius: var(--border-radius-small); 3 | background-color: var(--color-white-1); 4 | box-shadow: none; 5 | padding: 10px; 6 | border: 1px solid var(--ant-color-border); 7 | } 8 | -------------------------------------------------------------------------------- /src/components/card-wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.less'; 2 | 3 | const CardWrapper = (props: any) => { 4 | const { children, style } = props; 5 | return ( 6 |
7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | export default CardWrapper; 13 | -------------------------------------------------------------------------------- /src/components/check-buttons/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd'; 2 | import React from 'react'; 3 | 4 | interface CheckButtonsProps { 5 | options: Global.BaseOption[]; 6 | onChange: (value: string | number) => void; 7 | cancelable?: boolean; 8 | size?: 'small' | 'middle' | 'large'; 9 | type?: 'text' | 'primary' | 'default' | 'dashed' | 'link' | undefined; 10 | } 11 | 12 | const CheckButtons: React.FC = (props) => { 13 | const [type, setType] = React.useState(props.type || 'text'); 14 | const [active, setActive] = React.useState(null); 15 | const handleChange = (value: string | number) => { 16 | props.onChange(value); 17 | if (props.cancelable && active === value) { 18 | setActive(null); 19 | } else { 20 | setActive(value); 21 | } 22 | }; 23 | return ( 24 |
25 | {props.options?.map?.((option, index) => { 26 | return ( 27 | 37 | ); 38 | })} 39 |
40 | ); 41 | }; 42 | 43 | export default React.memo(CheckButtons); 44 | -------------------------------------------------------------------------------- /src/components/content-wrapper/index.less: -------------------------------------------------------------------------------- 1 | .content-wrapper { 2 | .content { 3 | padding-block-start: 0; 4 | padding-block-end: 32px; 5 | padding-inline: 40px; 6 | } 7 | 8 | .title { 9 | font-size: var(--font-size-large); 10 | font-weight: 600; 11 | line-height: 32px; 12 | padding-block-start: 8px; 13 | padding-block-end: 16px; 14 | padding-inline-start: 40px; 15 | padding-inline-end: 40px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/content-wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.less'; 3 | 4 | const ContentWrapper: React.FC<{ 5 | children: React.ReactNode; 6 | title: React.ReactNode; 7 | titleStyle?: React.CSSProperties; 8 | contentStyle?: React.CSSProperties; 9 | }> = ({ children, title = false, titleStyle, contentStyle }) => { 10 | return ( 11 |
12 | {title && ( 13 |
14 | {title} 15 |
16 | )} 17 |
18 | {children} 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default ContentWrapper; 25 | -------------------------------------------------------------------------------- /src/components/delete-modal/index.less: -------------------------------------------------------------------------------- 1 | :local(.delete-modal-content) { 2 | display: flex; 3 | font-size: var(--font-size-middle); 4 | 5 | :global { 6 | .anticon { 7 | font-size: 20px; 8 | margin-right: 10px; 9 | color: var(--ant-color-warning); 10 | } 11 | 12 | .title { 13 | display: flex; 14 | align-items: center; 15 | } 16 | } 17 | } 18 | 19 | :local(.content) { 20 | padding-top: 15px; 21 | padding-left: 30px; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/divider-line/index.less: -------------------------------------------------------------------------------- 1 | .divider-line { 2 | height: 8px; 3 | // border-radius: 4px; 4 | width: 100%; 5 | // background-color: var(--color-fill-1); 6 | z-index: 100; 7 | margin: 0; 8 | position: relative; 9 | &::after { 10 | content: ''; 11 | position: absolute; 12 | top: 0; 13 | left: -9px; 14 | bottom: 0; 15 | right: 0; 16 | height: 100%; 17 | background: var(--color-fill-1); 18 | // border-radius: 4px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/divider-line/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.less'; 2 | const DividerLine: React.FC = () => { 3 | return
; 4 | }; 5 | 6 | export default DividerLine; 7 | -------------------------------------------------------------------------------- /src/components/drop-down-actions/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { Dropdown, DropDownProps } from 'antd'; 3 | import _ from 'lodash'; 4 | import React, { useMemo } from 'react'; 5 | 6 | const DropDownActions: React.FC = (props) => { 7 | const { 8 | menu, 9 | trigger = ['hover'], 10 | placement = 'bottomRight', 11 | children, 12 | ...rest 13 | } = props; 14 | const intl = useIntl(); 15 | 16 | const items = useMemo(() => { 17 | return menu?.items?.map((item: any) => ({ 18 | ..._.omit(item, 'locale'), 19 | label: item.locale ? intl.formatMessage({ id: item.label }) : item.label 20 | })); 21 | }, [menu?.items, intl]); 22 | return ( 23 | 32 | {children} 33 | 34 | ); 35 | }; 36 | 37 | export default DropDownActions; 38 | -------------------------------------------------------------------------------- /src/components/drop-down-buttons/index.less: -------------------------------------------------------------------------------- 1 | .dropdown-button.middle { 2 | height: 28px; 3 | width: 28px; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/echarts/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | BarSeriesOption, 3 | GaugeSeriesOption, 4 | LineSeriesOption, 5 | ScatterSeriesOption 6 | } from 'echarts/charts'; 7 | import { BarChart, GaugeChart, LineChart, ScatterChart } from 'echarts/charts'; 8 | import type { 9 | DatasetComponentOption, 10 | GridComponentOption, 11 | TitleComponentOption, 12 | TooltipComponentOption 13 | } from 'echarts/components'; 14 | import { 15 | DataZoomComponent, 16 | DatasetComponent, 17 | GridComponent, 18 | LegendComponent, 19 | TitleComponent, 20 | TooltipComponent, 21 | // (filter, sort) 22 | TransformComponent 23 | } from 'echarts/components'; 24 | import type { ComposeOption } from 'echarts/core'; 25 | import * as echarts from 'echarts/core'; 26 | import { LabelLayout, UniversalTransition } from 'echarts/features'; 27 | import { CanvasRenderer } from 'echarts/renderers'; 28 | 29 | type ECOption = ComposeOption< 30 | | BarSeriesOption 31 | | LineSeriesOption 32 | | TitleComponentOption 33 | | TooltipComponentOption 34 | | GridComponentOption 35 | | DatasetComponentOption 36 | | GaugeSeriesOption 37 | | ScatterSeriesOption 38 | >; 39 | 40 | // register components and charts 41 | echarts.use([ 42 | LegendComponent, 43 | TitleComponent, 44 | TooltipComponent, 45 | GridComponent, 46 | DatasetComponent, 47 | TransformComponent, 48 | DataZoomComponent, 49 | BarChart, 50 | LineChart, 51 | ScatterChart, 52 | GaugeChart, 53 | LabelLayout, 54 | UniversalTransition, 55 | CanvasRenderer 56 | ]); 57 | 58 | export type { ECOption }; 59 | 60 | export default echarts; 61 | -------------------------------------------------------------------------------- /src/components/echarts/types.ts: -------------------------------------------------------------------------------- 1 | import type { LegendComponentOption } from 'echarts/components'; 2 | export interface ChartProps { 3 | seriesData: any[]; 4 | showEmpty?: boolean; 5 | xAxisData: string[]; 6 | legendData?: LegendComponentOption['data']; 7 | labelFormatter?: (val?: any) => string; 8 | tooltipValueFormatter?: (val: any) => string; 9 | height: string | number; 10 | width?: string | number; 11 | title?: string; 12 | value?: number; 13 | smooth?: boolean; 14 | color?: string; 15 | yAxisName?: string; 16 | } 17 | 18 | export interface AreaChartItemProps { 19 | name: string; 20 | color: string; 21 | areaStyle: any; 22 | data: { time: string; value: number }[]; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/editor-wrap/index.less: -------------------------------------------------------------------------------- 1 | .editor-wrap { 2 | border-radius: var(--border-radius-mini); 3 | overflow: hidden; 4 | font-size: 0; 5 | 6 | .code-pre { 7 | margin-bottom: 0; 8 | } 9 | 10 | .editor-header { 11 | display: flex; 12 | padding-block: 0; 13 | padding-inline: 12px 10px; 14 | justify-content: space-between; 15 | align-items: center; 16 | background-color: var(--color-editor-header-bg); 17 | } 18 | // set scrollbar style 19 | .scrollbar { 20 | .slider { 21 | border-radius: 6px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/empty-data/index.tsx: -------------------------------------------------------------------------------- 1 | import { Empty } from 'antd'; 2 | import React from 'react'; 3 | 4 | const EmptyData: React.FC<{ 5 | height?: string | number; 6 | title?: React.ReactNode; 7 | }> = ({ height, title }) => { 8 | return ( 9 |
16 | {title && ( 17 |

21 | {title} 22 |

23 | )} 24 |
28 | 29 |
30 |
31 | ); 32 | }; 33 | 34 | export default EmptyData; 35 | -------------------------------------------------------------------------------- /src/components/form-buttons/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { Button, Space } from 'antd'; 3 | 4 | type FormButtonsProps = { 5 | onOk?: () => void; 6 | onCancel?: () => void; 7 | cancelText?: string; 8 | okText?: string; 9 | showOk?: boolean; 10 | showCancel?: boolean; 11 | htmlType?: 'submit' | 'button'; 12 | }; 13 | const FormButtons: React.FC = ({ 14 | onOk, 15 | onCancel, 16 | cancelText, 17 | okText, 18 | showCancel = true, 19 | showOk = true, 20 | htmlType = 'button' 21 | }) => { 22 | const intl = useIntl(); 23 | return ( 24 | 25 | {showOk && ( 26 | 34 | )} 35 | {showCancel && ( 36 | 39 | )} 40 | 41 | ); 42 | }; 43 | 44 | export default FormButtons; 45 | -------------------------------------------------------------------------------- /src/components/highlight-code/code-viewer-dark.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import CodeViewer from './code-viewer'; 3 | import './styles/dark.less'; 4 | 5 | interface CodeViewerProps { 6 | code: string; 7 | copyValue?: string; 8 | lang: string; 9 | autodetect?: boolean; 10 | ignoreIllegals?: boolean; 11 | copyable?: boolean; 12 | height?: string | number; 13 | style?: React.CSSProperties; 14 | } 15 | const DarkViewer: React.FC = (props) => { 16 | const { 17 | code, 18 | copyValue, 19 | lang, 20 | autodetect, 21 | ignoreIllegals, 22 | copyable, 23 | height = 'auto' 24 | } = props || {}; 25 | 26 | return ( 27 | 38 | ); 39 | }; 40 | 41 | export default memo(DarkViewer); 42 | -------------------------------------------------------------------------------- /src/components/highlight-code/code-viewer-light.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react'; 2 | import CodeViewer from './code-viewer'; 3 | import './styles/light.less'; 4 | 5 | interface CodeViewerProps { 6 | code: string; 7 | copyValue?: string; 8 | lang: string; 9 | autodetect?: boolean; 10 | ignoreIllegals?: boolean; 11 | copyable?: boolean; 12 | height?: string | number; 13 | style?: React.CSSProperties; 14 | } 15 | const LightViewer: React.FC = (props) => { 16 | const { 17 | code, 18 | copyValue, 19 | lang, 20 | autodetect, 21 | ignoreIllegals, 22 | copyable, 23 | style, 24 | height = 'auto' 25 | } = props || {}; 26 | 27 | return ( 28 | 39 | ); 40 | }; 41 | 42 | export default memo(LightViewer); 43 | -------------------------------------------------------------------------------- /src/components/highlight-code/utils.ts: -------------------------------------------------------------------------------- 1 | export function escapeHtml(value: string): string { 2 | return value 3 | .replace(/&/g, '&') 4 | .replace(//g, '>') 6 | .replace(/"/g, '"') 7 | .replace(/'/g, '''); 8 | } 9 | -------------------------------------------------------------------------------- /src/components/icon-font/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/components/icon-font/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/components/icon-font/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/components/icon-font/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/components/icon-font/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/components/icon-font/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/components/icon-font/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from '@ant-design/icons'; 2 | // import './iconfont/iconfont.js'; 3 | 4 | const IconFont = createFromIconfontCN({ 5 | scriptUrl: '//at.alicdn.com/t/c/font_4613488_gxrotg9r0p.js' 6 | }); 7 | 8 | export default IconFont; 9 | -------------------------------------------------------------------------------- /src/components/image-editor/index.less: -------------------------------------------------------------------------------- 1 | .editor-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100%; 5 | height: 100%; 6 | 7 | .tools { 8 | margin-bottom: 10px; 9 | display: flex; 10 | gap: 10px; 11 | } 12 | 13 | .editor-content { 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | overflow: hidden; 18 | } 19 | 20 | .overlay-canvas:hover { 21 | cursor: none !important; 22 | } 23 | 24 | .overlay-canvas.overlay-canvas--disabled:hover { 25 | cursor: default !important; 26 | } 27 | 28 | .upload-mask { 29 | &:hover { 30 | .close-btn { 31 | display: block; 32 | } 33 | } 34 | } 35 | 36 | .close-btn { 37 | display: none; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/label-selector/styles/label-item.less: -------------------------------------------------------------------------------- 1 | .label-item { 2 | display: flex; 3 | margin-bottom: 12px; 4 | align-items: center; 5 | justify-content: flex-start; 6 | 7 | .seprator { 8 | display: flex; 9 | flex: none; 10 | width: 12px; 11 | align-items: center; 12 | justify-content: center; 13 | color: var(--ant-color-text-tertiary); 14 | } 15 | 16 | .btn { 17 | width: 24px; 18 | margin-left: 10px; 19 | flex: none; 20 | } 21 | 22 | .label-key { 23 | flex: 1; 24 | } 25 | 26 | .label-value { 27 | flex: 1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/label-selector/styles/wrapper.less: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | position: relative; 3 | padding: 14px; 4 | padding-top: 34px; 5 | border: 1px solid var(--ant-color-border); 6 | border-radius: var(--border-radius-base); 7 | display: flex; 8 | width: 100%; 9 | flex-direction: column; 10 | 11 | :global { 12 | .label { 13 | position: absolute; 14 | left: 16px; 15 | line-height: 1; 16 | top: 12px; 17 | color: var(--ant-color-text-tertiary); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/label-selector/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import LabelInfo from '@/components/seal-form/components/label-info'; 2 | import React from 'react'; 3 | import styles from './styles/wrapper.less'; 4 | 5 | const Wrapper: React.FC<{ 6 | label?: React.ReactNode; 7 | description?: React.ReactNode; 8 | labelExtra?: React.ReactNode; 9 | children: React.ReactNode; 10 | }> = ({ children, label, description, labelExtra, ...rest }) => { 11 | return ( 12 |
13 | {label && ( 14 | 15 | 20 | 21 | )} 22 | {React.isValidElement(children) 23 | ? React.cloneElement(children, { ...rest }) 24 | : children} 25 |
26 | ); 27 | }; 28 | 29 | export default Wrapper; 30 | -------------------------------------------------------------------------------- /src/components/lang-select/index.tsx: -------------------------------------------------------------------------------- 1 | import langConfigMap from '@/locales/lang-config-map'; 2 | import { GlobalOutlined } from '@ant-design/icons'; 3 | import { getAllLocales, setLocale } from '@umijs/max'; 4 | import { Dropdown } from 'antd'; 5 | import { get } from 'lodash'; 6 | 7 | const LangSelect = () => { 8 | const allLocals = getAllLocales(); 9 | const items = allLocals.map((key) => { 10 | return { 11 | key, 12 | label: ( 13 | 17 | {get(langConfigMap, [key, 'label'])} 18 | 19 | ), 20 | onClick: () => { 21 | setLocale(key, false); 22 | } 23 | }; 24 | }); 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default LangSelect; 36 | -------------------------------------------------------------------------------- /src/components/list-input/list-item.tsx: -------------------------------------------------------------------------------- 1 | // import AutoComplete from '@/components/seal-form/auto-complete'; 2 | import { MinusOutlined } from '@ant-design/icons'; 3 | import { Button } from 'antd'; 4 | import React from 'react'; 5 | import HintInput from './hint-input'; 6 | import './styles/list-item.less'; 7 | 8 | interface LabelItemProps { 9 | onRemove: () => void; 10 | onChange: (value: string) => void; 11 | onBlur?: (e: any) => void; 12 | value: string; 13 | label?: string; 14 | placeholder?: string; 15 | options?: Global.HintOptions[]; 16 | } 17 | 18 | const ListItem: React.FC = (props) => { 19 | const { onRemove, onChange, onBlur, label, value, options } = props; 20 | 21 | const handleOnChange = (value: any) => { 22 | onChange(value); 23 | }; 24 | 25 | return ( 26 |
27 | 35 |
44 | ); 45 | }; 46 | 47 | export default React.memo(ListItem); 48 | -------------------------------------------------------------------------------- /src/components/list-input/style/list-item.less: -------------------------------------------------------------------------------- 1 | .list-item { 2 | display: flex; 3 | align-items: center; 4 | justify-content: flex-start; 5 | width: 100%; 6 | margin-bottom: 12px; 7 | 8 | .field-wrapper { 9 | flex: 1; 10 | } 11 | 12 | .btn { 13 | margin-left: 10px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/list-input/styles/list-item.less: -------------------------------------------------------------------------------- 1 | .list-item { 2 | display: flex; 3 | align-items: center; 4 | justify-content: flex-start; 5 | width: 100%; 6 | margin-bottom: 12px; 7 | 8 | .field-wrapper { 9 | flex: 1; 10 | } 11 | 12 | .btn { 13 | margin-left: 10px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/logs-viewer/config.ts: -------------------------------------------------------------------------------- 1 | export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g; 2 | export const replaceLineRegex = /\r\n/g; 3 | 4 | export const PageSize = 500; 5 | 6 | export const throttle = void>( 7 | func: T, 8 | wait: number 9 | ): ((this: ThisParameterType, ...args: Parameters) => void) => { 10 | let timeout: ReturnType | null = null; 11 | let previous = Date.now(); 12 | 13 | return function (this: ThisParameterType, ...args: Parameters): void { 14 | const now = Date.now(); 15 | const remaining = wait - (now - previous); 16 | const context = this as ThisParameterType; 17 | 18 | if (remaining <= 0 || remaining > wait) { 19 | if (timeout) { 20 | clearTimeout(timeout); 21 | timeout = null; 22 | } 23 | previous = now; 24 | func.apply(context, args); 25 | } else if (!timeout) { 26 | timeout = setTimeout(() => { 27 | previous = Date.now(); 28 | timeout = null; 29 | func.apply(context, args); 30 | }, remaining); 31 | } 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/logs-viewer/styles/logs-list.less: -------------------------------------------------------------------------------- 1 | .logs-wrap { 2 | background-color: var(--color-logs-bg); 3 | border-radius: var(--border-radius-mini); 4 | font-family: monospace, Menlo, Courier, 'Courier New', Consolas, Monaco, 5 | 'Liberation Mono' !important; 6 | 7 | .content { 8 | word-wrap: break-word; 9 | height: 100%; 10 | padding-right: 2px; 11 | 12 | &.line-break { 13 | word-wrap: break-word; 14 | } 15 | 16 | .text { 17 | min-height: 22px; 18 | 19 | &.numable { 20 | position: relative; 21 | padding-left: 45px; 22 | 23 | .line-num { 24 | display: flex; 25 | align-items: center; 26 | width: 40px; 27 | justify-content: center; 28 | position: absolute; 29 | left: 0; 30 | top: 0; 31 | background-color: rgba(71, 71, 71, 50%); 32 | } 33 | } 34 | } 35 | 36 | color: var(--color-logs-text); 37 | font-size: var(--font-size-small); 38 | line-height: 22px; 39 | white-space: pre-wrap; 40 | background-color: var(--color-logs-bg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/logs-viewer/styles/pagination.less: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | color: rgba(255, 255, 255, 100%); 7 | gap: 5px; 8 | 9 | .ant-btn:hover { 10 | color: rgba(255, 255, 255, 90%) !important; 11 | background-color: rgba(71, 71, 71, 100%) !important; 12 | } 13 | 14 | .ant-btn { 15 | background-color: rgba(71, 71, 71, 100%) !important; 16 | } 17 | 18 | .pages { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | height: 38px; 23 | width: 38px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/logs-viewer/styles/xterm-viewer.less: -------------------------------------------------------------------------------- 1 | .logs-viewer-wrap-w2 { 2 | .wrap { 3 | padding: 5px 0 5px 10px; 4 | background-color: var(--color-logs-bg); 5 | border-radius: var(--border-radius-mini); 6 | overflow: hidden; 7 | 8 | .content { 9 | word-wrap: break-word; 10 | height: 100%; 11 | 12 | &.line-break { 13 | word-wrap: break-word; 14 | } 15 | 16 | .text { 17 | height: 100%; 18 | } 19 | 20 | color: var(--color-logs-text); 21 | font-size: var(--font-size-small); 22 | line-height: 22px; 23 | white-space: pre-wrap; 24 | background-color: var(--color-logs-bg); 25 | } 26 | } 27 | 28 | .xterm { 29 | // height: 100% !important; 30 | 31 | .xterm-viewport { 32 | overflow-y: auto !important; 33 | 34 | &::-webkit-scrollbar { 35 | width: var(--scrollbar-size); 36 | height: var(--scrollbar-size); 37 | } 38 | 39 | &::-webkit-scrollbar-thumb { 40 | background-color: var(--color-scrollbar-thumb); 41 | border-radius: 4px; 42 | } 43 | 44 | &::-webkit-scrollbar-track { 45 | background-color: var(--color-scrollbar-track); 46 | border-radius: 4px; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/logs-viewer/use-logs-pagination.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { PageSize } from './config'; 3 | 4 | const useLogsPagination = () => { 5 | const [pageSize, setPageSize] = useState(PageSize); 6 | const [page, setPage] = useState(1); 7 | const [total, setTotal] = useState(1); 8 | 9 | const nextPage = () => { 10 | setPage(page + 1); 11 | }; 12 | 13 | const prePage = () => { 14 | let newPage = page - 1; 15 | if (newPage < 1) { 16 | newPage = 1; 17 | } 18 | setPage(newPage); 19 | }; 20 | 21 | const resetPage = () => { 22 | setPage(1); 23 | }; 24 | 25 | const setTotalPage = (total: number) => { 26 | setTotal(total); 27 | }; 28 | 29 | return { 30 | nextPage, 31 | resetPage, 32 | prePage, 33 | setPage, 34 | pageSize, 35 | setTotalPage, 36 | page, 37 | totalPage: total 38 | }; 39 | }; 40 | 41 | export default useLogsPagination; 42 | -------------------------------------------------------------------------------- /src/components/logs-viewer/use-size.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const useResizeObserver = (ref: React.RefObject) => { 4 | const [size, setSize] = useState({ width: 0, height: 0 }); 5 | 6 | useEffect(() => { 7 | const element = ref.current; 8 | if (!element) return; 9 | 10 | const observer = new ResizeObserver((entries) => { 11 | if (entries[0]) { 12 | const { width, height } = entries[0].contentRect; 13 | setSize({ width, height }); 14 | } 15 | }); 16 | 17 | observer.observe(element); 18 | 19 | return () => { 20 | observer.disconnect(); 21 | }; 22 | }, [ref]); 23 | 24 | return size; 25 | }; 26 | 27 | export default useResizeObserver; 28 | -------------------------------------------------------------------------------- /src/components/markdown-viewer/code-viewer.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/components/markdown-viewer/code-viewer.tsx -------------------------------------------------------------------------------- /src/components/markdown-viewer/utils.ts: -------------------------------------------------------------------------------- 1 | const htmlUnescapes: Record = { 2 | '&': '&', 3 | '<': '<', 4 | '>': '>', 5 | '"': '"', 6 | ''': "'" 7 | }; 8 | 9 | const reEscapedHtml = /&(?:amp|lt|gt|quot|#(?:0+)?39);/g; 10 | const reHasEscapedHtml = RegExp(reEscapedHtml.source); 11 | 12 | export const unescape = (str = '') => { 13 | return reHasEscapedHtml.test(str) 14 | ? str.replace(reEscapedHtml, (entity) => htmlUnescapes[entity] || "'") 15 | : str; 16 | }; 17 | 18 | export function escapeDollarNumber(text: string) { 19 | let escapedText = ''; 20 | 21 | for (let i = 0; i < text.length; i += 1) { 22 | let char = text[i]; 23 | const nextChar = text[i + 1] || ' '; 24 | 25 | if (char === '$' && nextChar >= '0' && nextChar <= '9') { 26 | char = '\\$'; 27 | } 28 | 29 | escapedText += char; 30 | } 31 | 32 | return escapedText; 33 | } 34 | 35 | export function escapeBrackets(text: string) { 36 | const pattern = 37 | /(```[\S\s]*?```|`.*?`)|\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g; 38 | return text.replaceAll( 39 | pattern, 40 | (match, codeBlock, squareBracket, roundBracket) => { 41 | if (codeBlock) { 42 | return codeBlock; 43 | } else if (squareBracket) { 44 | return `$$${squareBracket}$$`; 45 | } else if (roundBracket) { 46 | return `$${roundBracket}$`; 47 | } 48 | return match; 49 | } 50 | ); 51 | } 52 | 53 | export function escapeMhchem(text: string) { 54 | return text.replaceAll('$\\ce{', '$\\\\ce{').replaceAll('$\\pu{', '$\\\\pu{'); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/page-tools/index.less: -------------------------------------------------------------------------------- 1 | .page-tools { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | margin-top: 70px; 6 | 7 | .left { 8 | display: flex; 9 | align-items: center; 10 | font-size: var(--font-size-middle); 11 | } 12 | 13 | .right { 14 | display: flex; 15 | align-items: center; 16 | font-size: var(--font-size-middle); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/popover/index.less: -------------------------------------------------------------------------------- 1 | :local(.seal_custom_popover) { 2 | position: relative; 3 | padding-top: var(--ant-popover-inner-padding); 4 | border-radius: var(--ant-border-radius-lg); 5 | background-color: var(--color-white-1); 6 | box-shadow: var(--ant-box-shadow-secondary); 7 | overflow: hidden; 8 | padding-top: 80px; 9 | :global(.ant-popover-inner) { 10 | box-shadow: none; 11 | padding-top: 0; 12 | } 13 | :global(.ant-popover-content) { 14 | position: static; 15 | } 16 | :global(.ant-popover-title) { 17 | position: absolute; 18 | top: 0; 19 | padding-block: var(--ant-popover-inner-padding) 20 | var(--ant-popover-title-margin-bottom); 21 | margin-bottom: 0; 22 | background: #fff; 23 | padding-left: var(--ant-popover-inner-padding); 24 | left: 0; 25 | width: 100%; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/popover/index.tsx: -------------------------------------------------------------------------------- 1 | import { Popover } from 'antd'; 2 | import type { PopoverProps } from 'antd/lib/popover'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | const SealPopover: React.FC = (props) => { 7 | const { className, children, style, ...restProps } = props; 8 | 9 | return ( 10 |
11 | 17 | {children} 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default SealPopover; 24 | -------------------------------------------------------------------------------- /src/components/radio-buttons/index.less: -------------------------------------------------------------------------------- 1 | .radio-button-wrap { 2 | .item { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | font-size: var(--font-size-base); 7 | border-radius: var(--border-radius-base); 8 | height: 32px; 9 | padding: 0 8px; 10 | border: 1px solid var(--ant-color-border); 11 | cursor: pointer; 12 | 13 | &.active { 14 | background-color: var(--ant-color-fill-secondary); 15 | } 16 | 17 | &:hover { 18 | background-color: var(--ant-color-fill-secondary); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/radio-buttons/index.tsx: -------------------------------------------------------------------------------- 1 | import { Space } from 'antd'; 2 | import classNames from 'classnames'; 3 | import React from 'react'; 4 | import './index.less'; 5 | 6 | interface RadioButtonsProps { 7 | options: { value: any; label: React.ReactNode }[]; 8 | value: string; 9 | gap?: number; 10 | onChange: (value: string) => void; 11 | } 12 | const RadioButtons: React.FC = (props) => { 13 | const { options, value, onChange, gap = 12 } = props; 14 | return ( 15 | 16 | {options.map((option) => ( 17 | onChange({ target: { value: option.value } } as any)} 20 | className={classNames('item', { active: value === option.value })} 21 | > 22 | {option.label} 23 | 24 | ))} 25 | 26 | ); 27 | }; 28 | 29 | export default RadioButtons; 30 | -------------------------------------------------------------------------------- /src/components/scroller-modal/index.tsx: -------------------------------------------------------------------------------- 1 | import useBodyScroll from '@/hooks/use-body-scroll'; 2 | import { Modal, type ModalProps } from 'antd'; 3 | import React from 'react'; 4 | 5 | const ScrollerModal = (props: ModalProps) => { 6 | const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); 7 | 8 | React.useEffect(() => { 9 | if (props.open) { 10 | saveScrollHeight(); 11 | } else { 12 | restoreScrollHeight(); 13 | } 14 | }, [props.open]); 15 | 16 | return ; 17 | }; 18 | 19 | export default ScrollerModal; 20 | -------------------------------------------------------------------------------- /src/components/seal-form/components/label-info.less: -------------------------------------------------------------------------------- 1 | .label-text { 2 | display: flex; 3 | justify-content: flex-start; 4 | align-items: center; 5 | color: var(--ant-color-text-tertiary); 6 | font-size: var(--font-size-base); 7 | 8 | .note-info { 9 | margin-left: 4px; 10 | } 11 | 12 | .star { 13 | position: relative; 14 | top: 2px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/seal-form/components/label-info.tsx: -------------------------------------------------------------------------------- 1 | import { QuestionCircleOutlined } from '@ant-design/icons'; 2 | import { Tooltip } from 'antd'; 3 | import React from 'react'; 4 | import './label-info.less'; 5 | 6 | interface NoteInfoProps { 7 | required?: boolean; 8 | label: React.ReactNode; 9 | description?: React.ReactNode; 10 | labelExtra?: React.ReactNode; 11 | } 12 | const NoteInfo: React.FC = (props) => { 13 | const { required, description, label, labelExtra } = props || {}; 14 | if (!label) return null; 15 | const renderRequiredStar = required ? ( 16 | 17 | * 18 | 19 | ) : null; 20 | 21 | const renderQuestionIcon = description ? ( 22 | 26 | 27 | 28 | ) : null; 29 | 30 | const labelContent = ( 31 | <> 32 | {label} 33 | {(required || description) && ( 34 | 35 | {renderRequiredStar} 36 | {renderQuestionIcon} 37 | 38 | )} 39 | 40 | ); 41 | 42 | return ( 43 | 44 | {description ? ( 45 | {labelContent} 46 | ) : ( 47 | labelContent 48 | )} 49 | {labelExtra} 50 | 51 | ); 52 | }; 53 | 54 | export default NoteInfo; 55 | -------------------------------------------------------------------------------- /src/components/seal-form/config/components.ts: -------------------------------------------------------------------------------- 1 | import { Checkbox } from 'antd'; 2 | import SealInput from '../seal-input'; 3 | import SealSelect from '../seal-select'; 4 | import Slider from '../seal-slider'; 5 | import Switch from '../seal-switch'; 6 | 7 | const components: { 8 | InputNumber: typeof SealInput.Number; 9 | Select: typeof SealSelect; 10 | Slider: React.ComponentType; 11 | TextArea: typeof SealInput.TextArea; 12 | Input: typeof SealInput.Input; 13 | Checkbox: typeof Checkbox; 14 | Switch: typeof Switch; 15 | } = { 16 | InputNumber: SealInput.Number, 17 | Select: SealSelect, 18 | Slider: Slider as React.ComponentType, 19 | TextArea: SealInput.TextArea, 20 | Input: SealInput.Input, 21 | Checkbox: Checkbox, 22 | Switch: Switch 23 | }; 24 | 25 | export default components; 26 | -------------------------------------------------------------------------------- /src/components/seal-form/config/index.ts: -------------------------------------------------------------------------------- 1 | const WRAPHEIGHT = 54; 2 | const INPUTHEIGHT = 32; 3 | const BORDERRADIUS = 4; 4 | const BGCOLOR = 'transparent'; 5 | const INPUT_INNER_PADDING = 14; 6 | const LINE_HEIGHT = 1.57; 7 | 8 | export { 9 | BGCOLOR, 10 | BORDERRADIUS, 11 | INPUTHEIGHT, 12 | INPUT_INNER_PADDING, 13 | LINE_HEIGHT, 14 | WRAPHEIGHT 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/seal-form/field-component.tsx: -------------------------------------------------------------------------------- 1 | import { ParamsSchema } from '@/pages/playground/config/types'; 2 | import { useIntl } from '@umijs/max'; 3 | import React, { useCallback, useMemo } from 'react'; 4 | import LabelInfo from './components/label-info'; 5 | import componentsMap from './config/components'; 6 | 7 | const FieldComponent: React.FC = (props) => { 8 | const intl = useIntl(); 9 | const { type, label, attrs, style, value, ...rest } = props; 10 | const renderChild = useCallback( 11 | (type: string) => { 12 | switch (type) { 13 | case 'Checkbox': 14 | return ( 15 | 23 | ); 24 | default: 25 | return null; 26 | } 27 | }, 28 | [intl, type] 29 | ); 30 | const checkboxAttrs = useMemo(() => { 31 | return type === 'Checkbox' ? { checked: value } : { value: value }; 32 | }, [type, value]); 33 | return React.createElement( 34 | componentsMap[type], 35 | { 36 | ...rest, 37 | ...attrs, 38 | ...checkboxAttrs, 39 | style: { ...style, width: '100%' }, 40 | label: label.isLocalized 41 | ? intl.formatMessage({ id: label.text }) 42 | : label.text 43 | }, 44 | renderChild(type) 45 | ); 46 | }; 47 | 48 | export default React.memo(FieldComponent); 49 | -------------------------------------------------------------------------------- /src/components/seal-form/styles/cascader.less: -------------------------------------------------------------------------------- 1 | :local(.dropdown-visible) { 2 | :local(.ant-select-selector) { 3 | border-bottom: 0 !important; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/seal-form/styles/row-textarea.less: -------------------------------------------------------------------------------- 1 | .row-textarea { 2 | position: relative; 3 | 4 | &.focus { 5 | // padding-top: 9px; 6 | } 7 | 8 | .content-wrap { 9 | position: relative; 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | padding-right: 20px; 14 | cursor: pointer; 15 | 16 | &:hover { 17 | .clear-btn { 18 | display: flex; 19 | } 20 | } 21 | } 22 | 23 | textarea.ant-input { 24 | background-color: transparent; 25 | box-shadow: none; 26 | } 27 | 28 | .clear-btn { 29 | display: none; 30 | position: absolute; 31 | right: 6px; 32 | top: 50%; 33 | transform: translateY(-50%); 34 | } 35 | 36 | .textarea-label { 37 | position: relative; 38 | top: 4px; 39 | padding-left: 14px; 40 | } 41 | 42 | .content { 43 | flex: 1; 44 | width: 100px; 45 | height: 46px; 46 | line-height: 30px; 47 | padding: 8px 14px; 48 | padding-right: 4px; 49 | text-overflow: ellipsis; 50 | overflow: hidden; 51 | white-space: nowrap; 52 | color: var(--ant-color-text-secondary); 53 | 54 | .title { 55 | font-weight: var(--font-weight-normal); 56 | padding-right: 10px; 57 | color: var(--ant-color-text); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/seal-form/styles/slider.less: -------------------------------------------------------------------------------- 1 | :local(.slider-label) { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: flex-start; 5 | width: 100%; 6 | 7 | :global(.val) { 8 | color: var(--ant-color-text); 9 | } 10 | 11 | :global(.label-val) { 12 | position: absolute !important; 13 | top: -14px; 14 | right: -14px; 15 | width: 80px; 16 | border-radius: var(--border-radius-base); 17 | text-align: center; 18 | border: 1px solid var(--ant-color-border) !important; 19 | 20 | :global(.ant-input-number-input) { 21 | text-align: center !important; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/seal-form/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface SealFormItemProps { 4 | label?: React.ReactNode; 5 | required?: boolean; 6 | isInFormItems?: boolean; 7 | description?: React.ReactNode; 8 | extra?: React.ReactNode; 9 | addAfter?: React.ReactNode; 10 | allowNull?: boolean; 11 | loading?: React.ReactNode; 12 | labelExtra?: React.ReactNode; 13 | trim?: boolean; 14 | checkStatus?: 'success' | 'error' | 'warning' | ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/seal-form/wrapper/slider.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { INPUTHEIGHT, INPUT_INNER_PADDING } from '../config'; 3 | 4 | const SliderWrapper = styled.div` 5 | .__wrapper__ { 6 | height: 100%; 7 | justify-content: center; 8 | } 9 | .borderless { 10 | background-color: transparent; 11 | } 12 | padding-block: 0; 13 | padding-inline: 2px; 14 | input.ant-input-number-input { 15 | text-align: center !important; 16 | flex: 1; 17 | height: ${INPUTHEIGHT}px !important; 18 | padding-block: 5px; 19 | padding-inline: ${INPUT_INNER_PADDING}px; 20 | } 21 | .isfoucs-has-value { 22 | left: 0; 23 | } 24 | .slider-label { 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: flex-start; 28 | width: 100%; 29 | 30 | .val { 31 | color: var(--ant-color-text); 32 | } 33 | 34 | .label-val { 35 | position: absolute !important; 36 | top: -14px; 37 | right: -10px; 38 | width: 80px; 39 | border-radius: var(--border-radius-base); 40 | text-align: center; 41 | border: 1px solid var(--ant-color-border) !important; 42 | 43 | .ant-input-number-input { 44 | text-align: center !important; 45 | } 46 | } 47 | } 48 | `; 49 | 50 | export default SliderWrapper; 51 | -------------------------------------------------------------------------------- /src/components/seal-table/components/header.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from 'antd'; 2 | import React from 'react'; 3 | import { SealColumnProps } from '../types'; 4 | import TableHeader from './table-header'; 5 | 6 | interface HeaderProps { 7 | columns: SealColumnProps[]; 8 | onSort?: (dataIndex: string, order: any) => void; 9 | } 10 | 11 | const Header: React.FC = (props) => { 12 | const { onSort } = props; 13 | 14 | return ( 15 | 16 | {props.columns?.map((columnProps, i) => { 17 | const { 18 | title, 19 | dataIndex, 20 | align, 21 | span, 22 | headerStyle, 23 | sortOrder, 24 | sorter, 25 | defaultSortOrder 26 | } = columnProps as SealColumnProps; 27 | return ( 28 | 29 | 41 | 42 | ); 43 | })} 44 | 45 | ); 46 | }; 47 | 48 | export default React.memo(Header); 49 | -------------------------------------------------------------------------------- /src/components/seal-table/components/layout.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Row = styled.div.attrs<{ className?: string }>((props) => ({ 4 | className: props.className 5 | }))` 6 | width: 100%; 7 | display: flex; 8 | justify-content: flex-start; 9 | align-items: center; 10 | `; 11 | 12 | const Col = styled.div<{ 13 | $width?: string | number; 14 | $align?: string; 15 | $flexBasis?: string | number; 16 | $maxWidth?: string | number; 17 | }>` 18 | flex: ${(props) => (props.$width ? 'none' : 1)}; 19 | display: flex; 20 | justify-content: ${(props) => props.$align || 'flex-start'}; 21 | align-items: center; 22 | width: ${({ $width }) => 23 | $width ? (typeof $width === 'number' ? `${$width}px` : $width) : '10px'}; 24 | ${({ $flexBasis }) => ($flexBasis ? `flex-basis: ${$flexBasis};` : '')} 25 | ${({ $maxWidth }) => ($maxWidth ? `max-width: ${$maxWidth};` : '')} 26 | `; 27 | 28 | export { Col, Row }; 29 | -------------------------------------------------------------------------------- /src/components/seal-table/components/pagination.tsx: -------------------------------------------------------------------------------- 1 | import { Pagination, type PaginationProps } from 'antd'; 2 | import { memo } from 'react'; 3 | 4 | const PaginationComponent: React.FC = (props) => { 5 | return ; 6 | }; 7 | 8 | export default memo(PaginationComponent); 9 | -------------------------------------------------------------------------------- /src/components/seal-table/components/row-children.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/row-children.less'; 2 | 3 | const RowChildren = (props: any) => { 4 | const { children } = props; 5 | 6 | return
{children}
; 7 | }; 8 | 9 | export default RowChildren; 10 | -------------------------------------------------------------------------------- /src/components/seal-table/components/table-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox } from 'antd'; 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | import '../styles/skeleton.less'; 5 | 6 | const SkeletonItem = () => { 7 | return ( 8 |
9 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | const Wrapper = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | gap: 20px; 19 | `; 20 | 21 | const TableSkeleton = () => { 22 | const dataSource = Array.from({ length: 5 }); 23 | return ( 24 | 25 | {dataSource.map((item, index) => ( 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | export default React.memo(TableSkeleton); 33 | -------------------------------------------------------------------------------- /src/components/seal-table/row-context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const RowContext = React.createContext({}); 4 | 5 | export default RowContext; 6 | -------------------------------------------------------------------------------- /src/components/seal-table/styles/cell.less: -------------------------------------------------------------------------------- 1 | .cell { 2 | padding: var(--ant-table-cell-padding-block) 3 | var(--ant-table-cell-padding-inline); 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-start; 7 | min-height: 68px; 8 | word-break: break-word; 9 | min-width: 20px; 10 | overflow: hidden; 11 | 12 | .cell-content { 13 | max-width: 100%; 14 | line-height: 18px; 15 | } 16 | 17 | &-left { 18 | justify-content: flex-start; 19 | } 20 | 21 | &-right { 22 | justify-content: flex-end; 23 | } 24 | 25 | &-center { 26 | justify-content: center; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/seal-table/styles/row-children.less: -------------------------------------------------------------------------------- 1 | .row-children { 2 | position: relative; 3 | display: flex; 4 | align-items: center; 5 | height: 54px; 6 | border-radius: var(--border-radius-mdium); 7 | transition: all 0.2s ease; 8 | 9 | &:hover { 10 | background-color: var(--ant-table-row-hover-bg); 11 | transition: all 0.2s ease; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/seal-table/styles/skeleton.less: -------------------------------------------------------------------------------- 1 | .row-skeleton { 2 | display: flex; 3 | justify-content: flex-start; 4 | background-color: var(--ant-color-fill-tertiary); 5 | padding: 16px; 6 | height: 68px; 7 | border-radius: var(--border-radius-base); 8 | align-items: center; 9 | 10 | .holder { 11 | width: 33px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/seal-table/table-context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TableContext = React.createContext({}); 4 | 5 | export default TableContext; 6 | -------------------------------------------------------------------------------- /src/components/short-cuts/index.less: -------------------------------------------------------------------------------- 1 | .short-cuts { 2 | margin-bottom: 10px; 3 | 4 | .ant-table-container .ant-table-content table tr > td { 5 | height: auto; 6 | border-bottom: var(--ant-line-width) var(--ant-line-type) 7 | var(--ant-table-border-color); 8 | } 9 | 10 | .ant-table-container .ant-table-content table { 11 | border-spacing: 0; 12 | 13 | .ant-table-tbody .ant-table-row { 14 | background: transparent; 15 | } 16 | 17 | tr > td:first-child { 18 | border-radius: 0; 19 | } 20 | 21 | tr > td:last-child { 22 | border-radius: 0; 23 | } 24 | 25 | .ant-table-thead > tr > th { 26 | background-color: var(--ant-color-fill-tertiary); 27 | height: 36px; 28 | padding-block: 0; 29 | } 30 | } 31 | 32 | .ant-table-fixed-header { 33 | // .ant-table-body { 34 | // overflow-y: auto !important; 35 | // } 36 | .ant-table-thead > tr > th { 37 | background-color: var(--ant-color-fill-tertiary); 38 | height: 36px; 39 | padding-block: 0; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/short-cuts/keymap.ts: -------------------------------------------------------------------------------- 1 | import { KeyMap } from '@/config/hotkeys'; 2 | export default [ 3 | { 4 | scope: 'playground', 5 | command: 'shortcuts.playground.newmessage', 6 | keybinding: KeyMap.CREATE.iconKeybinding 7 | }, 8 | { 9 | scope: 'playground', 10 | command: 'shortcuts.playground.clearmessage', 11 | keybinding: KeyMap.CLEAR.iconKeybinding 12 | }, 13 | { 14 | scope: 'playground', 15 | command: 'shortcuts.playground.toggleparams', 16 | keybinding: KeyMap.RIGHT.iconKeybinding 17 | }, 18 | { 19 | scope: 'models', 20 | // span: { 21 | // rowSpan: 3, 22 | // colSpan: 1 23 | // }, 24 | command: 'shortcuts.models.newmodelHF', 25 | keybinding: KeyMap.NEW1.iconKeybinding 26 | }, 27 | { 28 | scope: 'models', 29 | command: 'shortcuts.models.newmodelLM', 30 | keybinding: KeyMap.NEW2.iconKeybinding 31 | // span: { 32 | // rowSpan: 0, 33 | // colSpan: 0 34 | // } 35 | }, 36 | { 37 | scope: 'models', 38 | command: 'shortcuts.models.search', 39 | keybinding: KeyMap.SEARCH.iconKeybinding 40 | // span: { 41 | // rowSpan: 0, 42 | // colSpan: 0 43 | // } 44 | }, 45 | { 46 | scope: 'resources', 47 | command: 'shortcuts.resources.addworker', 48 | keybinding: KeyMap.CREATE.iconKeybinding 49 | }, 50 | { 51 | scope: 'API keys', 52 | command: 'shortcuts.apikeys.new', 53 | keybinding: KeyMap.CREATE.iconKeybinding 54 | }, 55 | { 56 | scope: 'users', 57 | command: 'shortcuts.users.new', 58 | keybinding: KeyMap.CREATE.iconKeybinding 59 | } 60 | ]; 61 | -------------------------------------------------------------------------------- /src/components/simple-table/cell.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/components/simple-table/cell.tsx -------------------------------------------------------------------------------- /src/components/simple-table/header.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import React from 'react'; 3 | 4 | interface TableHeaderProps { 5 | columns: any[]; 6 | } 7 | interface TableHeaderProps { 8 | columns: any[]; 9 | } 10 | const TableHeader = ({ columns }: TableHeaderProps) => { 11 | const intl = useIntl(); 12 | return ( 13 | 14 | {columns.map((column: any, index: number) => { 15 | return ( 16 | 17 | 18 | {column.locale 19 | ? intl.formatMessage({ id: column.title }) 20 | : column.title} 21 | 22 | 23 | ); 24 | })} 25 | 26 | ); 27 | }; 28 | export default React.memo(TableHeader); 29 | -------------------------------------------------------------------------------- /src/components/simple-table/index.less: -------------------------------------------------------------------------------- 1 | .simple-table { 2 | table-layout: auto; 3 | width: 100%; 4 | text-align: left; 5 | 6 | &.simple-table-bordered { 7 | border-collapse: collapse; 8 | border-spacing: 0; 9 | 10 | td { 11 | border-bottom: 1px solid var(--color-white-light-1); 12 | } 13 | 14 | th { 15 | border-bottom: 1px solid var(--color-white-light-1); 16 | } 17 | } 18 | 19 | th { 20 | height: 36px; 21 | font-weight: 600; 22 | } 23 | 24 | td { 25 | color: var(--color-white-quaternary); 26 | } 27 | 28 | .cell-span { 29 | display: flex; 30 | padding: 6px; 31 | min-height: 32px; 32 | } 33 | 34 | .cell-header { 35 | font-weight: var(--font-weight-medium); 36 | } 37 | 38 | &.light { 39 | th { 40 | color: var(--ant-color-text); 41 | border-bottom: 1px solid var(--ant-color-split); 42 | } 43 | 44 | td { 45 | color: var(--ant-color-text); 46 | border-bottom: 1px solid var(--ant-color-split); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/speech-content/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SpeechItem from './speech-item'; 3 | 4 | interface SpeechContentProps { 5 | dataList: any[]; 6 | loading?: boolean; 7 | } 8 | 9 | const SpeechContent: React.FC = (props) => { 10 | return ( 11 | <> 12 | {props.dataList.map((item) => ( 13 | 14 | ))} 15 | 16 | ); 17 | }; 18 | 19 | export default React.memo(SpeechContent); 20 | -------------------------------------------------------------------------------- /src/components/speech-content/styles/slider-progress.less: -------------------------------------------------------------------------------- 1 | .slider-progress { 2 | &:hover { 3 | .ant-slider-track { 4 | background-color: var(--ant-blue-5); 5 | } 6 | } 7 | 8 | .ant-slider-rail { 9 | border-radius: var(--border-radius-base); 10 | height: 2px; 11 | } 12 | 13 | .ant-slider-track { 14 | background-color: var(--ant-blue-5); 15 | height: 2px; 16 | } 17 | 18 | .ant-slider-handle { 19 | display: none; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/status-tag/copy-btn.less: -------------------------------------------------------------------------------- 1 | :local(.status-content-wrapper) { 2 | &:hover { 3 | :global(.copy-button-wrapper) { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: flex-start; 8 | } 9 | } 10 | 11 | :global { 12 | .copy-button-wrapper { 13 | display: none; 14 | background-color: rgba(255, 255, 255, 20%); 15 | position: absolute; 16 | z-index: 10; 17 | right: 0; 18 | top: 0; 19 | padding: 2px; 20 | border-radius: 0 4px; 21 | } 22 | 23 | .simplebar-content { 24 | width: max-content; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/status-tag/index.less: -------------------------------------------------------------------------------- 1 | .status-tag { 2 | position: relative; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | padding: 2px 10px; 7 | border-radius: 20px; 8 | min-width: 76px; 9 | width: max-content; 10 | height: 24px; 11 | font-size: var(--font-size-small); 12 | overflow: hidden; 13 | 14 | .txt { 15 | display: flex; 16 | align-items: center; 17 | height: 20px; 18 | 19 | &.err { 20 | cursor: default; 21 | } 22 | } 23 | 24 | &.download { 25 | border: 1px solid var(--ant-color-success-border-hover) !important; 26 | } 27 | 28 | .download { 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | position: absolute; 33 | left: 0; 34 | top: 0; 35 | bottom: 0; 36 | height: 100%; 37 | background-color: var(--ant-color-success-border); 38 | } 39 | 40 | .progress { 41 | position: relative; 42 | z-index: 10; 43 | color: var(--ant-color-success); 44 | } 45 | } 46 | 47 | .tooltip-scrollbar { 48 | .simplebar-scrollbar::before { 49 | width: var(--scrollbar-size); 50 | background: var(--scrollbar-handle-light-bg); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/tags-wrapper/index.less: -------------------------------------------------------------------------------- 1 | .tags-wrapper { 2 | display: flex; 3 | width: 100%; 4 | overflow: hidden; 5 | 6 | .more { 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | padding: 2px 6px; 11 | border-radius: 4px; 12 | font-size: 12px; 13 | height: 22px; 14 | opacity: 0.7; 15 | } 16 | 17 | .tags-content { 18 | display: flex; 19 | } 20 | } 21 | 22 | .tags-wrapper-dropdown { 23 | .ant-dropdown-menu { 24 | .ant-dropdown-menu-item { 25 | background-color: transparent; 26 | cursor: default; 27 | 28 | &:hover { 29 | background-color: transparent; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/tags-wrapper/more-dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { MoreOutlined } from '@ant-design/icons'; 2 | import { Dropdown, Tag } from 'antd'; 3 | import React from 'react'; 4 | 5 | interface MoreDropdownProps { 6 | items: any[]; 7 | } 8 | 9 | const MoreDropdown: React.FC = (props) => { 10 | const { items } = props; 11 | return ( 12 | <> 13 | {items && items.length > 0 ? ( 14 | 20 | 21 | 22 | 23 | 24 | ) : null} 25 | 26 | ); 27 | }; 28 | 29 | export default React.memo(MoreDropdown); 30 | -------------------------------------------------------------------------------- /src/components/tags-wrapper/theme-tag.tsx: -------------------------------------------------------------------------------- 1 | import useUserSettings from '@/hooks/use-user-settings'; 2 | import { Tag, TagProps } from 'antd'; 3 | import React from 'react'; 4 | 5 | const ThemeTag: React.FC = ({ 6 | opacity, 7 | style, 8 | children, 9 | ...restProps 10 | }) => { 11 | const { userSettings } = useUserSettings(); 12 | const { isDarkTheme } = userSettings; 13 | return ( 14 | 21 | {children} 22 | 23 | ); 24 | }; 25 | 26 | ThemeTag.displayName = 'ThemeTag'; 27 | 28 | export default ThemeTag; 29 | -------------------------------------------------------------------------------- /src/components/theme-toggle/index.tsx: -------------------------------------------------------------------------------- 1 | import useUserSettings from '@/hooks/use-user-settings'; 2 | import { MoonOutlined, SunOutlined } from '@ant-design/icons'; 3 | import { createStyles } from 'antd-style'; 4 | import React from 'react'; 5 | 6 | const useStyles = createStyles(({ token, css }) => ({ 7 | wrapper: css` 8 | display: inline-flex; 9 | justify-content: center; 10 | align-items: center; 11 | cursor: pointer; 12 | color: ${token.colorText}; 13 | font-size: var(--font-size-base); 14 | padding: 0 8px; 15 | gap: 8px; 16 | &:hover { 17 | color: ${token.colorTextTertiary}; 18 | } 19 | .anticon { 20 | font-size: 16px; 21 | } 22 | ` 23 | })); 24 | 25 | const ThemeToggle: React.FC = () => { 26 | const { setTheme, userSettings } = useUserSettings(); 27 | const { styles } = useStyles(); 28 | 29 | const handleChangeTheme = () => { 30 | const currentTheme = 31 | userSettings.theme === 'realDark' ? 'light' : 'realDark'; 32 | setTheme(currentTheme); 33 | }; 34 | 35 | return ( 36 |
37 | {userSettings.theme === 'realDark' ? : } 38 |
39 | ); 40 | }; 41 | 42 | export default ThemeToggle; 43 | -------------------------------------------------------------------------------- /src/components/tooltip-list/index.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | const UL = styled.ul` 6 | list-style: none; 7 | padding-left: 0; 8 | margin: 0; 9 | display: flex; 10 | flex-direction: column; 11 | li { 12 | display: flex; 13 | flex-direction: column; 14 | .title { 15 | font-weight: 600; 16 | color: var(--ant-color-text-light-solid); 17 | } 18 | .content { 19 | color: var(--color-white-tertiary); 20 | display: inline-flex; 21 | } 22 | } 23 | `; 24 | 25 | interface TooltipListProps { 26 | list: { title: any; tips: string }[]; 27 | } 28 | 29 | const TooltipList: React.FC = (props) => { 30 | const intl = useIntl(); 31 | const { list } = props; 32 | return ( 33 |
    34 | {list.map((item, index: number) => { 35 | return ( 36 |
  • 37 | 38 | {item.title?.locale 39 | ? intl.formatMessage({ id: item.title?.text || '' }) 40 | : item.title} 41 | : 42 | 43 | 44 | {intl.formatMessage({ id: item.tips })} 45 | 46 |
  • 47 | ); 48 | })} 49 |
50 | ); 51 | }; 52 | 53 | export default TooltipList; 54 | -------------------------------------------------------------------------------- /src/components/transition/index.less: -------------------------------------------------------------------------------- 1 | .transition-wrapper { 2 | border-radius: 8px; 3 | display: flex; 4 | flex-direction: column; 5 | overflow: hidden; 6 | 7 | &.bordered { 8 | border-width: var(--ant-line-width); 9 | border-style: var(--ant-line-type); 10 | border-color: var(--ant-color-border); 11 | background-color: var(--color-white-1); 12 | } 13 | 14 | &.filled { 15 | border-color: var(--ant-color-border); 16 | 17 | .transition-content-wrapper { 18 | background-color: var(--color-fill-1); 19 | } 20 | } 21 | 22 | .header { 23 | background-color: var(--color-fill-1); 24 | cursor: pointer; 25 | padding: 8px 16px; 26 | } 27 | 28 | .transition-content-wrapper { 29 | overflow: hidden; 30 | transition: all 300ms ease-in-out; 31 | 32 | .transition-content { 33 | height: max-content; 34 | background-color: var(--color-white-1); 35 | border-radius: var(--border-radius-base); 36 | } 37 | } 38 | 39 | .ant-input { 40 | &::-webkit-scrollbar-track { 41 | width: 0 !important; 42 | color: transparent; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/type-word-effect/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const TypingEffect: React.FC<{ text?: string }> = ({ text = '' }) => { 4 | const [displayedText, setDisplayedText] = useState(''); 5 | 6 | useEffect(() => { 7 | let index = 0; 8 | const intervalId = setInterval(() => { 9 | setDisplayedText((prev) => prev + text[index]); 10 | index += 1; 11 | if (index === text.length) { 12 | clearInterval(intervalId); 13 | } 14 | }, 20); 15 | 16 | return () => clearInterval(intervalId); 17 | }, [text]); 18 | 19 | return
{displayedText}
; 20 | }; 21 | 22 | export default TypingEffect; 23 | -------------------------------------------------------------------------------- /src/components/util-bar/index.less: -------------------------------------------------------------------------------- 1 | .util-bar-box { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | flex-direction: column; 7 | align-items: center; 8 | 9 | .title { 10 | font-weight: var(--font-weight-medium); 11 | padding: 20px 0; 12 | } 13 | 14 | .ant-progress.ant-progress-circle .ant-progress-text { 15 | font-size: 20px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/util-bar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Progress } from 'antd'; 2 | import './index.less'; 3 | 4 | interface UitilBarProps { 5 | title?: string; 6 | percent: number; 7 | steps?: number; 8 | gapDegree?: number; 9 | strokeWidth?: number; 10 | size?: number; 11 | trailColor?: string; 12 | strokeColor?: string; 13 | } 14 | const UitilBar: React.FC = (props) => { 15 | const { 16 | percent, 17 | steps = 10, 18 | gapDegree = 170, 19 | strokeWidth = 10, 20 | title, 21 | size = 150, 22 | strokeColor, 23 | trailColor = 'rgba(221,221,221,.5)' 24 | } = props; 25 | 26 | const strokeColorFunc = (percent: number) => { 27 | if (percent <= 50) { 28 | return 'var(--color-chart-green)'; 29 | } 30 | if (percent <= 80) { 31 | return 'var(--color-chart-glod)'; 32 | } 33 | return 'var(--color-chart-red)'; 34 | }; 35 | return ( 36 |
37 | {title && {title}} 38 | 48 |
49 | ); 50 | }; 51 | 52 | export default UitilBar; 53 | -------------------------------------------------------------------------------- /src/components/version-info/index.less: -------------------------------------------------------------------------------- 1 | .version-box { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | 6 | .img { 7 | margin-top: 16px; 8 | margin-bottom: 30px; 9 | text-align: center; 10 | height: 30px; 11 | 12 | img { 13 | height: 100%; 14 | } 15 | } 16 | 17 | .title { 18 | font-weight: var(--font-weight-medium); 19 | text-align: center; 20 | font-size: var(--font-size-middle); 21 | margin-block: 30px 10px; 22 | } 23 | 24 | .ver { 25 | margin-bottom: 20px; 26 | line-height: 32px; 27 | display: flex; 28 | justify-content: center; 29 | font-size: var(--font-size-middle); 30 | color: var(--ant-color-text-secondary); 31 | gap: 10px; 32 | 33 | .label { 34 | display: flex; 35 | justify-content: flex-start; 36 | font-size: var(--font-size-middle); 37 | } 38 | 39 | .dev.val { 40 | display: flex; 41 | flex-direction: column; 42 | 43 | .item { 44 | display: flex; 45 | } 46 | 47 | .tl { 48 | display: flex; 49 | justify-content: flex-start; 50 | width: 60px; 51 | } 52 | } 53 | } 54 | 55 | .upgrade-text { 56 | display: flex; 57 | justify-content: space-between; 58 | padding-block: 20px 4px; 59 | align-items: center; 60 | width: 100%; 61 | border-top: 1px solid var(--ant-color-split); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/config/breakpoints.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | xs: 0, 3 | sm: 576, 4 | md: 768, 5 | lg: 992, 6 | xl: 1200, 7 | xxl: 1600 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/driver-config.ts: -------------------------------------------------------------------------------- 1 | const driverIDMap = { 2 | deployModel: 'deploy-model' 3 | }; 4 | 5 | const driverConfig = {}; 6 | -------------------------------------------------------------------------------- /src/config/global.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Global { 2 | interface Pagination { 3 | page: number; 4 | perPage?: number; 5 | watch?: boolean; 6 | } 7 | interface PageResponse { 8 | items: T[]; 9 | pagination: { 10 | total: number; 11 | totalPage: number; 12 | page: number; 13 | perPage: number; 14 | }; 15 | } 16 | 17 | interface UserInfo { 18 | username: string; 19 | is_admin: boolean; 20 | full_name: string; 21 | require_password_change: boolean; 22 | id: number; 23 | } 24 | 25 | interface BaseListItem { 26 | key: string; 27 | locale?: boolean; 28 | value: T; 29 | } 30 | 31 | interface BaseOption { 32 | label: string; 33 | locale?: boolean; 34 | value: T; 35 | meta?: Record; 36 | } 37 | 38 | interface HintOptions { 39 | label: string; 40 | value: string; 41 | opts?: Array>; 42 | } 43 | 44 | type SearchParams = Pagination & { search?: string }; 45 | 46 | type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info'; 47 | } 48 | 49 | interface Window { 50 | __GPUSTACK_BODY_SCROLLER__?: any; 51 | } 52 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { PageActionType, StatusType } from './types'; 2 | 3 | export const PageAction: Record = { 4 | CREATE: 'create', 5 | UPDATE: 'update', 6 | VIEW: 'view' 7 | }; 8 | 9 | export const StatusColorMap: Record< 10 | StatusType, 11 | { text: string; bg: string; border?: string } 12 | > = { 13 | error: { 14 | text: `var(--ant-red-6)`, 15 | bg: `var(--ant-red-1)` 16 | }, 17 | warning: { 18 | text: `var(--ant-orange-6)`, 19 | bg: `var(--ant-orange-1)` 20 | }, 21 | transitioning: { 22 | text: `var(--ant-blue-6)`, 23 | bg: `var(--ant-blue-1)` 24 | }, 25 | success: { 26 | text: `var(--ant-color-success)`, 27 | bg: `var(--ant-color-success-bg)` 28 | }, 29 | inactive: { 30 | text: `var(--ant-color-text-tertiary)`, 31 | border: `rgba(200,200,200,1)`, 32 | bg: `var(--ant-color-fill)` 33 | } 34 | }; 35 | 36 | export const StatusMaps = { 37 | error: 'error', 38 | warning: 'warning', 39 | transitioning: 'transitioning', 40 | success: 'success', 41 | inactive: 'inactive' 42 | }; 43 | 44 | export const WatchEventType = { 45 | CREATE: 1, 46 | UPDATE: 2, 47 | DELETE: 3 48 | }; 49 | 50 | export const PasswordReg = 51 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*_+])[a-zA-Z\d!@#$%^&*_+]{6,12}$/; 52 | 53 | export const uppercaseReg = /(?=.*[A-Z])/; 54 | 55 | export const lowercaseReg = /(?=.*[a-z])/; 56 | 57 | export const digitReg = /(?=.*\d)/; 58 | 59 | export const specialCharacterReg = /(?=.*[\W_])/; 60 | 61 | export const noSpaceReg = /(?=\S+$)/; 62 | 63 | export const lengthReg = /^.{6,12}$/; 64 | -------------------------------------------------------------------------------- /src/config/route-cachekey.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | '/playground/text-to-image': '/playground/text-to-image', 3 | '/playground/speech': '/playground/speech' 4 | }; 5 | -------------------------------------------------------------------------------- /src/config/theme/index.ts: -------------------------------------------------------------------------------- 1 | import dark from './dark'; 2 | import light from './light'; 3 | 4 | export const colorPrimary = '#007BFF'; 5 | 6 | export default { 7 | light, 8 | dark, 9 | colorPrimary: '#007BFF' 10 | }; 11 | -------------------------------------------------------------------------------- /src/config/types.ts: -------------------------------------------------------------------------------- 1 | export interface DropDownItem { 2 | key: string; 3 | label: string; 4 | icon?: React.ReactNode; 5 | disabled?: boolean; 6 | iconfont?: boolean; 7 | } 8 | 9 | export type PageActionType = 'create' | 'update' | 'view'; 10 | 11 | export type StatusType = 12 | | 'error' 13 | | 'warning' 14 | | 'transitioning' 15 | | 'success' 16 | | 'inactive'; 17 | -------------------------------------------------------------------------------- /src/constants/external-links.ts: -------------------------------------------------------------------------------- 1 | const externalLinks = { 2 | documentation: 'https://docs.gpustack.ai/', 3 | github: 'https://github.com/gpustack/gpustack', 4 | discord: 'https://discord.gg/VXYJzuaqwD', 5 | site: 'https://gpustack.ai/', 6 | release: 'https://github.com/gpustack/gpustack/releases', 7 | reportIssue: 'https://github.com/gpustack/gpustack/issues/new/choose', 8 | faq: 'https://docs.gpustack.ai/latest/faq/', 9 | resetPassword: 10 | 'https://docs.gpustack.ai/latest/troubleshooting/?h=reset#reset-admin-password' 11 | }; 12 | 13 | export default externalLinks; 14 | 15 | export const externalRefer = { 16 | audioPermission: `${externalLinks.documentation}latest/user-guide/playground/audio/#provide-audio-file` 17 | }; 18 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NAME = 'seal'; 2 | 3 | export const INPUT_WIDTH = { 4 | default: 500, 5 | large: 600, 6 | small: 400, 7 | mini: 300 8 | }; 9 | -------------------------------------------------------------------------------- /src/global.tsx: -------------------------------------------------------------------------------- 1 | // Logic that runs globally and before the application will be executed here 2 | 3 | import dayjs from 'dayjs'; 4 | import localizedFormat from 'dayjs/plugin/localizedFormat'; 5 | import relativeTime from 'dayjs/plugin/relativeTime'; 6 | import timezone from 'dayjs/plugin/timezone'; 7 | import utc from 'dayjs/plugin/utc'; 8 | 9 | dayjs.extend(localizedFormat); 10 | dayjs.extend(utc); 11 | dayjs.extend(timezone); 12 | dayjs.extend(relativeTime); 13 | -------------------------------------------------------------------------------- /src/hooks/use-app-utils.ts: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { message } from 'antd'; 3 | 4 | const useAppUtils = () => { 5 | const intl = useIntl(); 6 | const [messageApi, contextHolder] = message.useMessage(); 7 | 8 | const getRuleMessage = ( 9 | type: 'input' | 'select', 10 | name: string, 11 | locale = true 12 | ) => { 13 | const nameStr = locale ? intl.formatMessage({ id: name }) : name; 14 | 15 | if (type === 'input') { 16 | return intl.formatMessage( 17 | { id: 'common.form.rule.input' }, 18 | { name: nameStr } 19 | ); 20 | } 21 | return intl.formatMessage( 22 | { id: 'common.form.rule.select' }, 23 | { name: nameStr } 24 | ); 25 | }; 26 | 27 | const showSuccess = (msg?: string) => { 28 | messageApi.success( 29 | msg || intl.formatMessage({ id: 'common.message.success' }) 30 | ); 31 | }; 32 | 33 | return { 34 | getRuleMessage, 35 | showSuccess 36 | }; 37 | }; 38 | 39 | export default useAppUtils; 40 | -------------------------------------------------------------------------------- /src/hooks/use-broadcast.ts: -------------------------------------------------------------------------------- 1 | // broadcast channel hook 2 | 3 | import { useEffect, useRef } from 'react'; 4 | 5 | export const useBroadcast = () => { 6 | const broadcastChannel = useRef(null); 7 | 8 | useEffect(() => { 9 | broadcastChannel.current = new BroadcastChannel('broadcast_channel'); 10 | 11 | return () => { 12 | broadcastChannel.current?.close(); 13 | console.log('broadcast channel closed'); 14 | }; 15 | }, []); 16 | 17 | return { broadcastChannel }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/hooks/use-copy-to-clipboard.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | const useCopyToClipboard = () => { 4 | const [copied, setCopied] = useState(false); 5 | 6 | const copyToClipboard = useCallback(async (text: string) => { 7 | try { 8 | if (navigator.clipboard) { 9 | await navigator.clipboard.writeText(text); 10 | setCopied(true); 11 | setTimeout(() => { 12 | setCopied(false); 13 | }, 3000); 14 | } 15 | } catch (error) { 16 | setCopied(false); 17 | } 18 | }, []); 19 | 20 | return { copied, copyToClipboard }; 21 | }; 22 | 23 | export default useCopyToClipboard; 24 | -------------------------------------------------------------------------------- /src/hooks/use-delete-modal.ts: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { Modal, message, type ModalFuncProps } from 'antd'; 3 | import { useRef } from 'react'; 4 | 5 | export default function useDeleteModal() { 6 | const intl = useIntl(); 7 | const modalRef = useRef(); 8 | const showDeleteModal = (config: ModalFuncProps = {}) => { 9 | modalRef.current = Modal.confirm({ 10 | ...config, 11 | okText: intl.formatMessage({ 12 | id: 'common.button.delete' 13 | }), 14 | onCancel: () => { 15 | config.onCancel?.(); 16 | modalRef.current.destroy?.(); 17 | }, 18 | onOk: async () => { 19 | await config.onOk?.(); 20 | message.success(intl.formatMessage({ id: 'common.message.success' })); 21 | } 22 | }); 23 | }; 24 | 25 | return { showDeleteModal }; 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/use-driver.ts: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { driver, type Config } from 'driver.js'; 3 | import { useEffect, useRef } from 'react'; 4 | 5 | export const useDriver = (config?: Config & { id: string }) => { 6 | const intl = useIntl(); 7 | const driverRef = useRef(null); 8 | 9 | const handleDoNotShowAgain = () => {}; 10 | 11 | const init = () => { 12 | driverRef.current = driver({ 13 | overlayOpacity: 0.2, 14 | animate: false, 15 | ...config 16 | }); 17 | }; 18 | 19 | const start = () => { 20 | if (!driverRef.current) { 21 | init(); 22 | } 23 | driverRef.current.drive(); 24 | }; 25 | 26 | useEffect(() => { 27 | return () => { 28 | driverRef.current?.destroy(); 29 | }; 30 | }, []); 31 | 32 | return { start, initDriver: init, driver: driverRef.current }; 33 | }; 34 | 35 | export default useDriver; 36 | -------------------------------------------------------------------------------- /src/hooks/use-tab-active.ts: -------------------------------------------------------------------------------- 1 | import { getActiveStatus, setActiveStatus } from '@/atoms/tab-active'; 2 | 3 | const tabsMap = { 4 | resources: 'resources', 5 | userSettings: 'userSettings' 6 | }; 7 | 8 | export default function useTabActive() { 9 | const setTabActive = (key: string, value: any) => { 10 | setActiveStatus(key, value); 11 | }; 12 | const getTabActive = (key: string) => { 13 | return getActiveStatus(key); 14 | }; 15 | return { 16 | setTabActive, 17 | getTabActive, 18 | tabsMap 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/use-table-sort.ts: -------------------------------------------------------------------------------- 1 | import type { SortOrder } from 'antd/es/table/interface'; 2 | import { useState } from 'react'; 3 | 4 | export default function useTableSort({ 5 | defaultSortOrder = 'descend' 6 | }: { 7 | defaultSortOrder?: SortOrder; 8 | }) { 9 | const [sortOrder, setSortOrder] = useState(defaultSortOrder); 10 | 11 | const handleSortChange = (order: SortOrder) => { 12 | if (order) { 13 | setSortOrder(order); 14 | } else if (defaultSortOrder === 'descend') { 15 | setSortOrder('ascend'); 16 | } else if (defaultSortOrder === 'ascend') { 17 | setSortOrder('descend'); 18 | } 19 | }; 20 | 21 | return { 22 | sortOrder, 23 | setSortOrder: handleSortChange 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/layouts/Exception.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { history, useIntl, type IRoute } from '@umijs/max'; 4 | import { Button, Result } from 'antd'; 5 | import React from 'react'; 6 | 7 | const Exception: React.FC<{ 8 | children: React.ReactNode; 9 | route?: IRoute; 10 | notFound?: React.ReactNode; 11 | noAccessible?: React.ReactNode; 12 | unAccessible?: React.ReactNode; 13 | noFound?: React.ReactNode; 14 | }> = (props) => { 15 | const intl = useIntl(); 16 | // render custom 404 17 | console.log('exception====', props.route); 18 | return ( 19 | (!props.route && (props.noFound || props.notFound)) || 20 | // render custom 403 21 | (props.route?.unaccessible && (props.unAccessible || props.noAccessible)) || 22 | // render default exception 23 | ((!props.route || props.route?.unaccessible) && ( 24 | history.push('/')}> 34 | {intl.formatMessage({ id: 'common.button.back' })} 35 | 36 | } 37 | /> 38 | )) || 39 | // normal render 40 | props.children 41 | ); 42 | }; 43 | 44 | export default Exception; 45 | -------------------------------------------------------------------------------- /src/layouts/Layout.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: 480px) { 2 | .umi-plugin-layout-container { 3 | width: 100% !important; 4 | } 5 | 6 | .umi-plugin-layout-container > * { 7 | border-radius: 0 !important; 8 | } 9 | } 10 | 11 | .umi-plugin-layout-menu .anticon { 12 | margin-right: 8px; 13 | } 14 | 15 | .umi-plugin-layout-menu .ant-dropdown-menu-item { 16 | min-width: 160px; 17 | } 18 | 19 | .umi-plugin-layout-right { 20 | display: flex !important; 21 | height: 100%; 22 | overflow: hidden; 23 | } 24 | 25 | .umi-plugin-layout-right .umi-plugin-layout-action { 26 | display: flex; 27 | align-items: center; 28 | height: 100%; 29 | padding: 0 12px; 30 | cursor: pointer; 31 | transition: all 0.3s; 32 | } 33 | 34 | .umi-plugin-layout-right .umi-plugin-layout-action > i { 35 | color: rgba(255, 255, 255, 85%); 36 | vertical-align: middle; 37 | } 38 | 39 | .umi-plugin-layout-right .umi-plugin-layout-action:hover { 40 | background: rgba(0, 0, 0, 2.5%); 41 | } 42 | 43 | .umi-plugin-layout-right .umi-plugin-layout-action.opened { 44 | background: rgba(0, 0, 0, 2.5%); 45 | } 46 | 47 | .umi-plugin-layout-right .umi-plugin-layout-search { 48 | padding: 0 12px; 49 | } 50 | 51 | .umi-plugin-layout-right .umi-plugin-layout-search:hover { 52 | background: transparent; 53 | } 54 | 55 | .umi-plugin-layout-name { 56 | margin-left: 8px; 57 | } 58 | 59 | .umi-plugin-layout-name.umi-plugin-layout-hide-avatar-img { 60 | margin-left: 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/layouts/Logo.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import GpustackLogo from '@/assets/images/gpustack-logo.png'; 3 | import SmallLogo from '@/assets/images/small-logo-200x200.png'; 4 | import React from 'react'; 5 | 6 | const LogoIcon: React.FC = () => { 7 | return logo; 8 | }; 9 | const SLogoIcon: React.FC = () => { 10 | return logo; 11 | }; 12 | 13 | export { LogoIcon, SLogoIcon }; 14 | -------------------------------------------------------------------------------- /src/layouts/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import type { ErrorInfo } from 'react'; 2 | import React from 'react'; 3 | import ErrorResult from './error-result'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/ban-types 6 | class ErrorBoundary extends React.Component< 7 | { children?: React.ReactNode }, 8 | { hasError: boolean; errorInfo: string } 9 | > { 10 | state = { hasError: false, errorInfo: '' }; 11 | 12 | static getDerivedStateFromError(error: Error) { 13 | return { hasError: true, errorInfo: error.message }; 14 | } 15 | 16 | componentDidCatch(error: any, errorInfo: ErrorInfo) { 17 | // You can also log the error to an error reporting service 18 | // eslint-disable-next-line no-console 19 | console.log(error, errorInfo); 20 | } 21 | 22 | render() { 23 | if (this.state.hasError) { 24 | // You can render any custom fallback UI 25 | return ; 26 | } 27 | return this.props.children; 28 | } 29 | } 30 | 31 | export default ErrorBoundary; 32 | -------------------------------------------------------------------------------- /src/layouts/error-result.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { Result } from 'antd'; 3 | import styled from 'styled-components'; 4 | 5 | const Inner = styled.div` 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: center; 10 | gap: 16px; 11 | `; 12 | 13 | interface ErrorResultProps { 14 | extra?: string; 15 | } 16 | 17 | function isChunkLoadError(msg?: string): boolean { 18 | if (typeof msg !== 'string') return false; 19 | const jsChunkFailed = msg.includes('Loading chunk'); 20 | const cssChunkFailed = msg.includes('Loading CSS chunk'); 21 | 22 | return (jsChunkFailed || cssChunkFailed) && msg.includes('failed'); 23 | } 24 | 25 | const ErrorResult: React.FC = ({ extra }) => { 26 | const intl = useIntl(); 27 | return ( 28 | 37 | ); 38 | }; 39 | 40 | export default ErrorResult; 41 | -------------------------------------------------------------------------------- /src/layouts/icons.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as icons from '@ant-design/icons'; 3 | export default icons 4 | -------------------------------------------------------------------------------- /src/layouts/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // This file is generated by Umi automatically 3 | // DO NOT CHANGE IT MANUALLY! 4 | export type TempType = string 5 | -------------------------------------------------------------------------------- /src/layouts/render-links.tsx: -------------------------------------------------------------------------------- 1 | import { MailOutlined } from '@ant-design/icons'; 2 | import { Menu, type MenuProps } from 'antd'; 3 | import React from 'react'; 4 | 5 | type MenuItem = Required['items'][number]; 6 | const items: MenuItem[] = [ 7 | { 8 | key: 'sub1', 9 | icon: , 10 | label: 'Navigation One', 11 | children: [ 12 | { 13 | key: '1-1', 14 | label: 'Item 1' 15 | }, 16 | { 17 | key: '1-2', 18 | label: 'Item 2' 19 | } 20 | ] 21 | } 22 | ]; 23 | 24 | const RenderLinks: React.FC = () => { 25 | return ; 26 | }; 27 | 28 | export default React.memo(RenderLinks); 29 | -------------------------------------------------------------------------------- /src/layouts/runtime.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import icons from './icons'; 4 | 5 | function formatIcon(name: string) { 6 | return name 7 | .replace(name[0], name[0].toUpperCase()) 8 | .replace(/-(w)/g, function (all, letter) { 9 | return letter.toUpperCase(); 10 | }); 11 | } 12 | 13 | export function patchRoutes({ routes, initialState }) { 14 | Object.keys(routes).forEach((key) => { 15 | const { icon } = routes[key]; 16 | if (icon && typeof icon === 'string') { 17 | const upperIcon = formatIcon(icon); 18 | if (icons[upperIcon] || icons[upperIcon + 'Outlined']) { 19 | routes[key].icon = React.createElement( 20 | icons[upperIcon] || icons[upperIcon + 'Outlined'] 21 | ); 22 | } 23 | } 24 | }); 25 | } 26 | 27 | export function renderMenuIcon(icon: string) { 28 | const upperIcon = formatIcon(icon); 29 | if (icons[upperIcon] || icons[upperIcon + 'Outlined']) { 30 | return React.createElement( 31 | icons[upperIcon] || icons[upperIcon + 'Outlined'] 32 | ); 33 | } 34 | return ; 35 | } 36 | -------------------------------------------------------------------------------- /src/layouts/runtimeConfig.d.ts: -------------------------------------------------------------------------------- 1 | import type { RunTimeLayoutConfig } from './types'; 2 | export interface IRuntimeConfig { 3 | layout?: RunTimeLayoutConfig; 4 | } 5 | -------------------------------------------------------------------------------- /src/layouts/types.d.ts: -------------------------------------------------------------------------------- 1 | import type InitialStateType from '@@/plugin-initialState/@@initialState'; 2 | import '@ant-design/pro-components'; 3 | import type { HeaderProps, ProLayoutProps } from '@ant-design/pro-components'; 4 | type InitDataType = ReturnType; 5 | 6 | import type { IConfigFromPlugins } from '@@/core/pluginConfig'; 7 | 8 | export type RunTimeLayoutConfig = (initData: InitDataType) => Omit< 9 | ProLayoutProps, 10 | 'rightContentRender' 11 | > & { 12 | childrenRender?: (dom: JSX.Element, props: ProLayoutProps) => React.ReactNode; 13 | unAccessible?: JSX.Element; 14 | noFound?: JSX.Element; 15 | logout?: (initialState: InitDataType['initialState']) => Promise | void; 16 | rightContentRender?: 17 | | (( 18 | headerProps: HeaderProps, 19 | dom: JSX.Element, 20 | props: { 21 | userConfig: IConfigFromPlugins['layout']; 22 | runtimeConfig: RunTimeLayoutConfig; 23 | loading: InitDataType['loading']; 24 | initialState: InitDataType['initialState']; 25 | setInitialState: InitDataType['setInitialState']; 26 | } 27 | ) => JSX.Element) 28 | | false; 29 | rightRender?: ( 30 | initialState: InitDataType['initialState'], 31 | setInitialState: InitDataType['setInitialState'], 32 | runtimeConfig: RunTimeLayoutConfig 33 | ) => JSX.Element; 34 | }; 35 | -------------------------------------------------------------------------------- /src/locales/en-US.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description The directory name used in require.context must match the language configuration names defined in lang-config-map.ts. 3 | * @example Directories like 'en-US' or 'zh-CN' should correspond exactly to the configuration names in lang-config-map.ts. 4 | */ 5 | 6 | // @ts-ignore 7 | const requireContext = require.context(`./en-US`, false, /\.ts$/); 8 | 9 | let languageConfig: Record = {}; 10 | 11 | requireContext.keys().forEach((fileName: any) => { 12 | const moduleConfig = requireContext(fileName).default; 13 | languageConfig = { 14 | ...languageConfig, 15 | ...moduleConfig 16 | }; 17 | }); 18 | 19 | export default languageConfig; 20 | -------------------------------------------------------------------------------- /src/locales/en-US/apikeys.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'apikeys.title': 'API Keys', 3 | 'apikeys.table.apikeys': 'keys', 4 | 'apikeys.button.create': 'New API Key', 5 | 'apikeys.title.save': 'Save API Key', 6 | 'apikeys.form.expiretime': 'Expiration', 7 | 'apikeys.form.apikey': 'API Key', 8 | 'apikeys.table.name': 'Key Name', 9 | 'apikeys.table.save.tips': 10 | 'Make sure to copy your key immediately. You will not be able to see it again.', 11 | 'apikeys.form.expiration.7days': '7 days', 12 | 'apikeys.form.expiration.1month': '1 month', 13 | 'apikeys.form.expiration.6months': '6 months', 14 | 'apikeys.form.expiration.never': 'No expiration' 15 | }; 16 | -------------------------------------------------------------------------------- /src/locales/en-US/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboard.title': 'Dashboard', 3 | 'dashboard.workers': 'Workers', 4 | 'dashboard.models': 'Models', 5 | 'dashboard.totalgpus': 'GPUs', 6 | 'dashboard.allocategpus': 'Allocated GPUs', 7 | 'dashboard.instances': 'Instances', 8 | 'dashboard.systemload': 'System Load', 9 | 'dashboard.memory': 'RAM', 10 | 'dashboard.disk': 'Storage', 11 | 'dashboard.vram': 'VRAM', 12 | 'dashboard.cpuutilization': 'Average CPU Utilization', 13 | 'dashboard.memoryutilization': 'Average RAM Utilization', 14 | 'dashboard.diskutilization': 'Storage Utilization', 15 | 'dashboard.vramutilization': 'Average VRAM Utilization', 16 | 'dashboard.gpuutilization': 'Average GPU Utilization', 17 | 'dashboard.usage': 'Usage', 18 | 'dashboard.apirequest': 'API Requests', 19 | 'dashboard.tokens': 'Token Usage', 20 | 'dashboard.topusers': 'Top Users', 21 | 'dashboard.activeModels': 'Active Models', 22 | 'dashboard.runninginstances': 'Running Instances', 23 | 'dashboard.activeModels.name': 'Model Name', 24 | 'dashboard.allocatevram': 'Allocated VRAM / RAM', 25 | 'dashboard.usage.selectuser': 'Select users', 26 | 'dashboard.usage.selectmodel': 'Select models', 27 | 'dashboard.usage.export': 'Export Data', 28 | 'dashboard.usage.export.user': 'User', 29 | 'dashboard.usage.export.model': 'Model', 30 | 'dashboard.usage.export.date': 'Date' 31 | }; 32 | -------------------------------------------------------------------------------- /src/locales/en-US/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard': 'Dashboard', 3 | 'menu.playground': 'Playground', 4 | 'menu.playground.rerank': 'Rerank', 5 | 'menu.playground.embedding': 'Embedding', 6 | 'menu.playground.chat': 'Chat', 7 | 'menu.playground.speech': 'Audio', 8 | 'menu.playground.text2images': 'Image', 9 | 'menu.compare': 'Compare', 10 | 'menu.models': 'Models', 11 | 'menu.models.modelList': 'Deploy & Manage', 12 | 'menu.models.modelCatalog': 'Catalog', 13 | 'menu.models.catalog': 'Model Catalog', 14 | 'menu.modelCatalog': 'Catalog', 15 | 'menu.resources': 'Resources', 16 | 'menu.apikeys': 'API Keys', 17 | 'menu.users': 'Users', 18 | 'menu.profile': 'Profile', 19 | 'menu.login': 'Login', 20 | 'menu.usage': 'Usage', 21 | 'menu.404': '404' 22 | }; 23 | -------------------------------------------------------------------------------- /src/locales/en-US/shortcuts.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'shortcuts.search.placeholder': 'Search keybindings', 3 | 'shortcuts.title': 'Keyboard shortcuts', 4 | 'shortcuts.playground.newmessage': 'New message', 5 | 'shortcuts.playground.clearmessage': 'Clear messages', 6 | 'shortcuts.playground.toggleparams': 'Collapse/Expand parameters', 7 | 'shortcuts.models.newmodelHF': 'Deploy Hugging Face model', 8 | 'shortcuts.models.newmodelLM': 'Deploy Ollama model', 9 | 'shortcuts.models.search': 'Search models from Hugging Face', 10 | 'shortcuts.resources.addworker': 'Add worker', 11 | 'shortcuts.apikeys.new': 'New API key', 12 | 'shortcuts.users.new': 'New user' 13 | }; 14 | -------------------------------------------------------------------------------- /src/locales/en-US/usage.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'usage.title': 'Usage', 3 | 'usage.filter.user': 'Filter by User', 4 | 'usage.filter.model': 'Filter by Model' 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/ja-JP.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description The directory name used in require.context must match the language configuration names defined in lang-config-map.ts. 3 | * @example Directories like 'en-US' or 'zh-CN' should correspond exactly to the configuration names in lang-config-map.ts. 4 | */ 5 | 6 | // @ts-ignore 7 | const requireContext = require.context(`./ja-JP`, false, /\.ts$/); 8 | 9 | let languageConfig: Record = {}; 10 | 11 | requireContext.keys().forEach((fileName: any) => { 12 | const moduleConfig = requireContext(fileName).default; 13 | languageConfig = { 14 | ...languageConfig, 15 | ...moduleConfig 16 | }; 17 | }); 18 | 19 | export default languageConfig; 20 | -------------------------------------------------------------------------------- /src/locales/ja-JP/apikeys.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'apikeys.title': 'APIキー', 3 | 'apikeys.table.apikeys': 'キー', 4 | 'apikeys.button.create': '新しいAPIキーを作成', 5 | 'apikeys.title.save': 'APIキーを保存', 6 | 'apikeys.form.expiretime': '有効期限', 7 | 'apikeys.form.apikey': 'APIキー', 8 | 'apikeys.table.name': 'キー名', 9 | 'apikeys.table.save.tips': 10 | 'キーをすぐにコピーしてください。一度閉じると再表示はできません。', 11 | 'apikeys.form.expiration.7days': '7日間', 12 | 'apikeys.form.expiration.1month': '1ヶ月', 13 | 'apikeys.form.expiration.6months': '6ヶ月', 14 | 'apikeys.form.expiration.never': '無期限' 15 | }; 16 | -------------------------------------------------------------------------------- /src/locales/ja-JP/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard': 'ダッシュボード', 3 | 'menu.playground': 'プレイグラウンド', 4 | 'menu.playground.rerank': 'リランク', 5 | 'menu.playground.embedding': '埋め込み', 6 | 'menu.playground.chat': 'チャット', 7 | 'menu.playground.speech': '音声', 8 | 'menu.playground.text2images': '画像生成', 9 | 'menu.compare': '比較', 10 | 'menu.models': 'モデル', 11 | 'menu.models.modelList': 'デプロイと管理', 12 | 'menu.models.modelCatalog': 'カタログ', 13 | 'menu.models.catalog': 'モデルカタログ', 14 | 'menu.modelCatalog': 'カタログ', 15 | 'menu.resources': 'リソース', 16 | 'menu.apikeys': 'APIキー', 17 | 'menu.users': 'ユーザー', 18 | 'menu.profile': 'プロフィール', 19 | 'menu.login': 'ログイン', 20 | 'menu.usage': '使用状況', 21 | 'menu.404': '404' 22 | }; 23 | -------------------------------------------------------------------------------- /src/locales/ja-JP/shortcuts.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'shortcuts.search.placeholder': 'キー設定を検索', 3 | 'shortcuts.title': 'ショートカット', 4 | 'shortcuts.playground.newmessage': '新しいメッセージ', 5 | 'shortcuts.playground.clearmessage': 'メッセージをクリア', 6 | 'shortcuts.playground.toggleparams': 'パラメータを折りたたむ/展開', 7 | 'shortcuts.models.newmodelHF': 'Hugging Faceモデルをデプロイ', 8 | 'shortcuts.models.newmodelLM': 'Ollamaモデルをデプロイ', 9 | 'shortcuts.models.search': 'Hugging Faceからモデルを検索', 10 | 'shortcuts.resources.addworker': 'ワーカーを追加', 11 | 'shortcuts.apikeys.new': '新しいAPIキー', 12 | 'shortcuts.users.new': '新しいユーザー' 13 | }; 14 | -------------------------------------------------------------------------------- /src/locales/ja-JP/usage.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'usage.title': '使用状況', 3 | 'usage.filter.user': 'ユーザーでフィルタ', 4 | 'usage.filter.model': 'モデルでフィルタ' 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/ja-JP/users.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'users.title': 'ユーザー', 3 | 'users.button.create': 'ユーザーを追加', 4 | 'users.form.edit': 'ユーザーを編集', 5 | 'users.form.create': 'ユーザーを追加', 6 | 'users.table.username': 'ユーザー名', 7 | 'users.table.role': '役割', 8 | 'users.form.fullname': 'フルネーム', 9 | 'users.table.user': 'ユーザー', 10 | 'users.form.admin': '管理者', 11 | 'users.form.user': '一般ユーザー', 12 | 'users.form.newpassword': '新しいパスワード', 13 | 'users.form.currentpassword': '現在のパスワード', 14 | 'users.form.updatepassword': 'パスワードを変更', 15 | 'users.form.rule.password': 16 | '大文字と小文字、数字、特殊文字(!@#$%^&*_+)を含む6〜12文字で、スペースは使用できません。', 17 | 'users.password.uppcase': '少なくとも1つの大文字を含む', 18 | 'users.password.lowercase': '少なくとも1つの小文字を含む', 19 | 'users.password.number': '少なくとも1つの数字を含む', 20 | 'users.password.special': '少なくとも1つの特殊文字を含む', 21 | 'users.password.length': '6〜12文字の長さ', 22 | 'users.password.modify.title': 'パスワードを変更', 23 | 'users.password.modify.description': 24 | 'アカウントのセキュリティのため、初期パスワードを変更してください。', 25 | 'users.password.confirm': '新しいパスワードを確認', 26 | 'users.password.confirm.empty': '新しいパスワードを確認してください。', 27 | 'users.password.confirm.error': '入力された2つのパスワードが一致しません。', 28 | 'users.login.title': 'ログイン', 29 | 'users.version.islatest': 'GPUStack {version} は最新バージョンです', 30 | 'users.version.update': 'GPUStack {version} が利用可能です', 31 | 'users.settings.title': 'User Settings' 32 | }; 33 | 34 | // ========== To-Do: Translate Keys (Remove After Translation) ========== 35 | // 1. 'users.settings.title': 'User Settings' 36 | // ========== End of To-Do List ========== 37 | -------------------------------------------------------------------------------- /src/locales/ru-RU.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description The directory name used in require.context must match the language configuration names defined in lang-config-map.ts. 3 | * @example Directories like 'en-US' or 'zh-CN' should correspond exactly to the configuration names in lang-config-map.ts. 4 | */ 5 | 6 | // @ts-ignore 7 | const requireContext = require.context(`./ru-RU`, false, /\.ts$/); 8 | 9 | let languageConfig: Record = {}; 10 | 11 | requireContext.keys().forEach((fileName: any) => { 12 | const moduleConfig = requireContext(fileName).default; 13 | languageConfig = { 14 | ...languageConfig, 15 | ...moduleConfig 16 | }; 17 | }); 18 | 19 | export default languageConfig; 20 | -------------------------------------------------------------------------------- /src/locales/ru-RU/apikeys.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'apikeys.title': 'API-ключи', 3 | 'apikeys.table.apikeys': 'Ключи', 4 | 'apikeys.button.create': 'Создать API-ключ', 5 | 'apikeys.title.save': 'Сохранить API-ключ', 6 | 'apikeys.form.expiretime': 'Срок действия', 7 | 'apikeys.form.apikey': 'API-ключ', 8 | 'apikeys.table.name': 'Название ключа', 9 | 'apikeys.table.save.tips': 10 | 'Обязательно скопируйте ключ сразу. Вы не сможете увидеть его снова.', 11 | 'apikeys.form.expiration.7days': '7 дней', 12 | 'apikeys.form.expiration.1month': '1 месяц', 13 | 'apikeys.form.expiration.6months': '6 месяцев', 14 | 'apikeys.form.expiration.never': 'Без срока действия' 15 | }; 16 | -------------------------------------------------------------------------------- /src/locales/ru-RU/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard': 'Панель управления', 3 | 'menu.playground': 'Песочница', 4 | 'menu.playground.rerank': 'Переранжирование', 5 | 'menu.playground.embedding': 'Векторизация', 6 | 'menu.playground.chat': 'Чат', 7 | 'menu.playground.speech': 'Аудио', 8 | 'menu.playground.text2images': 'Генерация изображений', 9 | 'menu.compare': 'Сравнение', 10 | 'menu.models': 'Модели', 11 | 'menu.models.modelList': 'Развертывание и управление', 12 | 'menu.models.modelCatalog': 'Каталог', 13 | 'menu.models.catalog': 'Каталог моделей', 14 | 'menu.modelCatalog': 'Каталог', 15 | 'menu.resources': 'Ресурсы', 16 | 'menu.apikeys': 'API-ключи', 17 | 'menu.users': 'Пользователи', 18 | 'menu.profile': 'Профиль', 19 | 'menu.login': 'Авторизация', 20 | 'menu.usage': 'Использование', 21 | 'menu.404': '404' 22 | }; 23 | -------------------------------------------------------------------------------- /src/locales/ru-RU/shortcuts.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'shortcuts.search.placeholder': 'Поиск сочетаний клавиш', 3 | 'shortcuts.title': 'Сочетания клавиш', 4 | 'shortcuts.playground.newmessage': 'Новое сообщение', 5 | 'shortcuts.playground.clearmessage': 'Очистить сообщения', 6 | 'shortcuts.playground.toggleparams': 'Свернуть/развернуть параметры', 7 | 'shortcuts.models.newmodelHF': 'Развернуть модель Hugging Face', 8 | 'shortcuts.models.newmodelLM': 'Развернуть модель Ollama', 9 | 'shortcuts.models.search': 'Поиск моделей в Hugging Face', 10 | 'shortcuts.resources.addworker': 'Добавить воркер', 11 | 'shortcuts.apikeys.new': 'Создать API-ключ', 12 | 'shortcuts.users.new': 'Создать пользователя' 13 | }; 14 | -------------------------------------------------------------------------------- /src/locales/ru-RU/usage.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'usage.title': 'Использование', 3 | 'usage.filter.user': 'Фильтр по пользователю', 4 | 'usage.filter.model': 'Фильтр по модели' 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/zh-CN.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description The directory name used in require.context must match the language configuration names defined in lang-config-map.ts. 3 | * @example Directories like 'en-US' or 'zh-CN' should correspond exactly to the configuration names in lang-config-map.ts. 4 | */ 5 | 6 | // @ts-ignore 7 | const requireContext = require.context(`./zh-CN`, false, /\.ts$/); 8 | 9 | let languageConfig: Record = {}; 10 | 11 | requireContext.keys().forEach((fileName: any) => { 12 | const moduleConfig = requireContext(fileName).default; 13 | languageConfig = { 14 | ...languageConfig, 15 | ...moduleConfig 16 | }; 17 | }); 18 | 19 | export default languageConfig; 20 | -------------------------------------------------------------------------------- /src/locales/zh-CN/apikeys.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'apikeys.title': 'API 密钥', 3 | 'apikeys.table.apikeys': '密钥', 4 | 'apikeys.button.create': '新建 API 密钥', 5 | 'apikeys.title.save': '保存 API 密钥', 6 | 'apikeys.form.expiretime': '过期时间', 7 | 'apikeys.form.apikey': 'API 密钥', 8 | 'apikeys.table.name': '密钥名称', 9 | 'apikeys.table.save.tips': '确保立即复制您的密钥。您将无法再次看到它!', 10 | 'apikeys.form.expiration.7days': '7天', 11 | 'apikeys.form.expiration.1month': '1个月', 12 | 'apikeys.form.expiration.6months': '6个月', 13 | 'apikeys.form.expiration.never': '永不过期' 14 | }; 15 | -------------------------------------------------------------------------------- /src/locales/zh-CN/dashboard.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboard.title': '概览', 3 | 'dashboard.workers': 'Workers', 4 | 'dashboard.models': '模型', 5 | 'dashboard.totalgpus': 'GPUs', 6 | 'dashboard.allocategpus': '已分配 GPU 数量', 7 | 'dashboard.instances': '实例', 8 | 'dashboard.systemload': '系统负载', 9 | 'dashboard.memory': '内存', 10 | 'dashboard.disk': '磁盘', 11 | 'dashboard.vram': '显存', 12 | 'dashboard.cpuutilization': '平均 CPU 利用率', 13 | 'dashboard.memoryutilization': '平均内存利用率', 14 | 'dashboard.diskutilization': '磁盘利用率', 15 | 'dashboard.vramutilization': '平均显存利用率', 16 | 'dashboard.gpuutilization': '平均 GPU 利用率', 17 | 'dashboard.usage': '使用量', 18 | 'dashboard.apirequest': 'API 请求', 19 | 'dashboard.tokens': 'Token 使用量', 20 | 'dashboard.topusers': '用户排行', 21 | 'dashboard.activeModels': '活跃模型', 22 | 'dashboard.activeModels.name': '模型名称', 23 | 'dashboard.runninginstances': '运行实例', 24 | 'dashboard.allocatevram': '已分配显存 / 内存', 25 | 'dashboard.usage.selectuser': '选择用户', 26 | 'dashboard.usage.selectmodel': '选择模型', 27 | 'dashboard.usage.export': '导出数据', 28 | 'dashboard.usage.export.user': '用户', 29 | 'dashboard.usage.export.model': '模型', 30 | 'dashboard.usage.export.date': '日期' 31 | }; 32 | -------------------------------------------------------------------------------- /src/locales/zh-CN/menu.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard': '概览', 3 | 'menu.playground': '试验场', 4 | 'menu.playground.rerank': '重排', 5 | 'menu.playground.embedding': '文本嵌入', 6 | 'menu.playground.chat': '对话', 7 | 'menu.playground.speech': '语音', 8 | 'menu.playground.text2images': '图像', 9 | 'menu.compare': '多模型对比', 10 | 'menu.models': '模型', 11 | 'menu.models.modelList': '部署与管理', 12 | 'menu.models.modelCatalog': '模型库', 13 | 'menu.modelCatalog': '模型库', 14 | 'menu.models.catalog': '模型库', 15 | 'menu.resources': '资源', 16 | 'menu.apikeys': 'API 密钥', 17 | 'menu.users': '用户', 18 | 'menu.profile': '个人信息', 19 | 'menu.login': '登录', 20 | 'menu.usage': '使用量', 21 | 'menu.404': '404' 22 | }; 23 | -------------------------------------------------------------------------------- /src/locales/zh-CN/shortcuts.ts: -------------------------------------------------------------------------------- 1 | // keyborad shortcuts 2 | export default { 3 | 'shortcuts.title': '快捷键', 4 | 'shortcuts.search.placeholder': '搜索快捷键', 5 | 'shortcuts.playground.newmessage': '新建消息', 6 | 'shortcuts.playground.clearmessage': '清空消息', 7 | 'shortcuts.playground.toggleparams': '收起/展开参数', 8 | 'shortcuts.models.newmodelHF': '部署 Hugging Face 模型', 9 | 'shortcuts.models.newmodelLM': '部署 Ollama 模型', 10 | 'shortcuts.models.search': '从 Hugging Face 搜索模型', 11 | 'shortcuts.resources.addworker': '添加节点', 12 | 'shortcuts.apikeys.new': '新建 API 密钥', 13 | 'shortcuts.users.new': '新建用户' 14 | }; 15 | -------------------------------------------------------------------------------- /src/locales/zh-CN/usage.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'usage.title': '使用量', 3 | 'usage.filter.user': '按用户查询', 4 | 'usage.filter.model': '按模型查询' 5 | }; 6 | -------------------------------------------------------------------------------- /src/locales/zh-CN/users.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'users.title': '用户', 3 | 'users.button.create': '新建用户', 4 | 'users.form.edit': '编辑用户', 5 | 'users.form.create': '新建用户', 6 | 'users.table.username': '用户名', 7 | 'users.table.role': '角色', 8 | 'users.form.fullname': '全名', 9 | 'users.table.user': '用户', 10 | 'users.form.admin': '管理员', 11 | 'users.form.user': '普通用户', 12 | 'users.form.newpassword': '新密码', 13 | 'users.form.currentpassword': '当前密码', 14 | 'users.form.updatepassword': '修改密码', 15 | 'users.form.rule.password': 16 | '包含大小写字母、数字和特殊字符(!@#$%^&*_+),6至12个字符,不允许有空格', 17 | 'users.password.uppcase': '至少包含一个大写字母', 18 | 'users.password.lowercase': '至少包含一个小写字母', 19 | 'users.password.number': '至少包含一个数字', 20 | 'users.password.special': '至少包含一个特殊字符', 21 | 'users.password.length': '长度在6至12个字符之间', 22 | 'users.password.modify.title': '修改密码', 23 | 'users.password.modify.description': '为了确保您的账户安全,请修改初始密码', 24 | 'users.password.confirm': '确认新密码', 25 | 'users.password.confirm.empty': '请确认新密码', 26 | 'users.password.confirm.error': '两次输入的密码不一致', 27 | 'users.login.title': '登录', 28 | 'users.version.islatest': 'GPUStack {version} 已是最新版本', 29 | 'users.version.update': 'GPUStack {version} 版本可供更新', 30 | 'users.settings.title': '用户设置' 31 | }; 32 | -------------------------------------------------------------------------------- /src/models/global.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useGlobalState = () => { 4 | const [globalState, setGlobalState] = useState({}); 5 | return { 6 | globalState, 7 | setGlobalState 8 | }; 9 | }; 10 | 11 | export default useGlobalState; 12 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import { Result } from 'antd'; 3 | import React from 'react'; 4 | 5 | const NoFoundPage: React.FC = () => { 6 | const intl = useIntl(); 7 | return ( 8 | 13 | ); 14 | }; 15 | 16 | export default NoFoundPage; 17 | -------------------------------------------------------------------------------- /src/pages/access/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageContainer } from '@ant-design/pro-components'; 2 | import { Access, useAccess } from '@umijs/max'; 3 | import { Button } from 'antd'; 4 | 5 | const AccessPage: React.FC = () => { 6 | const access = useAccess(); 7 | return ( 8 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default AccessPage; 25 | -------------------------------------------------------------------------------- /src/pages/api-keys/apis/index.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@umijs/max'; 2 | import { FormData, ListItem } from '../config/types'; 3 | 4 | export const APIS_KEYS_API = '/api-keys'; 5 | 6 | export async function queryApisKeysList(params: Global.SearchParams) { 7 | return request>(`${APIS_KEYS_API}`, { 8 | method: 'GET', 9 | params 10 | }); 11 | } 12 | 13 | export async function createApisKey(params: { data: FormData }) { 14 | return request(`${APIS_KEYS_API}`, { 15 | method: 'POST', 16 | data: params.data 17 | }); 18 | } 19 | 20 | export async function deleteApisKey(id: number) { 21 | return request(`${APIS_KEYS_API}/${id}`, { 22 | method: 'DELETE' 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/api-keys/config/index.ts: -------------------------------------------------------------------------------- 1 | export const expirationOptions = [ 2 | { 3 | label: 'apikeys.form.expiration.7days', 4 | type: 'day', 5 | value: 7, 6 | locale: true 7 | }, 8 | { 9 | label: 'apikeys.form.expiration.1month', 10 | type: 'month', 11 | value: 1, 12 | locale: true 13 | }, 14 | { 15 | label: 'apikeys.form.expiration.6months', 16 | type: 'month', 17 | value: 6, 18 | locale: true 19 | }, 20 | { 21 | label: 'apikeys.form.expiration.never', 22 | type: 'never', 23 | value: -1, 24 | locale: true 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /src/pages/api-keys/config/types.ts: -------------------------------------------------------------------------------- 1 | export interface ListItem { 2 | name: string; 3 | description: string; 4 | id: number; 5 | value: string; 6 | created_at: string; 7 | updated_at: string; 8 | expires_at: string; 9 | } 10 | 11 | export interface FormData { 12 | name: string; 13 | description: string; 14 | expires_in: number | null; 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/dashboard/apis/index.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@umijs/max'; 2 | 3 | export const DASHBOARD_API = '/dashboard'; 4 | 5 | export async function queryDashboardData() { 6 | return request(DASHBOARD_API); 7 | } 8 | 9 | export async function queryDashboardUsageData() { 10 | // return request(`${DASHBOARD_API}/usage`); 11 | return {}; 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/dahboard-inner.tsx: -------------------------------------------------------------------------------- 1 | import { memo, useCallback, useEffect, useState } from 'react'; 2 | import { queryDashboardData } from '../apis'; 3 | import DashboardContext from '../config/dashboard-context'; 4 | import { DashboardProps } from '../config/types'; 5 | import ActiveTable from './active-table'; 6 | import Overview from './over-view'; 7 | import SystemLoad from './system-load'; 8 | import Usage from './usage'; 9 | 10 | const Dashboard: React.FC<{ setLoading: (loading: boolean) => void }> = ({ 11 | setLoading 12 | }) => { 13 | const [data, setData] = useState({} as DashboardProps); 14 | 15 | const getDashboardData = useCallback(async () => { 16 | try { 17 | setLoading(true); 18 | const res = await queryDashboardData(); 19 | setData(res); 20 | setLoading(false); 21 | } catch (error) { 22 | setLoading(false); 23 | setData({} as DashboardProps); 24 | } 25 | }, []); 26 | useEffect(() => { 27 | getDashboardData(); 28 | }, []); 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default memo(Dashboard); 40 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/over-view.less: -------------------------------------------------------------------------------- 1 | :local(.card-body) { 2 | box-shadow: none !important; 3 | 4 | :global(.ant-card-body) { 5 | height: 110px; 6 | display: flex; 7 | justify-content: space-around; 8 | border-radius: var(--ant-border-radius-lg); 9 | border: 1px solid var(--ant-color-border); 10 | } 11 | } 12 | 13 | :local(.row) { 14 | :global(.ant-col-5) { 15 | flex: 0 0 20%; 16 | max-width: 20%; 17 | } 18 | } 19 | 20 | .content { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: space-between; 24 | align-items: center; 25 | font-size: 14px; 26 | 27 | .label { 28 | font-weight: var(--font-weight-medium); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/usage-inner/top-user.tsx: -------------------------------------------------------------------------------- 1 | import CardWrapper from '@/components/card-wrapper'; 2 | import HBar from '@/components/echarts/h-bar'; 3 | import { useIntl } from '@umijs/max'; 4 | import React from 'react'; 5 | 6 | interface TopUserProps { 7 | userData: { name: string; value: number }[]; 8 | topUserList: string[]; 9 | } 10 | const TopUser: React.FC = (props) => { 11 | const { userData, topUserList } = props; 12 | const intl = useIntl(); 13 | 14 | return ( 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default React.memo(TopUser); 22 | -------------------------------------------------------------------------------- /src/pages/dashboard/components/usage.tsx: -------------------------------------------------------------------------------- 1 | import breakpoints from '@/config/breakpoints'; 2 | import useWindowResize from '@/hooks/use-window-resize'; 3 | import { useEffect, useState } from 'react'; 4 | import UserInner from './usage-inner'; 5 | 6 | const Usage = () => { 7 | const { 8 | size: { width } 9 | } = useWindowResize(); 10 | const [paddingRight, setPaddingRight] = useState('20px'); 11 | 12 | useEffect(() => { 13 | if (width < breakpoints.xl) { 14 | setPaddingRight('0'); 15 | } else { 16 | setPaddingRight('20px'); 17 | } 18 | }, [width]); 19 | 20 | return ; 21 | }; 22 | 23 | export default Usage; 24 | -------------------------------------------------------------------------------- /src/pages/dashboard/config/dashboard-context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { DashboardProps } from './types'; 3 | 4 | export const DashboardContext = createContext< 5 | DashboardProps & { fetchData: () => Promise } 6 | >({} as DashboardProps & { fetchData: () => Promise }); 7 | 8 | export default DashboardContext; 9 | -------------------------------------------------------------------------------- /src/pages/dashboard/config/types.ts: -------------------------------------------------------------------------------- 1 | export interface DashboardProps { 2 | resource_counts: { 3 | worker_count: number; 4 | gpu_count: number; 5 | model_count: number; 6 | model_instance_count: number; 7 | }; 8 | system_load: { 9 | current: { 10 | cpu: number; 11 | ram: number; 12 | gpu: number; 13 | vram: number; 14 | }; 15 | history: { 16 | cpu: { 17 | timestamp: number; 18 | value: number; 19 | }[]; 20 | ram: { 21 | timestamp: number; 22 | value: number; 23 | }[]; 24 | gpu: { 25 | timestamp: number; 26 | value: number; 27 | }[]; 28 | vram: { 29 | timestamp: number; 30 | value: number; 31 | }[]; 32 | }; 33 | }; 34 | model_usage: { 35 | api_request_history: any[]; 36 | completion_token_history: any[]; 37 | prompt_token_history: any[]; 38 | top_users: { 39 | user_id: number; 40 | username: string; 41 | prompt_token_count: number; 42 | completion_token_count: number; 43 | }[]; 44 | }; 45 | active_models: any[]; 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageContainer } from '@ant-design/pro-components'; 2 | import { Spin } from 'antd'; 3 | import { useState } from 'react'; 4 | import DashboardInner from './components/dahboard-inner'; 5 | 6 | const Dashboard: React.FC = () => { 7 | const [loading, setLoading] = useState(false); 8 | 9 | return ( 10 | <> 11 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default Dashboard; 29 | -------------------------------------------------------------------------------- /src/pages/dashboard/styles/index.less: -------------------------------------------------------------------------------- 1 | .value-box { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | 6 | .value-healthy { 7 | color: var(--ant-green-6); 8 | } 9 | 10 | .value-warning { 11 | color: var(--ant-gold-6); 12 | } 13 | 14 | .value-error { 15 | color: var(--ant-red-6); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/llmodels/apis/evaluateWorker.ts: -------------------------------------------------------------------------------- 1 | const MODEL_EVALUATIONS = '/model-evaluations'; 2 | 3 | let controller = new AbortController(); 4 | let signal = controller.signal; 5 | 6 | self.onmessage = async (event) => { 7 | const { list, modelSource, modelSourceMap } = event.data; 8 | 9 | const repoList = list.map((item: any) => ({ 10 | source: modelSource, 11 | ...(modelSource === modelSourceMap.huggingface_value 12 | ? { huggingface_repo_id: item.name } 13 | : { model_scope_model_id: item.name }) 14 | })); 15 | 16 | try { 17 | controller?.abort(); 18 | controller = new AbortController(); 19 | signal = controller.signal; 20 | const response = await fetch(`v1/${MODEL_EVALUATIONS}`, { 21 | method: 'POST', 22 | headers: { 23 | 'Content-Type': 'application/json' 24 | }, 25 | signal, 26 | body: JSON.stringify({ model_specs: repoList }) 27 | }); 28 | 29 | if (!response.ok) { 30 | throw new Error(`HTTP error! Status: ${response.status}`); 31 | } 32 | 33 | const evaluations = await response.json(); 34 | const { results } = evaluations; 35 | const resultList = list.map((item: any, index: number) => { 36 | return { 37 | ...item, 38 | evaluateResult: results[index] || null 39 | }; 40 | }); 41 | 42 | self.postMessage({ success: true, resultList }); 43 | } catch (error) { 44 | self.postMessage({ success: false, resultList: list }); 45 | } 46 | }; 47 | 48 | self.onmessage = (event) => { 49 | if (event.data === 'abort') { 50 | controller.abort(); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/catalog-skelton.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Row, Skeleton } from 'antd'; 2 | import React from 'react'; 3 | 4 | interface CatalogSkeltonProps { 5 | span: number; 6 | } 7 | 8 | const CatalogSkelton: React.FC = (props) => { 9 | return ( 10 | 11 | {Array(6) 12 | .fill(1) 13 | .map((_, index) => { 14 | return ( 15 | 16 | 27 | 28 | ); 29 | })} 30 | 31 | ); 32 | }; 33 | 34 | export default React.memo(CatalogSkelton); 35 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/column-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import useOverlayScroller from '@/hooks/use-overlay-scroller'; 2 | import React from 'react'; 3 | import '../style/column-wrapper.less'; 4 | 5 | const ColumnWrapper: React.FC = ({ 6 | children, 7 | footer, 8 | maxHeight, 9 | paddingBottom = 50 10 | }) => { 11 | const scroller = React.useRef(null); 12 | const { initialize } = useOverlayScroller({ 13 | options: { 14 | scrollbars: { 15 | autoHide: 'move' 16 | } 17 | } 18 | }); 19 | 20 | React.useEffect(() => { 21 | if (scroller.current) { 22 | initialize(scroller.current); 23 | } 24 | }, []); 25 | 26 | return ( 27 | <> 28 | {footer ? ( 29 |
30 |
31 |
36 | {children} 37 |
38 |
39 | {
{footer}
} 40 |
41 | ) : ( 42 |
43 |
{children}
44 |
45 | )} 46 | 47 | ); 48 | }; 49 | 50 | export default ColumnWrapper; 51 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/deploy-dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from 'antd'; 2 | import React from 'react'; 3 | import '../style/deploy-dropdown.less'; 4 | 5 | interface DeployDropdownProps { 6 | items: { label: string; value: string; key: string; icon: React.ReactNode }[]; 7 | onSelect: (item: { 8 | label: string; 9 | value: string; 10 | key: string; 11 | icon: React.ReactNode; 12 | }) => void; 13 | } 14 | 15 | const DeployDropdown: React.FC = ({ items, onSelect }) => { 16 | return ( 17 |
18 | 19 | {items.map((item) => ( 20 | 21 |
onSelect(item)} className="item"> 22 | {item.icon} 23 | {item.label} 24 |
25 | 26 | ))} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default DeployDropdown; 33 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/file-parts.tsx: -------------------------------------------------------------------------------- 1 | import { convertFileSize } from '@/utils'; 2 | import React from 'react'; 3 | import 'simplebar-react/dist/simplebar.min.css'; 4 | import styled from 'styled-components'; 5 | 6 | const Wrapper = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | .file-part-item { 10 | width: 180px; 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-between; 14 | } 15 | `; 16 | 17 | const FileParts: React.FC<{ 18 | showSize?: boolean; 19 | fileList: any[]; 20 | }> = ({ fileList, showSize = true }) => { 21 | return ( 22 | 23 | {fileList.map((file, index) => { 24 | return ( 25 |
26 | 27 | Part {file.part} of {file.total} 28 | 29 | {{convertFileSize(file.size)}} 30 |
31 | ); 32 | })} 33 |
34 | ); 35 | }; 36 | 37 | export default FileParts; 38 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/separator.tsx: -------------------------------------------------------------------------------- 1 | import { Divider } from 'antd'; 2 | import React from 'react'; 3 | import '../style/separator.less'; 4 | 5 | const Separator: React.FC = () => { 6 | return ( 7 |
8 | 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default Separator; 18 | -------------------------------------------------------------------------------- /src/pages/llmodels/components/title-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../style/title-wrapper.less'; 3 | 4 | const TitleWrapper: React.FC = ({ children }) => { 5 | return ( 6 |

7 | {children} 8 |

9 | ); 10 | }; 11 | 12 | export default TitleWrapper; 13 | -------------------------------------------------------------------------------- /src/pages/llmodels/config/form-context.ts: -------------------------------------------------------------------------------- 1 | import { PageActionType } from '@/config/types'; 2 | import React from 'react'; 3 | 4 | interface FormContextProps { 5 | isGGUF?: boolean; 6 | byBuiltIn?: boolean; 7 | pageAction: PageActionType; 8 | sizeOptions?: Global.BaseOption[]; 9 | quantizationOptions?: Global.BaseOption[]; 10 | modelFileOptions?: any[]; 11 | onSizeChange?: (val: number) => void; 12 | onQuantizationChange?: (val: string) => void; 13 | onValuesChange?: (changedValues: any, allValues: any) => void; 14 | } 15 | 16 | interface FormInnerContextProps { 17 | onBackendChange?: (backend: string) => void; 18 | gpuOptions?: any[]; 19 | } 20 | 21 | export const FormContext = React.createContext( 22 | {} as FormContextProps 23 | ); 24 | 25 | export const FormInnerContext = React.createContext( 26 | {} as FormInnerContextProps 27 | ); 28 | 29 | export const useFormContext = () => { 30 | const context = React.useContext(FormContext); 31 | if (!context) { 32 | throw new Error('useFormContext must be used within a FormProvider'); 33 | } 34 | return context; 35 | }; 36 | 37 | export const useFormInnerContext = () => { 38 | const context = React.useContext(FormInnerContext); 39 | if (!context) { 40 | throw new Error( 41 | 'useFormInnerContext must be used within a FormInnerProvider' 42 | ); 43 | } 44 | return context; 45 | }; 46 | -------------------------------------------------------------------------------- /src/pages/llmodels/hooks/use-selector-change.ts: -------------------------------------------------------------------------------- 1 | export default function useSelectorChange() {} 2 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/column-wrapper.less: -------------------------------------------------------------------------------- 1 | .column-wrapper { 2 | flex: 1; 3 | position: relative; 4 | height: 100%; 5 | overflow-y: auto; 6 | } 7 | 8 | .simplebar-scrollbar::before { 9 | width: var(--scrollbar-size); 10 | background: var(--scrollbar-handle-bg); 11 | } 12 | 13 | .column-wrapper-footer { 14 | flex: 1; 15 | display: flex; 16 | position: relative; 17 | 18 | .column-wrapper { 19 | border-left: none; 20 | } 21 | 22 | .footer { 23 | position: absolute; 24 | bottom: 0; 25 | left: 0; 26 | right: 0; 27 | z-index: 100; 28 | padding-block: 0; 29 | background-color: var(--ant-color-bg-elevated); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/data-form.less: -------------------------------------------------------------------------------- 1 | .advanced-collapse { 2 | :global { 3 | .ant-collapse-header { 4 | display: flex; 5 | align-items: center; 6 | margin-bottom: 10px !important; 7 | padding-inline: 5px !important; 8 | padding-block: 5px !important; 9 | border-radius: var(--border-radius-base) !important; 10 | font-size: var(--font-size-large) !important; 11 | font-weight: var(--font-weight-bold) !important; 12 | 13 | &:hover { 14 | background-color: var(--ant-color-fill-tertiary) !important; 15 | } 16 | } 17 | 18 | .ant-collapse-content-box { 19 | padding-inline: 0 !important; 20 | padding-block: 0 !important; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/deploy-dropdown.less: -------------------------------------------------------------------------------- 1 | .deploy-dropdown { 2 | display: flex; 3 | justify-content: flex-start; 4 | padding: var(--ant-padding-xxs); 5 | background-color: var(--ant-color-bg-elevated); 6 | background-clip: padding-box; 7 | border-radius: var(--ant-border-radius-lg); 8 | outline: none; 9 | box-shadow: var(--ant-box-shadow-secondary); 10 | 11 | .item { 12 | cursor: pointer; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: center; 17 | padding: var(--ant-dropdown-padding-block) 18 | var(--ant-control-padding-horizontal); 19 | 20 | &:hover { 21 | background-color: var(--ant-control-item-bg-hover); 22 | } 23 | 24 | .icon { 25 | font-size: 20px; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/gpu-card.less: -------------------------------------------------------------------------------- 1 | .gpu-card { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: flex-start; 5 | justify-content: center; 6 | width: 100%; 7 | border-bottom: 1px solid var(--ant-color-split); 8 | padding-bottom: 10px; 9 | 10 | .header { 11 | margin-bottom: 10px; 12 | } 13 | 14 | .info { 15 | display: flex; 16 | flex-direction: column; 17 | align-items: flex-start; 18 | gap: 10px; 19 | color: var(--ant-color-text-tertiary); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/hf-model-file.less: -------------------------------------------------------------------------------- 1 | @padding: 24px; 2 | 3 | .hf-model-file { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: space-between; 7 | padding: 12px 14px; 8 | border: 1px solid var(--ant-color-border); 9 | border-radius: var(--border-radius-base); 10 | cursor: pointer; 11 | background-color: var(--ant-color-bg-container); 12 | 13 | &:hover { 14 | background-color: var(--ant-color-fill-tertiary); 15 | } 16 | 17 | &.active { 18 | background-color: var(--ant-color-fill-tertiary); 19 | } 20 | 21 | .tags { 22 | display: flex; 23 | gap: 8px; 24 | align-items: center; 25 | flex-wrap: wrap; 26 | } 27 | 28 | .title { 29 | margin-bottom: 14px; 30 | } 31 | 32 | .tag-item { 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | padding: 2px 4px; 37 | border-radius: 4px; 38 | font-size: 12px; 39 | height: 22px; 40 | } 41 | 42 | .btn { 43 | display: flex; 44 | justify-content: flex-end; 45 | } 46 | } 47 | 48 | .files-wrap { 49 | position: relative; 50 | 51 | .spin-wrapper { 52 | position: absolute; 53 | left: 0; 54 | right: 0; 55 | height: 100%; 56 | z-index: 100; 57 | bottom: 0; 58 | top: 0; 59 | padding-top: 150px; 60 | text-align: center; 61 | background-color: var(--color-fill-spin-bg); 62 | } 63 | } 64 | 65 | .wrapper { 66 | padding: 16px @padding; 67 | } 68 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/hf-model-item.less: -------------------------------------------------------------------------------- 1 | .hf-model-item { 2 | min-height: 82px; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | border: 1px solid var(--ant-color-border); 7 | border-radius: var(--border-radius-base); 8 | padding: 12px 14px; 9 | cursor: pointer; 10 | background-color: var(--ant-color-bg-container); 11 | 12 | &:hover { 13 | background-color: var(--ant-color-fill-tertiary); 14 | } 15 | 16 | &.active { 17 | background-color: var(--ant-color-fill-tertiary); 18 | } 19 | 20 | .title { 21 | font-size: var(--font-size-base); 22 | word-break: break-all; 23 | } 24 | 25 | .info { 26 | display: flex; 27 | align-items: center; 28 | color: var(--ant-color-text-tertiary); 29 | font-size: var(--font-size-small); 30 | justify-content: space-between; 31 | height: 22px; 32 | 33 | .info-item { 34 | display: flex; 35 | align-items: center; 36 | gap: 16px; 37 | } 38 | 39 | .tag-item { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | padding: 2px 4px; 44 | border-radius: 4px; 45 | font-size: 12px; 46 | height: 22px; 47 | opacity: 0.9; 48 | // border: 1px solid var(--ant-color-border); 49 | // color: var(--ant-color-text-secondary); 50 | } 51 | } 52 | 53 | .tags { 54 | display: flex; 55 | align-items: center; 56 | flex-wrap: wrap; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/instance-item.less: -------------------------------------------------------------------------------- 1 | .instance-name { 2 | min-height: 54px; 3 | 4 | &:hover { 5 | .server-info { 6 | .text { 7 | display: block; 8 | } 9 | 10 | color: var(--ant-blue-5); 11 | } 12 | } 13 | 14 | .server-info { 15 | display: flex; 16 | justify-content: flex-start; 17 | align-items: center; 18 | color: var(--ant-blue-5); 19 | cursor: pointer; 20 | white-space: nowrap; 21 | 22 | .text { 23 | display: none; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/model-card.less: -------------------------------------------------------------------------------- 1 | .model-card-wrap { 2 | display: flex; 3 | flex-direction: column; 4 | border-radius: var(--border-radius-base); 5 | 6 | .title { 7 | margin-bottom: 5px; 8 | display: flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | .tag-item { 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | padding: 2px 4px; 18 | border-radius: 4px; 19 | font-size: 12px; 20 | height: 20px; 21 | } 22 | 23 | .btn { 24 | display: flex; 25 | justify-content: flex-end; 26 | } 27 | 28 | .mkd-title { 29 | cursor: pointer; 30 | background-color: var(--ant-color-fill-tertiary); 31 | display: flex; 32 | justify-content: space-between; 33 | align-items: center; 34 | padding: 10px; 35 | height: 36px; 36 | } 37 | } 38 | 39 | .card-wrapper { 40 | padding: 16px 24px; 41 | padding-top: 0; 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/search-result.less: -------------------------------------------------------------------------------- 1 | @padding: 24px; 2 | 3 | .search-result-wrap { 4 | padding: 16px @padding; 5 | border-radius: 0 0 var(--border-radius-base) var(--border-radius-base); 6 | } 7 | 8 | .gguf-tips { 9 | display: flex; 10 | align-items: center; 11 | padding: 7px 0; 12 | color: var(--ant-color-text-tertiary); 13 | 14 | .warning { 15 | color: var(--ant-color-warning); 16 | } 17 | } 18 | 19 | .search-bar { 20 | left: 0; 21 | right: 0; 22 | padding-inline: @padding; 23 | background: var(--ant-color-bg-elevated); 24 | padding-bottom: 10px; 25 | 26 | .filter { 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | margin-top: 10px; 31 | color: var(--ant-color-text-tertiary); 32 | 33 | :global { 34 | .value { 35 | color: var(--ant-color-text); 36 | margin-right: 6px; 37 | font-weight: var(--font-weight-bold); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/separator.less: -------------------------------------------------------------------------------- 1 | .separator { 2 | position: relative; 3 | 4 | .shape { 5 | position: absolute; 6 | border-radius: 0 2px 0 0; 7 | top: 10px; 8 | left: -13px; 9 | width: 24px; 10 | height: 24px; 11 | border: 2px solid var(--ant-color-split); 12 | transform: rotate(45deg); 13 | border-left: none; 14 | border-bottom: none; 15 | background-color: var(--ant-color-bg-elevated); 16 | z-index: 100; 17 | clip-path: polygon(0 -1px, 24px 0, 24px 23px); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/llmodels/style/title-wrapper.less: -------------------------------------------------------------------------------- 1 | @padding: 16px 24px; 2 | 3 | .h3 { 4 | position: sticky; 5 | top: 0; 6 | z-index: 100; 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | font-size: 14px; 11 | padding: @padding; 12 | padding-top: 10px; 13 | margin-bottom: 0; 14 | font-weight: var(--font-weight-bold); 15 | background-color: var(--ant-color-bg-elevated); 16 | 17 | .title { 18 | font-weight: var(--font-weight-bold); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/login/apis/index.ts: -------------------------------------------------------------------------------- 1 | import { userAtom } from '@/atoms/user'; 2 | import { clearAtomStorage } from '@/atoms/utils'; 3 | import { request } from '@umijs/max'; 4 | import qs from 'query-string'; 5 | 6 | export const AUTH_API = '/auth'; 7 | 8 | export const login = async ( 9 | params: { username: string; password: string }, 10 | options?: any 11 | ) => { 12 | return request(`${AUTH_API}/login`, { 13 | method: 'POST', 14 | data: qs.stringify(params), 15 | headers: { 16 | 'Content-Type': 'application/x-www-form-urlencoded' 17 | } 18 | }); 19 | }; 20 | 21 | export const logout = async (userInfo: any) => { 22 | await request(`${AUTH_API}/logout`, { 23 | method: 'POST' 24 | }); 25 | clearAtomStorage(userAtom); 26 | return; 27 | }; 28 | 29 | export const accessToken = async () => { 30 | return request(`${AUTH_API}/token`, { 31 | method: 'POST' 32 | }); 33 | }; 34 | 35 | export const updatePassword = async (params: any) => { 36 | return request(`${AUTH_API}/update-password`, { 37 | method: 'POST', 38 | data: params 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/pages/login/components/styles.less: -------------------------------------------------------------------------------- 1 | :local(.login-form-wrapper) { 2 | margin: 0 auto; 3 | border-radius: var(--border-radius-modal); 4 | width: max-content; 5 | height: max-content; 6 | padding: 32px; 7 | background-color: var(--ant-modal-content-bg); 8 | 9 | :global(.field-wrapper) { 10 | background-color: transparent !important; 11 | } 12 | 13 | :global(.ant-input-outlined.ant-input-status-error:not(.ant-input-disabled)) { 14 | background-color: transparent !important; 15 | } 16 | } 17 | 18 | :local(.box) { 19 | padding-top: 10%; 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: space-between; 23 | min-height: 100vh; 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/login/utils.ts: -------------------------------------------------------------------------------- 1 | import { isOnline } from '@/utils'; 2 | import { 3 | IS_FIRST_LOGIN, 4 | readState, 5 | writeState 6 | } from '@/utils/localstore/index'; 7 | import { history } from '@umijs/max'; 8 | 9 | export const checkDefaultPage = async (userInfo: any, replace: boolean) => { 10 | const isFirstLogin = await readState(IS_FIRST_LOGIN); 11 | 12 | if (isFirstLogin === null && isOnline()) { 13 | writeState(IS_FIRST_LOGIN, true); 14 | const pathname = 15 | userInfo && userInfo?.is_admin ? '/models/list' : '/playground'; 16 | history.push(pathname); 17 | return; 18 | } 19 | await writeState(IS_FIRST_LOGIN, false); 20 | 21 | const pathname = userInfo?.is_admin ? '/dashboard' : '/playground'; 22 | 23 | history.push(pathname, { replace: replace }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/pages/playground/components/audio-content.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpustack/gpustack-ui/281d8bddc198bf1f9881e2ff2cccd2c6ef99c0a6/src/pages/playground/components/audio-content.tsx -------------------------------------------------------------------------------- /src/pages/playground/components/empty-models.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl, useNavigate } from '@umijs/max'; 2 | import { Empty, Typography } from 'antd'; 3 | import React from 'react'; 4 | 5 | const EmptyModels: React.FC<{ 6 | style?: React.CSSProperties; 7 | }> = ({ style }) => { 8 | const intl = useIntl(); 9 | const navigate = useNavigate(); 10 | 11 | const handleDeployModel = () => { 12 | navigate('/models'); 13 | }; 14 | return ( 15 |
21 | 25 | 26 | {intl.formatMessage({ id: 'playground.model.noavailable' })} 27 | 28 |
29 | } 30 | > 31 | 32 | ); 33 | }; 34 | 35 | export default EmptyModels; 36 | -------------------------------------------------------------------------------- /src/pages/playground/components/multiple-chat/active-models.tsx: -------------------------------------------------------------------------------- 1 | import { Col, Row } from 'antd'; 2 | import React from 'react'; 3 | import { ModelSelectionItem } from '../../config/types'; 4 | import ModelItem from './model-item'; 5 | 6 | interface ActiveModelsProps { 7 | spans: { 8 | span: number; 9 | count: number; 10 | }; 11 | modelSelections: ModelSelectionItem[]; 12 | setModelRefs: (modelname: symbol, value: React.MutableRefObject) => void; 13 | } 14 | 15 | const ActiveModels: React.FC = (props) => { 16 | const { spans, modelSelections, setModelRefs } = props; 17 | return ( 18 | 19 | {modelSelections.map((model, index) => ( 20 | 27 | ) => 30 | setModelRefs(model.instanceId, el) 31 | } 32 | instanceId={model.instanceId} 33 | modelList={modelSelections} 34 | model={model.value} 35 | /> 36 | 37 | ))} 38 | 39 | ); 40 | }; 41 | 42 | export default React.memo(ActiveModels); 43 | -------------------------------------------------------------------------------- /src/pages/playground/components/multiple-chat/think-content.tsx: -------------------------------------------------------------------------------- 1 | import FullMarkdown from '@/components/markdown-viewer/full-markdown'; 2 | import React from 'react'; 3 | import '../../style/think-content.less'; 4 | 5 | interface ThinkContentProps { 6 | content: string; 7 | isThinking?: boolean; 8 | collapsed?: boolean; 9 | } 10 | 11 | const ThinkContent: React.FC = ({ content, collapsed }) => { 12 | return ( 13 | <> 14 | {content ? ( 15 |
16 | {!collapsed && ( 17 |
18 | 19 |
20 | )} 21 |
22 | ) : null} 23 | 24 | ); 25 | }; 26 | 27 | export default ThinkContent; 28 | -------------------------------------------------------------------------------- /src/pages/playground/components/token-usage.tsx: -------------------------------------------------------------------------------- 1 | import { useIntl } from '@umijs/max'; 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | const TokenUsageWrapper = styled.div` 6 | font-size: var(--font-size-small); 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | padding: 2px 5px; 11 | .text { 12 | color: var(--ant-orange); 13 | } 14 | `; 15 | 16 | const TokenUsage: React.FC<{ 17 | tokenResult?: any; 18 | [key: string]: any; 19 | }> = ({ tokenResult, ...rest }) => { 20 | const intl = useIntl(); 21 | if (!tokenResult) { 22 | return null; 23 | } 24 | return ( 25 | 26 | {tokenResult?.total_tokens && ( 27 | 28 | {intl.formatMessage({ id: 'playground.tokenusage' })}:{' '} 29 | {tokenResult?.total_tokens} 30 | 31 | )} 32 | 33 | ); 34 | }; 35 | 36 | export default TokenUsage; 37 | -------------------------------------------------------------------------------- /src/pages/playground/config/compare-context.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MessageItemAction } from './types'; 3 | 4 | interface CompareContextProps { 5 | spans: { 6 | span: number; 7 | count: number; 8 | }; 9 | actions?: MessageItemAction[]; 10 | systemMessage?: string; 11 | globalParams: Record; 12 | loadingStatus: Record; 13 | modelFullList: (Global.BaseOption & { type?: string })[]; 14 | handleApplySystemChangeToAll: (val: string) => void; 15 | handleDeleteModel: (instanceId: symbol) => void; 16 | setSystemMessage?: (message: string) => void; 17 | setGlobalParams: (value: Record) => void; 18 | setLoadingStatus: (instanceId: symbol, status: boolean) => void; 19 | } 20 | const CompareContext = React.createContext( 21 | {} as CompareContextProps 22 | ); 23 | 24 | export default CompareContext; 25 | -------------------------------------------------------------------------------- /src/pages/playground/hooks/config.ts: -------------------------------------------------------------------------------- 1 | export const LLM_METAKEYS: Record = { 2 | seed: 'seed', 3 | stop: 'stop', 4 | temperature: 'temperature', 5 | top_p: 'top_p', 6 | n_ctx: 'n_ctx', 7 | n_slot: 'n_slot', 8 | max_model_len: 'max_model_len', 9 | frequency_penalty: 'frequency_penalty', 10 | presence_penalty: 'presence_penalty', 11 | max_total_tokens: 'max_total_tokens' 12 | }; 13 | 14 | export const precisionTwoKeys = [ 15 | 'temperature', 16 | 'top_p', 17 | 'frequency_penalty', 18 | 'presence_penalty' 19 | ]; 20 | 21 | export const IMG_METAKEYS = [ 22 | 'sample_method', 23 | 'sampling_steps', 24 | 'schedule_method', 25 | 'cfg_scale', 26 | 'guidance', 27 | 'negative_prompt', 28 | 'seed', 29 | 'strength' 30 | ]; 31 | 32 | export const llmInitialValues = { 33 | seed: null, 34 | stop: null, 35 | temperature: 1, 36 | top_p: 1, 37 | max_tokens: null, 38 | frequency_penalty: null, 39 | presence_penalty: null 40 | }; 41 | 42 | export const advancedFieldsDefaultValus = { 43 | seed: null, 44 | sample_method: 'euler_a', 45 | cfg_scale: 4.5, 46 | guidance: 3.5, 47 | sampling_steps: 10, 48 | negative_prompt: null, 49 | strength: null, 50 | schedule_method: 'discrete', 51 | preview: 'preview_faster' 52 | }; 53 | 54 | export const openaiCompatibleFieldsDefaultValus = { 55 | // quality: 'standard', 56 | style: null 57 | }; 58 | 59 | export const imgInitialValues = { 60 | n: 1, 61 | size: '512x512', 62 | width: 512, 63 | height: 512 64 | }; 65 | -------------------------------------------------------------------------------- /src/pages/playground/hooks/use-collapse-layout.tsx: -------------------------------------------------------------------------------- 1 | import breakpoints from '@/config/breakpoints'; 2 | import useWindowResize from '@/hooks/use-window-resize'; 3 | import { useEffect } from 'react'; 4 | 5 | export default function useCollapseLayout(options: { 6 | handler: () => void; 7 | triggeredRef: { 8 | collapse: boolean; 9 | }; 10 | }) { 11 | const { size } = useWindowResize(); 12 | 13 | useEffect(() => { 14 | if (size.width < breakpoints.lg) { 15 | if (!options.triggeredRef?.collapse) { 16 | options.handler(); 17 | } 18 | } 19 | }, [size.width]); 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/playground/hooks/use-embedding-worker.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export default function useEmbeddingWorker() { 4 | const workerRef = useRef(null); 5 | 6 | const createWorker = () => { 7 | if (workerRef.current) { 8 | workerRef.current.terminate(); 9 | } 10 | workerRef.current = new Worker( 11 | new URL('../config/embedding-worker.worker.ts', import.meta.url), 12 | { 13 | type: 'module' 14 | } 15 | ); 16 | }; 17 | 18 | const postMessage = (params: { 19 | embeddings: any[]; 20 | textList: { text: string; name: string; uid: number | string }[]; 21 | fileList: { text: string; name: string; uid: number | string }[]; 22 | }) => { 23 | if (workerRef.current) { 24 | workerRef.current.postMessage(params); 25 | } 26 | }; 27 | 28 | const terminateWorker = () => { 29 | if (workerRef.current) { 30 | workerRef.current.terminate(); 31 | workerRef.current = null; 32 | } 33 | }; 34 | 35 | useEffect(() => { 36 | return () => { 37 | terminateWorker(); 38 | }; 39 | }, []); 40 | 41 | return { workerRef, createWorker, postMessage, terminateWorker }; 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/playground/style/audio-input.less: -------------------------------------------------------------------------------- 1 | .audio-input { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | background-color: var(--ant-color-fill-quaternary); 7 | border-radius: 4px; 8 | padding: 16px; 9 | 10 | .ant-upload { 11 | border: none; 12 | background-color: transparent !important; 13 | } 14 | 15 | .btns { 16 | .anticon { 17 | font-size: 24px; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/playground/style/custom-label.less: -------------------------------------------------------------------------------- 1 | :local(.label) { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: flex-start; 5 | width: 100%; 6 | 7 | :global(.label-val) { 8 | position: absolute !important; 9 | top: -14px; 10 | right: -14px; 11 | width: 80px; 12 | border-radius: var(--border-radius-base); 13 | text-align: center; 14 | border: 1px solid var(--ant-color-border) !important; 15 | 16 | :global(.ant-input-number-input) { 17 | text-align: center !important; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/playground/style/file-list.less: -------------------------------------------------------------------------------- 1 | .file-list { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 8px; 5 | 6 | .file-item { 7 | display: flex; 8 | align-items: center; 9 | justify-content: space-between; 10 | border: 1px solid var(--ant-color-border); 11 | border-radius: var(--border-radius-base); 12 | padding: 8px 6px 8px 14px; 13 | cursor: pointer; 14 | height: 41px; 15 | transition: background-color 0.3s ease; 16 | 17 | .delete-btn { 18 | display: none; 19 | } 20 | 21 | &:hover { 22 | background-color: var(--ant-color-fill-tertiary); 23 | 24 | .delete-btn { 25 | display: block; 26 | } 27 | } 28 | 29 | &.ghost { 30 | background-color: transparent; 31 | } 32 | 33 | .title { 34 | display: flex; 35 | align-items: center; 36 | width: 300px; 37 | flex: 1; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/playground/style/input-list.less: -------------------------------------------------------------------------------- 1 | .input-list { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 12px; 5 | 6 | .input-item { 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | padding: 0; 12 | padding-right: 6px; 13 | overflow: hidden; 14 | cursor: pointer; 15 | transition: background-color 0.3s ease; 16 | border-radius: var(--border-radius-base); 17 | border: 1px solid var(--ant-color-border); 18 | 19 | .btn-group { 20 | display: none; 21 | } 22 | 23 | &:hover { 24 | //background-color: var(--ant-color-fill-tertiary); 25 | &::before { 26 | content: ''; 27 | display: flex; 28 | position: absolute; 29 | left: 0; 30 | bottom: 0; 31 | right: 0; 32 | top: 0; 33 | background-color: var(--ant-color-fill-tertiary); 34 | z-index: 5; 35 | pointer-events: none; 36 | } 37 | 38 | .btn-group { 39 | display: flex; 40 | gap: 8px; 41 | } 42 | 43 | .hover-hidden { 44 | display: none; 45 | } 46 | } 47 | 48 | &:focus-within { 49 | //background-color: transparent; 50 | 51 | &::before { 52 | background-color: transparent; 53 | } 54 | } 55 | 56 | .input-wrap { 57 | flex: 1; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/pages/playground/style/message-item.less: -------------------------------------------------------------------------------- 1 | .message-item { 2 | position: relative; 3 | display: flex; 4 | justify-content: flex-start; 5 | align-items: flex-start; 6 | margin-bottom: 12px; 7 | 8 | &:hover { 9 | .delete-btn { 10 | opacity: 1; 11 | transition: all 0.3s var(--seal-transition-func); 12 | } 13 | } 14 | 15 | .role-type { 16 | margin-right: 12px; 17 | background-color: var(--ant-button-text-hover-bg); 18 | 19 | .ant-btn { 20 | text-align: left; 21 | width: 100px; 22 | background-color: var(--ant-button-text-hover-bg); 23 | height: 46px; 24 | } 25 | } 26 | 27 | .message-content-input { 28 | flex: 1; 29 | cursor: pointer; 30 | 31 | &.has-img { 32 | border: 1px solid var(--ant-color-fill-secondary); 33 | border-radius: var(--border-radius-base); 34 | overflow: hidden; 35 | 36 | .ant-input { 37 | border-radius: 0 0 var(--border-radius-base) var(--border-radius-base); 38 | border-color: transparent; 39 | background-color: transparent; 40 | } 41 | 42 | &:focus-within { 43 | border-color: var(--ant-color-primary); 44 | } 45 | } 46 | } 47 | 48 | .delete-btn { 49 | opacity: 0; 50 | position: absolute; 51 | top: 2px; 52 | right: 2px; 53 | transition: all 0.3s var(--seal-transition-func); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pages/playground/style/model-item.less: -------------------------------------------------------------------------------- 1 | .model-item { 2 | display: flex; 3 | flex-direction: column; 4 | border: 1px solid var(--ant-color-border); 5 | border-radius: var(--border-radius-base); 6 | height: 100%; 7 | background-color: var(--color-white-1); 8 | 9 | .header { 10 | padding-inline: 2px 16px; 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-between; 14 | height: 46px; 15 | border-bottom: 1px solid var(--ant-color-border); 16 | width: 100%; 17 | 18 | .title { 19 | max-width: min(100%, 180px); 20 | min-width: min(120px, 32%); 21 | } 22 | } 23 | 24 | .action { 25 | display: flex; 26 | align-items: center; 27 | gap: 2px; 28 | } 29 | 30 | .content { 31 | flex: 1; 32 | padding: 16px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/playground/style/multiple-chat.less: -------------------------------------------------------------------------------- 1 | .multiple-chat { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | 6 | .chat-list { 7 | flex: 1; 8 | overflow-y: auto; 9 | padding-inline: var(--layout-content-inlinepadding); 10 | } 11 | 12 | .chat-list-inner { 13 | min-height: calc(100vh - 199px); 14 | max-height: max-content; 15 | height: 100%; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/playground/style/prompt-modal.less: -------------------------------------------------------------------------------- 1 | .prompt-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 16px; 5 | padding: var(--ant-modal-content-padding); 6 | 7 | .title { 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | 12 | .text { 13 | font-weight: var(--font-weight-bold); 14 | font-size: var(--font-size-base); 15 | } 16 | } 17 | 18 | .prompt-item { 19 | display: flex; 20 | flex-direction: column; 21 | border: 1px solid var(--ant-color-border); 22 | border-radius: var(--border-radius-base); 23 | min-height: 150px; 24 | padding: 10px 12px; 25 | gap: 8px; 26 | 27 | .data-item { 28 | display: flex; 29 | justify-content: flex-start; 30 | align-items: start; 31 | } 32 | 33 | .role { 34 | display: flex; 35 | width: 50px; 36 | font-weight: var(--font-weight-bold); 37 | margin-bottom: 8px; 38 | } 39 | 40 | .prompt { 41 | flex: 1; 42 | padding: 8px; 43 | border-radius: var(--border-radius-mini); 44 | background-color: var(--ant-color-fill-tertiary); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/pages/playground/style/reference-params.less: -------------------------------------------------------------------------------- 1 | .reference-params { 2 | display: flex; 3 | height: 40px; 4 | justify-content: center; 5 | align-items: center; 6 | color: var(--ant-orange); 7 | gap: 20px; 8 | 9 | &.scaleable { 10 | gap: 15px; 11 | overflow: hidden; 12 | } 13 | 14 | .usage { 15 | cursor: pointer; 16 | line-height: 16px; 17 | 18 | &.scaleable { 19 | transform: scale(0.9); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/playground/style/speech-to-text.less: -------------------------------------------------------------------------------- 1 | .speech-to-text { 2 | padding: 32px; 3 | height: 100%; 4 | display: flex; 5 | gap: 30px; 6 | flex-direction: column; 7 | 8 | .tips-text { 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | font-size: var(--font-size-large); 13 | height: 66px; 14 | padding: 0; 15 | gap: 10px; 16 | } 17 | 18 | .speech-box { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | gap: 120px; 23 | flex: 1; 24 | min-height: 160px; 25 | 26 | .ant-upload { 27 | border: none; 28 | background-color: transparent !important; 29 | } 30 | 31 | .ant-btn { 32 | width: 60px; 33 | height: 60px; 34 | } 35 | 36 | .btns { 37 | .anticon { 38 | font-size: 24px; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/playground/style/sys-message.less: -------------------------------------------------------------------------------- 1 | .sys-message { 2 | position: relative; 3 | 4 | &.focus { 5 | padding-top: 9px; 6 | } 7 | 8 | .apply-check { 9 | position: absolute; 10 | bottom: 10px; 11 | display: flex; 12 | right: 10px; 13 | background-color: var(--color-white-1); 14 | box-shadow: var(--ant-box-shadow-tertiary); 15 | align-items: center; 16 | padding: 1px 4px; 17 | cursor: pointer; 18 | } 19 | 20 | .sys-content-wrap { 21 | position: relative; 22 | display: flex; 23 | align-items: center; 24 | justify-content: space-between; 25 | background-color: var(--ant-color-fill-tertiary); 26 | padding-right: 20px; 27 | cursor: pointer; 28 | 29 | &:hover { 30 | .clear-btn { 31 | display: block; 32 | } 33 | } 34 | } 35 | 36 | .clear-btn { 37 | display: none; 38 | position: absolute; 39 | right: 6px; 40 | top: 6px; 41 | } 42 | 43 | .system-label { 44 | font-weight: var(--font-weight-bold); 45 | padding-left: 14px; 46 | } 47 | 48 | .sys-content { 49 | flex: 1; 50 | height: 38px; 51 | width: 300px; 52 | line-height: 18px; 53 | padding: 10px 14px; 54 | text-overflow: ellipsis; 55 | overflow: hidden; 56 | white-space: nowrap; 57 | color: var(--ant-color-text-secondary); 58 | 59 | .title { 60 | font-weight: var(--font-weight-bold); 61 | padding-right: 10px; 62 | color: var(--ant-color-text); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/pages/playground/style/system-message-wrap.less: -------------------------------------------------------------------------------- 1 | .system-message-wrap { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | height: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/playground/style/think-content.less: -------------------------------------------------------------------------------- 1 | .think-wrapper { 2 | color: var(--ant-color-text-tertiary); 3 | 4 | .btn-collapse { 5 | margin-bottom: 8px; 6 | border-radius: var(--border-radius-base); 7 | width: max-content; 8 | overflow: hidden; 9 | } 10 | 11 | .think-content { 12 | margin-bottom: 8px; 13 | padding: 12px; 14 | border-left: 2px solid var(--ant-color-fill-secondary); 15 | background-color: var(--ant-color-fill-tertiary); 16 | color: var(--ant-color-text-tertiary); 17 | border-radius: var(--border-radius-base); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/playground/style/thumb-img.less: -------------------------------------------------------------------------------- 1 | .thumb-img { 2 | position: relative; 3 | display: flex; 4 | max-width: 100%; 5 | max-height: 100%; 6 | justify-content: center; 7 | align-items: center; 8 | border-radius: var(--border-radius-base); 9 | overflow: hidden; 10 | 11 | .img { 12 | display: flex; 13 | width: auto; 14 | height: auto; 15 | max-width: 100%; 16 | max-height: 100%; 17 | overflow: hidden; 18 | border-radius: var(--border-radius-base); 19 | cursor: pointer; 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .del { 25 | position: absolute; 26 | top: 2px; 27 | right: 2px; 28 | font-size: var(--font-size-middle); 29 | cursor: pointer; 30 | background-color: var(--color-white-1); 31 | display: none; 32 | border-radius: 50%; 33 | height: 16px; 34 | width: 16px; 35 | overflow: hidden; 36 | pointer-events: all; 37 | 38 | &:hover { 39 | transform: scale(1.1); 40 | } 41 | } 42 | 43 | &:hover { 44 | .ant-image .ant-image-mask { 45 | opacity: 1; 46 | transition: opacity var(--ant-motion-duration-slow); 47 | } 48 | 49 | .del { 50 | display: flex; 51 | justify-content: center; 52 | align-items: center; 53 | } 54 | } 55 | } 56 | 57 | .thumb-list-wrap { 58 | display: flex; 59 | gap: 10px; 60 | flex-wrap: wrap; 61 | padding: 10px; 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/playground/view-code/rerank.ts: -------------------------------------------------------------------------------- 1 | import { formatCurlArgs } from './utils'; 2 | 3 | export const generateRerankCode = ({ 4 | api, 5 | parameters 6 | }: Record) => { 7 | const host = window.location.origin; 8 | 9 | // ========================= Curl ========================= 10 | const curlCode = ` 11 | curl ${host}${api} \\ 12 | -H "Content-Type: application/json" \\ 13 | -H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\ 14 | ${formatCurlArgs(parameters, false)}`.trim(); 15 | 16 | // ========================= Python ========================= 17 | const pythonCode = ` 18 | import requests\n 19 | url="${host}${api}" 20 | headers = { 21 | "Content-type": "application/json", 22 | "Authorization": "Bearer $\{YOUR_GPUSTACK_API_KEY}" 23 | } 24 | data = ${JSON.stringify(parameters, null, 2)}\n 25 | response = requests.post(url, headers=headers, json=data) 26 | print(response.json())`.trim(); 27 | 28 | // ========================= Node.js ========================= 29 | const nodeJsCode = ` 30 | const axios = require('axios'); 31 | 32 | const url = "${host}${api}"; 33 | const headers = { 34 | "Content-type": "application/json", 35 | "Authorization": "Bearer $\{YOUR_GPUSTACK_API_KEY}" 36 | }; 37 | const data = ${JSON.stringify(parameters, null, 2)}; 38 | 39 | axios.post(url, data, { headers }).then((response) => { 40 | console.log(response.data); 41 | });`.trim(); 42 | 43 | return { 44 | curlCode, 45 | pythonCode, 46 | nodeJsCode 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/pages/playground/view-code/utils.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | // curl format 4 | export const formatCurlArgs = ( 5 | parameters: Record, 6 | isFormdata?: boolean 7 | ) => { 8 | if (isFormdata) { 9 | return _.keys(parameters).reduce((acc: string, key: string) => { 10 | const val = parameters[key]; 11 | const value = 12 | typeof val === 'object' ? JSON.stringify(val, null, 2) : `${val}`; 13 | return acc + `-F ${key}="${value}" \\\n`; 14 | }, ''); 15 | } 16 | return `-d '${JSON.stringify(parameters, null, 2)}'`; 17 | }; 18 | 19 | // python format 20 | export const formatPyParams = (parameters: Record) => { 21 | return _.keys(parameters).reduce((acc: string, key: string) => { 22 | if (parameters[key] === null || parameters[key] === undefined) { 23 | return acc; 24 | } 25 | const vauleType = typeof parameters[key]; 26 | const value = 27 | vauleType === 'object' 28 | ? JSON.stringify(parameters[key], null, 2) 29 | : vauleType === 'string' 30 | ? `"${parameters[key]}"` 31 | : parameters[key]; 32 | 33 | return acc + ` ${key}=${value},\n`; 34 | }, ''); 35 | }; 36 | 37 | // node format 38 | export const fomatNodeJsParams = (parameters: Record) => { 39 | return JSON.stringify( 40 | { 41 | ...parameters 42 | }, 43 | null, 44 | 4 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /src/pages/resources/components/styles/installation.less: -------------------------------------------------------------------------------- 1 | .container-install { 2 | .notes { 3 | padding: 10px 0; 4 | display: flex; 5 | flex-direction: column; 6 | gap: 5px; 7 | border-left: 2px solid var(--ant-color-split); 8 | margin-bottom: 16px; 9 | padding-left: 16px; 10 | background: var(--ant-color-fill-tertiary); 11 | border-radius: 2px; 12 | list-style-type: decimal; 13 | 14 | li { 15 | margin-left: 12px; 16 | } 17 | } 18 | 19 | .code-pre { 20 | white-space: normal !important; 21 | } 22 | 23 | .ant-radio-button-wrapper { 24 | flex: auto; 25 | font-size: var(--font-size-small); 26 | background-color: var(--ant-tabs-card-bg); 27 | } 28 | } 29 | 30 | .script-install { 31 | .code-pre { 32 | white-space: pre-wrap; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/usage/apis/index.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@umijs/max'; 2 | 3 | export const DASHBOARD_API = '/dashboard'; 4 | 5 | export async function queryDashboardData() { 6 | return request(DASHBOARD_API); 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/usage/components/over-view.less: -------------------------------------------------------------------------------- 1 | :local(.card-body) { 2 | box-shadow: none !important; 3 | 4 | :global(.ant-card-body) { 5 | height: 110px; 6 | display: flex; 7 | justify-content: space-around; 8 | border-radius: var(--ant-border-radius-lg); 9 | border: 1px solid var(--ant-color-border); 10 | } 11 | } 12 | 13 | :local(.row) { 14 | :global(.ant-col-5) { 15 | flex: 0 0 20%; 16 | max-width: 20%; 17 | } 18 | } 19 | 20 | .content { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: space-between; 24 | align-items: center; 25 | font-size: 14px; 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/usage/components/usage-inner/top-user.tsx: -------------------------------------------------------------------------------- 1 | import CardWrapper from '@/components/card-wrapper'; 2 | import HBar from '@/components/echarts/h-bar'; 3 | import { useIntl } from '@umijs/max'; 4 | import React from 'react'; 5 | 6 | interface TopUserProps { 7 | userData: { name: string; value: number }[]; 8 | topUserList: string[]; 9 | } 10 | const TopUser: React.FC = (props) => { 11 | console.log('TopUser====================='); 12 | const { userData, topUserList } = props; 13 | const intl = useIntl(); 14 | 15 | return ( 16 | 17 | 23 | 24 | ); 25 | }; 26 | 27 | export default React.memo(TopUser); 28 | -------------------------------------------------------------------------------- /src/pages/usage/components/usage.tsx: -------------------------------------------------------------------------------- 1 | import breakpoints from '@/config/breakpoints'; 2 | import useWindowResize from '@/hooks/use-window-resize'; 3 | import { useEffect, useState } from 'react'; 4 | import UserInner from './usage-inner'; 5 | 6 | const Usage = () => { 7 | const { 8 | size: { width } 9 | } = useWindowResize(); 10 | const [paddingRight, setPaddingRight] = useState('20px'); 11 | 12 | useEffect(() => { 13 | if (width < breakpoints.xl) { 14 | setPaddingRight('0'); 15 | } else { 16 | setPaddingRight('20px'); 17 | } 18 | }, [width]); 19 | 20 | return ; 21 | }; 22 | 23 | export default Usage; 24 | -------------------------------------------------------------------------------- /src/pages/usage/config/dashboard-context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { DashboardProps } from './types'; 3 | 4 | export const DashboardContext = createContext< 5 | DashboardProps & { fetchData: () => Promise } 6 | >({} as DashboardProps & { fetchData: () => Promise }); 7 | 8 | export default DashboardContext; 9 | -------------------------------------------------------------------------------- /src/pages/usage/config/index.ts: -------------------------------------------------------------------------------- 1 | export const overviewConfigs = [ 2 | { 3 | key: 'worker_count', 4 | label: 'dashboard.workers', 5 | backgroundColor: 'var(--color-white-1)' 6 | }, 7 | { 8 | key: 'gpu_count', 9 | label: 'dashboard.totalgpus', 10 | backgroundColor: 'var(--color-white-1)' 11 | }, 12 | 13 | { 14 | key: 'model_count', 15 | label: 'dashboard.models', 16 | backgroundColor: 'var(--color-white-1)' 17 | }, 18 | { 19 | key: 'model_instance_count', 20 | label: 'models.form.replicas', 21 | backgroundColor: 'var(--color-white-1)' 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /src/pages/usage/config/types.ts: -------------------------------------------------------------------------------- 1 | export interface DashboardProps { 2 | resource_counts: { 3 | worker_count: number; 4 | gpu_count: number; 5 | model_count: number; 6 | model_instance_count: number; 7 | }; 8 | system_load: { 9 | current: { 10 | cpu: number; 11 | ram: number; 12 | gpu: number; 13 | vram: number; 14 | }; 15 | history: { 16 | cpu: { 17 | timestamp: number; 18 | value: number; 19 | }[]; 20 | ram: { 21 | timestamp: number; 22 | value: number; 23 | }[]; 24 | gpu: { 25 | timestamp: number; 26 | value: number; 27 | }[]; 28 | vram: { 29 | timestamp: number; 30 | value: number; 31 | }[]; 32 | }; 33 | }; 34 | model_usage: { 35 | api_request_history: any[]; 36 | completion_token_history: any[]; 37 | prompt_token_history: any[]; 38 | top_users: { 39 | user_id: number; 40 | username: string; 41 | prompt_token_count: number; 42 | completion_token_count: number; 43 | }[]; 44 | }; 45 | active_models: any[]; 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/usage/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageContainer } from '@ant-design/pro-components'; 2 | import { Spin } from 'antd'; 3 | import { memo, useState } from 'react'; 4 | import DashboardInner from './components/dahboard-inner'; 5 | 6 | const Dashboard: React.FC = () => { 7 | const [loading, setLoading] = useState(false); 8 | 9 | return ( 10 | <> 11 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default memo(Dashboard); 29 | -------------------------------------------------------------------------------- /src/pages/usage/styles/index.less: -------------------------------------------------------------------------------- 1 | .value-box { 2 | display: flex; 3 | justify-content: space-around; 4 | align-items: center; 5 | 6 | .value-healthy { 7 | color: var(--ant-green-6); 8 | } 9 | 10 | .value-warning { 11 | color: var(--ant-gold-6); 12 | } 13 | 14 | .value-error { 15 | color: var(--ant-red-6); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/users/apis/index.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@umijs/max'; 2 | import { FormData, ListItem } from '../config/types'; 3 | 4 | export const USERS_API = '/users'; 5 | 6 | export async function queryUsersList(params: Global.SearchParams) { 7 | return request>(`${USERS_API}`, { 8 | methos: 'GET', 9 | params 10 | }); 11 | } 12 | 13 | export async function createUser(params: { data: FormData }) { 14 | return request(`${USERS_API}`, { 15 | method: 'POST', 16 | data: params.data 17 | }); 18 | } 19 | 20 | export async function updateUser(params: { data: FormData }) { 21 | return request(`${USERS_API}/${params.data.id}`, { 22 | method: 'PUT', 23 | data: params.data 24 | }); 25 | } 26 | 27 | export async function deleteUser(id: number) { 28 | return request(`${USERS_API}/${id}`, { 29 | method: 'DELETE' 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/users/config/index.ts: -------------------------------------------------------------------------------- 1 | export const UserRoles = { 2 | ADMIN: 'admin', 3 | USER: 'user' 4 | }; 5 | 6 | export const UserRolesOptions = [ 7 | { label: 'users.form.admin', value: UserRoles.ADMIN }, 8 | { label: 'users.form.user', value: UserRoles.USER } 9 | ]; 10 | -------------------------------------------------------------------------------- /src/pages/users/config/types.ts: -------------------------------------------------------------------------------- 1 | export interface ListItem { 2 | name: string; 3 | is_admin: boolean; 4 | full_name: string; 5 | id: number; 6 | username: string; 7 | created_at: string; 8 | updated_at: string; 9 | } 10 | 11 | export interface FormData { 12 | username: string; 13 | id?: number; 14 | is_admin: boolean | string; 15 | full_name: string; 16 | password: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/request-config.ts: -------------------------------------------------------------------------------- 1 | import { userAtom } from '@/atoms/user'; 2 | import { clearAtomStorage } from '@/atoms/utils'; 3 | import { RequestConfig, history } from '@umijs/max'; 4 | import { message } from 'antd'; 5 | 6 | const NoBaseURLAPIs = ['/auth', '/v1-openai', '/version', '/proxy', '/update']; 7 | 8 | export const requestConfig: RequestConfig = { 9 | errorConfig: { 10 | errorThrower: (res: any) => { 11 | // to do something 12 | }, 13 | errorHandler: (error: any, opts: any) => { 14 | const { message: errorMessage, response } = error; 15 | const errMsg = response?.data?.message || errorMessage; 16 | 17 | if (!opts?.skipErrorHandler && response?.status) { 18 | message.error(errMsg); 19 | } 20 | if (response?.status === 401) { 21 | clearAtomStorage(userAtom); 22 | 23 | history.push('/login', { replace: true }); 24 | } 25 | } 26 | }, 27 | requestInterceptors: [ 28 | (url, options) => { 29 | if (NoBaseURLAPIs.some((api) => url.startsWith(api))) { 30 | options.baseURL = ''; 31 | return { url, options }; 32 | } 33 | return { url, options }; 34 | } 35 | ], 36 | responseInterceptors: [ 37 | (response) => { 38 | // to do something 39 | return response; 40 | } 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /src/services/profile/apis.ts: -------------------------------------------------------------------------------- 1 | import { request } from '@umijs/max'; 2 | 3 | export async function queryCurrentUserState(opts?: Record) { 4 | return request(`/users/me`, { 5 | method: 'GET', 6 | ...opts 7 | }); 8 | } 9 | 10 | export async function queryVersionInfo() { 11 | return request(`/version`, { 12 | method: 'GET' 13 | }); 14 | } 15 | 16 | export async function updateCheck() { 17 | return request(`/update/`, { 18 | method: 'GET' 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/epub-reader.ts: -------------------------------------------------------------------------------- 1 | import ePub from 'epubjs'; 2 | 3 | export default function readEpubContent(file: File): Promise { 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.onload = function (e: any) { 7 | const arrayBuffer = e.target.result; 8 | const book = ePub(arrayBuffer); 9 | 10 | book.loaded?.spine?.then?.((spine: any) => { 11 | const chapterPromises = spine.spineItems?.map?.((chapter: any) => { 12 | return book.load(chapter.href).then((content: any) => { 13 | return content.body?.textContent || ''; 14 | }); 15 | }); 16 | Promise.all(chapterPromises) 17 | .then((chaptersText) => { 18 | const result = chaptersText.join(''); 19 | resolve(result); 20 | }) 21 | .catch((error) => { 22 | reject(error); 23 | }); 24 | }); 25 | }; 26 | reader.onerror = (error) => reject(error); 27 | reader.readAsArrayBuffer(file); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/excel-reader.ts: -------------------------------------------------------------------------------- 1 | import XLSX from 'xlsx'; 2 | 3 | export default function readExcelContent(file: File): Promise { 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.onload = function (e: any) { 7 | const arrayBuffer = e.target.result; 8 | const workbook = XLSX.read(arrayBuffer, { type: 'string' }); 9 | const ws = workbook.Sheets[workbook.SheetNames[0]]; // get the first worksheet 10 | const data = XLSX.utils.sheet_to_json(ws); 11 | resolve(JSON.stringify(data)); 12 | }; 13 | reader.onerror = (error) => reject(error); 14 | reader.readAsArrayBuffer(file); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/localstore/index.ts: -------------------------------------------------------------------------------- 1 | import localStore from './store'; 2 | 3 | const IS_FIRST_LOGIN = 'is_first_login'; 4 | 5 | const REMEMBER_ME_KEY = 'r_m'; 6 | const CRYPT_TEXT = 'seal'; 7 | 8 | const store = localStore.createInstance({ name: '_xWXJKJ_S1Sna_' }); 9 | 10 | const rememberMe = (key: string, data: any) => { 11 | if (store) { 12 | store.setItem(key, data); 13 | } 14 | }; 15 | 16 | const getRememberMe = (key: string) => { 17 | if (store) { 18 | return store.getItem(key); 19 | } 20 | }; 21 | 22 | const removeRememberMe = (key: string) => { 23 | if (store) { 24 | store.removeItem(key); 25 | } 26 | }; 27 | 28 | const readState = (key: string) => { 29 | if (store) { 30 | return store.getItem(key); 31 | } 32 | }; 33 | 34 | const writeState = (key: string, data: any) => { 35 | if (store) { 36 | store.setItem(key, data); 37 | } 38 | }; 39 | 40 | export { 41 | CRYPT_TEXT, 42 | IS_FIRST_LOGIN, 43 | REMEMBER_ME_KEY, 44 | getRememberMe, 45 | readState, 46 | rememberMe, 47 | removeRememberMe, 48 | writeState 49 | }; 50 | 51 | export default store; 52 | -------------------------------------------------------------------------------- /src/utils/localstore/store.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | import localForage from 'localforage'; 3 | import { get } from 'lodash'; 4 | 5 | class Localestore extends Object.getPrototypeOf(localForage).constructor { 6 | constructor() { 7 | super(); 8 | this.state = {}; 9 | } 10 | 11 | public async removeValue(key: string) { 12 | return localForage.removeItem(key); 13 | } 14 | 15 | public async setValue( 16 | key: string, 17 | value: any, 18 | callback?: (args?: any) => void, 19 | expire?: number 20 | ) { 21 | if (!expire) { 22 | return localForage.setItem(key, { value }, callback); 23 | } 24 | return localForage.setItem( 25 | key, 26 | { 27 | value, 28 | expire: { 29 | expire, 30 | createTime: dayjs().valueOf(), 31 | expiration: dayjs().add(expire, 'day').valueOf() 32 | } 33 | }, 34 | callback 35 | ); 36 | } 37 | 38 | public async getValue(key: string, callback?: (args?: any) => void) { 39 | const storeValue = await localForage.getItem(key, callback); 40 | 41 | const expire = get(storeValue, 'expire'); 42 | if (!expire) return storeValue; 43 | const expiration = get(expire, 'expiration'); 44 | return { 45 | value: get(storeValue, 'value'), 46 | isExpiration: dayjs().isAfter(expiration) 47 | }; 48 | } 49 | } 50 | 51 | export default new Localestore(); 52 | -------------------------------------------------------------------------------- /src/utils/pdf-reader.ts: -------------------------------------------------------------------------------- 1 | import * as pdfjsLib from 'pdfjs-dist'; 2 | 3 | pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( 4 | 'pdfjs-dist/build/pdf.worker.mjs', 5 | // @ts-ignore 6 | import.meta.url 7 | ).href; 8 | 9 | const readPDFContent = (file: File): Promise => { 10 | return new Promise((resolve, reject) => { 11 | const reader = new FileReader(); 12 | reader.onload = async function (e: any) { 13 | try { 14 | const arrayBuffer = e.target.result; 15 | const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer }); 16 | 17 | const pdf = await loadingTask.promise; 18 | const numPages = pdf.numPages; 19 | 20 | const pagePromises = []; 21 | 22 | for (let i = 1; i <= numPages; i++) { 23 | const pagePromise = pdf.getPage(i).then(function (page) { 24 | return page.getTextContent().then(function (textContent) { 25 | return textContent.items 26 | .map(function (item: any) { 27 | return item.str; 28 | }) 29 | .join(' '); 30 | }); 31 | }); 32 | pagePromises.push(pagePromise); 33 | } 34 | 35 | const pageTexts = await Promise.all(pagePromises); 36 | const result = pageTexts?.join(' '); 37 | resolve(result); 38 | } catch (error) { 39 | reject(error); 40 | } 41 | }; 42 | 43 | reader.onerror = (error) => reject(error); 44 | reader.readAsArrayBuffer(file); 45 | }); 46 | }; 47 | 48 | export default readPDFContent; 49 | -------------------------------------------------------------------------------- /src/utils/pptx-reader.ts: -------------------------------------------------------------------------------- 1 | import JSZip from 'jszip'; 2 | 3 | const readPptxContent = (file: File): Promise => { 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.onload = function (e: any) { 7 | const arrayBuffer = e.target.result; 8 | JSZip.loadAsync(arrayBuffer).then(function (zip: any) { 9 | const slideFiles = Object.keys(zip.files).filter(function (fileName) { 10 | return ( 11 | fileName.startsWith('ppt/slides/slide') && fileName.endsWith('.xml') 12 | ); 13 | }); 14 | 15 | let slideText = ''; 16 | const slidePromises = slideFiles.map((slideFile) => 17 | zip 18 | .file(slideFile) 19 | .async('string') 20 | .then(function (content: any) { 21 | const parser = new DOMParser(); 22 | const xmlDoc = parser.parseFromString(content, 'application/xml'); 23 | const texts = xmlDoc.getElementsByTagName('a:t'); 24 | for (let i = 0; i < texts.length; i++) { 25 | slideText += texts[i].textContent + '\n'; 26 | } 27 | }) 28 | ); 29 | 30 | Promise.all(slidePromises).then(() => { 31 | resolve(slideText); 32 | }); 33 | }); 34 | }; 35 | reader.onerror = (error) => reject(error); 36 | reader.readAsArrayBuffer(file); 37 | }); 38 | }; 39 | 40 | export default readPptxContent; 41 | -------------------------------------------------------------------------------- /src/utils/read-html.ts: -------------------------------------------------------------------------------- 1 | export default function readHtmlContent(file: File): Promise { 2 | return new Promise((resolve, reject) => { 3 | const reader = new FileReader(); 4 | reader.onload = function (e: any) { 5 | const fileContent = e.target.result; 6 | 7 | const parser = new DOMParser(); 8 | const doc = parser.parseFromString(fileContent, 'text/html'); 9 | const textContent = doc.body.textContent || ''; 10 | const cleanedTextContent = textContent.replace(/[\t\n]/g, '').trim(); 11 | resolve(cleanedTextContent); 12 | }; 13 | reader.onerror = (error) => reject(error); 14 | reader.readAsText(file); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/word-reader.ts: -------------------------------------------------------------------------------- 1 | import mammoth from 'mammoth'; 2 | 3 | export default function readWordContent(file: File): Promise { 4 | return new Promise((resolve, reject) => { 5 | const reader = new FileReader(); 6 | reader.onload = function (e: any) { 7 | const arrayBuffer = e.target.result; 8 | mammoth 9 | .extractRawText({ arrayBuffer }) 10 | .then((result) => { 11 | resolve(result.value); 12 | }) 13 | .catch((error) => reject(error)); 14 | }; 15 | reader.onerror = (error) => reject(error); 16 | reader.readAsArrayBuffer(file); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "react-jsx", 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noImplicitReturns": true, 13 | "noImplicitAny": true, 14 | "declaration": true, 15 | "skipLibCheck": true, 16 | "resolveJsonModule": true, 17 | "paths": { 18 | "@/*": ["./src/*"], 19 | "@@/*": ["./src/.umi/*"], 20 | "@@test/*": ["./src/.umi-test/*"] 21 | } 22 | }, 23 | "include": [ 24 | "./**/*.d.ts", 25 | "./**/*.ts", 26 | "./**/*.tsx", 27 | "src/components/logs-viewer/parse-worker.ts", 28 | "src/components/image-editor/invert-worker.ts", 29 | "src/components/image-editor/offscreen-worker.ts" 30 | // "src/pages/playground/config/embedding-worker.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'slash2'; 2 | declare module '*.css'; 3 | declare module '*.less'; 4 | declare module '*.scss'; 5 | declare module '*.sass'; 6 | declare module '*.svg'; 7 | declare module '*.png'; 8 | declare module '*.jpg'; 9 | declare module '*.jpeg'; 10 | declare module '*.gif'; 11 | declare module '*.bmp'; 12 | declare module '*.tiff'; 13 | declare module 'omit.js'; 14 | declare module 'numeral'; 15 | declare module 'mockjs'; 16 | declare module 'react-fittext'; 17 | declare module 'lodash'; 18 | declare module 'crypto-js'; 19 | declare module 'has-ansi'; 20 | declare module 'terminal-kit'; 21 | declare module 'monaco-vim'; 22 | declare module '@orcid/bibtex-parse-js'; 23 | declare module 'sortablejs'; 24 | declare module 'colorthief'; 25 | declare module 'vibrant'; 26 | declare module 'node-vibrant'; 27 | declare module 'lamejs'; 28 | 29 | declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; 30 | --------------------------------------------------------------------------------