Compare commits

..
730 Commits
Author SHA1 Message Date
jialin f9cf111a86 chore: vllm docs link 2025-08-20 17:21:01 +08:00
jialin 99f8914830 chore: update vllm arguments 2025-08-20 16:43:48 +08:00
jialin 6da877709d style: display title when sso loading 2025-08-20 16:43:48 +08:00
jialin c4948dc11f fix: sso message 2025-08-19 11:08:15 +08:00
jialin 3f243129a4 fix: do not extract the error message from chat chunk 2025-08-18 18:22:46 +08:00
jialin eb2cae7a77 fix: break early while reading stream data 2025-08-15 19:12:12 +08:00
jialin a2bbcd3939 style: side menu 2025-08-15 15:37:51 +08:00
jialin 0650c4bc26 chore: some sso adjustments 2025-08-15 10:26:24 +08:00
jialin 7c32bf97ae chore: auth-config api 2025-08-14 21:50:59 +08:00
jialin 70aecc1f0c fix: keep storing avatar url when edit external user 2025-08-14 20:28:21 +08:00
jialin 5bd5c7d2c5 feat: add user avatar 2025-08-14 16:39:25 +08:00
jialin d6dce4021d fix: filter non-normal gguf files 2025-08-14 11:03:55 +08:00
jialin ccfdb99348 fix: miss global user info 2025-08-13 20:13:46 +08:00
jialin 2ca712435a fix: markdown view \n do not work 2025-08-13 19:50:54 +08:00
jialin 0af8e34a8f fix: external redirect issue 2025-08-13 19:50:54 +08:00
jialin f2583e152a style: login form style 2025-08-13 16:25:26 +08:00
jialin cf70bd2dc1 refactor: login form based on sso auth 2025-08-13 12:15:16 +08:00
forrestzhouandjialin 8f50af917f feat: Change get_config to auth_config to make it more specific 2025-08-12 17:19:04 +08:00
forrestzhouandjialin 4bdde365cf add sso login logic 2025-08-12 17:19:04 +08:00
forrestzhouandjialin 7f1bc33eed first push 0.7 2025-08-12 17:19:04 +08:00
jialin d0d24aba4e fix: markdown viewer white-space: pre-line 2025-08-12 16:45:50 +08:00
EVGENY Mandjialin 0d68c0d683 Update menu.ts 2025-08-12 00:15:14 +08:00
jialin e1ab735823 perf: toggle theme too much render in catalog 2025-08-11 09:35:01 +08:00
jialin bb67a8cbbb chore: display downloading progress in model instance log 2025-08-08 15:51:09 +08:00
jialin c8a7ac7dc6 style: keep text wrap for evalution result 2025-08-05 15:57:35 +08:00
EVGENY Mandjialin 788a294200 Update menu.ts 2025-07-31 09:38:00 +08:00
EVGENY Mandjialin f34935a634 Update models.ts 2025-07-31 09:36:50 +08:00
EVGENY Mandjialin a7331256fc Update dashboard.ts 2025-07-31 09:36:21 +08:00
jialin 8fa3941db9 fix: do not evaluate when local path is null 2025-07-30 11:12:13 +08:00
jialin ada120c1b9 fix: the theme align with the system 2025-07-30 11:12:13 +08:00
jialin 72f7168893 fix: adjust precision for big size 2025-07-29 18:40:29 +08:00
jialin 75b70a26bd fix: download log failed 2025-07-25 11:26:09 +08:00
jialin 4433e5d8e5 chore: move favicon icon to root 2025-07-23 16:02:58 +08:00
jialin 351b2e1417 chore: adjust the evaluation result statement 2025-07-23 11:13:37 +08:00
jialin 3c7dc5a49e fix: update the instance servers field 2025-07-22 19:26:08 +08:00
jialin 9895231696 fix: miss model category、name on catalog page 2025-07-21 19:51:30 +08:00
jialin 3fa3281c51 fix: progress bar default open 2025-07-18 15:12:18 +08:00
jialin bbf11d0374 style: model file status cell size 2025-07-18 10:35:35 +08:00
jialin 04ee696f6a style: playground bold text 2025-07-17 17:26:34 +08:00
jialin ddbbe92a85 chore: show used vram for a second 2025-07-17 12:01:49 +08:00
jialin 32ba5e1802 fix(style): axis tick value too long 2025-07-16 16:33:22 +08:00
jialin a1c6eb177a chore: remove autoplay for audio and video 2025-07-16 14:38:37 +08:00
jialin 14d60264c7 fix: wav format audio, #2317 2025-07-16 13:06:01 +08:00
jialin f4d6abace7 fix: remove insert action for watching, #2504 2025-07-15 19:33:04 +08:00
jialin a5e1717138 chore: update mindie backend parameters 2025-07-15 15:21:26 +08:00
jialin 472a4d400b chore: installer download link 2025-07-14 17:01:11 +08:00
jialin fda4786737 fix: set null to filename for download non-gguf 2025-07-11 11:07:54 +08:00
jialin c3686579b2 chore: add worker statements 2025-07-11 10:09:01 +08:00
jialin f2dfb2f32d style: uniform margin top for resources pages 2025-07-11 10:09:01 +08:00
jialin 60ee9f1a15 fix: catalog jump to deployments route error 2025-07-10 17:50:21 +08:00
jialin f43d1af6a7 fix(style): menu item text wrap 2025-07-10 15:24:55 +08:00
jialin b631b4c7b7 chore: remove menu data render 2025-07-10 14:52:08 +08:00
jialin e3aba0529f chore: update iconfont files 2025-07-10 10:02:10 +08:00
jialin cf682feae9 fix: menu items for user role 2025-07-10 10:02:10 +08:00
jialin 03d798760c chore: add worker guide 2025-07-10 09:47:12 +08:00
jialin 51450c2b02 style: flatten menu items 2025-07-10 09:47:12 +08:00
EVGENY Mandjialin 751ddbb3f3 Update resources.ts 2025-07-10 09:44:00 +08:00
EVGENY Mandjialin 36b3620d0c Update common.ts 2025-07-10 09:43:16 +08:00
jialin fd3eb513ff chore: update the guide for adding worker 2025-07-09 16:03:07 +08:00
jialin af0aaddbfd fix: update the requestId when switch model 2025-07-09 16:03:07 +08:00
jialin b167170aa9 style: tag tooltip content width 2025-07-09 16:03:07 +08:00
jialin 642ac3ebc4 fix: reset form after switch model 2025-07-07 17:40:09 +08:00
jialin 69cceb4fc7 refactor: update user guide for add worker
chore: update resource locales
2025-07-07 17:40:09 +08:00
jialin ac32e23e87 fix: distributed inference across workers disabled for vox-box 2025-07-04 20:57:57 +08:00
jialin 04de4ff606 fix: enable distributed inference field for mindie 2025-07-04 19:56:47 +08:00
jialin 27761566c9 fix: collpase instance error 2025-07-04 17:49:17 +08:00
jialin 3d93eda92d fix: distribution downloading data do not show 2025-07-04 16:45:15 +08:00
jialin 6943571a6d fix: images did not get cleared after clicking stop 2025-07-04 15:42:03 +08:00
jialin 28994cb9da chore: add iluvatar corex in add worker 2025-07-04 15:42:03 +08:00
jialin 5866634b3f chore: update iconfont files 2025-07-03 11:38:19 +08:00
jialin e3d91f1cc7 style: source icon in model result list 2025-07-03 11:38:19 +08:00
jialin 8ac06259f5 fix: do nothing when clicking the selected model or file 2025-07-03 10:41:22 +08:00
jialin c8933a6669 fix: update form values when switching model、size, #2191 2025-07-02 20:34:48 +08:00
jialin 6c2da264db fix: image generating flickering, #1415 2025-07-02 19:33:33 +08:00
jialin 993f01a301 fix: multiple select ux 2025-07-02 11:39:54 +08:00
jialin 9e29e7283c feat: add simple select component 2025-07-01 20:20:50 +08:00
jialin 7da106798d fix: some fields value do not be updated from default spec 2025-06-30 20:10:20 +08:00
jialin b2e9cbb44a chore: update backend parameters and logs reading frequency 2025-06-30 18:57:13 +08:00
jialin 143f6df2c1 perf: do not handle ansi sequence for parsing logs 2025-06-30 17:07:29 +08:00
jialin a64505fcc1 chore: update mindie parameters 2025-06-29 16:43:03 +08:00
jialin 0aaf919827 fix: switch model clear backend_parameters, env, backend_version 2025-06-27 18:07:30 +08:00
jialin bf8e988488 style: top users chart compact 2025-06-27 17:26:34 +08:00
jialin d26ea28028 fix(style): top users bar name too long 2025-06-27 16:42:24 +08:00
jialin abdd5e08a6 fix(style): top users bar name too long 2025-06-27 16:38:19 +08:00
jialin 47a5ef915b fix: pick backend_parameter, env, backend_version from spec in catalog 2025-06-27 16:38:19 +08:00
jialin 5861508153 fix(style): imgs overflow style on chat 2025-06-27 13:43:40 +08:00
jialin 85754ccfb5 fix(style): chart grid right margin 2025-06-27 10:53:51 +08:00
jialin cbeea23295 style: keep the logs pagination visible 2025-06-27 10:15:11 +08:00
jialin e2d79a6802 style: multiple selection tag 2025-06-26 20:39:07 +08:00
jialin c68d85096b fix(style): usage reponse size 2025-06-26 15:54:59 +08:00
jialin fcafa83111 fix: split line align for two yAxis chart 2025-06-26 15:54:59 +08:00
jialin 47d7418eb1 fix: usage: datepicker item disabled issue 2025-06-26 12:15:09 +08:00
jialin 8751400ae3 fix: distributed servers info 2025-06-25 20:29:48 +08:00
jialin 3889c1fa71 fix: reranker item uid updated, chat image size issue, #2280 2025-06-25 16:49:46 +08:00
jialin a4d1d3b16c style: skeleton for search result 2025-06-25 14:35:00 +08:00
jialin adfdf41e85 fix: do not check the required fields for evaluation, #2191 2025-06-25 14:35:00 +08:00
jialin a6825f7ee3 feat: clean markdown content 2025-06-25 14:35:00 +08:00
jialin 830ee45161 fix: update evaluation result issue 2025-06-24 20:47:27 +08:00
jialin 5f7df3f871 chore: update usage api 2025-06-24 16:54:03 +08:00
jialin 34af730934 feat: model usage 2025-06-24 16:54:03 +08:00
jialin 645801fe86 chore: mindle multiple support 2025-06-23 20:23:05 +08:00
jialin 547f49a978 fix: audio uploader has cache issue 2025-06-23 16:25:22 +08:00
jialin fd3d7a6f29 style: side menu style 2025-06-23 15:13:49 +08:00
jialin 6be7140a47 fix: #2227, #2263 2025-06-20 19:34:04 +08:00
jialin ca7dc02d88 revert: markdown katex parse 2025-06-20 11:18:18 +08:00
jialin 1924c45358 fix: chat request payload error, #2163 2025-06-20 11:18:18 +08:00
jialin be92f92991 fix: for gguf ref not be created, #2227 2025-06-19 15:25:21 +08:00
jialin a9b909cca1 fix: upload a audio will clear imgs, #2163 2025-06-19 13:36:49 +08:00
jialin 175b83dd68 fix: #2191,#2227 2025-06-19 13:36:49 +08:00
jialin df85284a60 style: audio player, usage chart 2025-06-19 13:36:49 +08:00
jialin 17ffa93393 fix: do not modify the custom name 2025-06-18 17:56:19 +08:00
jialin 73c525127f fix(style): simple audio player 2025-06-18 17:45:58 +08:00
jialin 3e76d7c321 fix: embedding params escape 2025-06-18 15:03:35 +08:00
jialin 9295f9cef3 fix: sync and update the evaluation result 2025-06-18 11:48:09 +08:00
jialin 9d1e6df8f0 chore: date ranger picker presets 2025-06-18 11:48:09 +08:00
jialin 0805d70c07 style: usage download button 2025-06-17 16:17:26 +08:00
jialin 0133524f09 fix: debounce for page change in search models 2025-06-17 11:50:33 +08:00
EVGENY Mandjialin 2c5100b2ed Update common.ts 2025-06-17 09:50:38 +08:00
EVGENY Mandjialin 51296cdc15 Update dashboard.ts 2025-06-17 09:50:14 +08:00
EVGENY Mandjialin f261bec248 Update users.ts 2025-06-17 09:49:54 +08:00
jialin 97c7087f6c fix(style): esc hint flickering 2025-06-16 19:54:23 +08:00
jialin 109906d083 fix: usage chart y axis ticks, add esc hint for drawer 2025-06-16 19:28:02 +08:00
jialin 81833c4938 fix: update gpus state by polling 2025-06-12 17:26:34 +08:00
jialin 1b6cfdbdc4 fix: evaluate status add lock state 2025-06-12 09:57:25 +08:00
jialin 281d8bddc1 feat: add pagination for searching models 2025-06-10 19:24:13 +08:00
jialin 0eaa3aeeb7 fix: do not undo when is inverting 2025-06-09 19:39:27 +08:00
jialin 4dcb0bcfed fix: image editor fit view incorrect, do not query gguf files when gguf unchecked 2025-06-09 18:45:47 +08:00
jialin e69b7dfd6a Revert "fix: normaliza embedding data"
This reverts commit 2bfb93a063.
2025-06-09 18:45:47 +08:00
jialin 38d63c71ee fix: normaliza embedding data 2025-06-09 18:45:47 +08:00
jialin 6880fd3f5f fix: user settings locales 2025-06-09 18:45:47 +08:00
jialin 1771ca363a fix: show embedding token usage 2025-06-09 18:45:47 +08:00
jialin c896c88c23 chore: remove ollama model source 2025-06-09 18:45:47 +08:00
jialin 5ef733729b fix: ui conflicts in update of multiple evalute result 2025-06-09 18:45:47 +08:00
jialin f73e29937c chore: export table title 2025-06-09 18:45:47 +08:00
jialin 8383547943 style: simple audio player 2025-06-09 18:45:47 +08:00
jialin 8070d3f1e6 style: usage chart style 2025-06-09 18:45:47 +08:00
jialin a161fa953e feat: usage export data 2025-06-09 18:45:47 +08:00
jialin 471e1ae96f feat: upload audio file in chat 2025-06-03 19:58:08 +08:00
jialin ed28bcdfb0 fix: set default spec to form 2025-06-03 15:16:52 +08:00
gitlawr 31ea81c211 ci: publish v*-dev branches 2025-05-30 14:02:56 +08:00
jialin 283189a17c fix: model instances always update by cache 2025-05-30 11:23:25 +08:00
jialin 40020df865 fix: clear backend parameters when change model file 2025-05-29 11:32:23 +08:00
jialin aa6b5ec24f fix: refresh model list manually 2025-05-28 17:25:23 +08:00
jialin f53fe042a9 fix: update allChildren after stop or start 2025-05-28 14:41:35 +08:00
jialin 034a54bf4b chore: model file components for updating 2025-05-27 19:45:24 +08:00
jialin 894dd1e353 chore: update mindie parameters 2025-05-27 16:50:08 +08:00
jialin ca99db5a97 chore: show llm code with common component 2025-05-27 16:50:08 +08:00
jialin 61e506010c fix: pca calculating takes a long time 2025-05-26 19:56:51 +08:00
jialin 4b4343a9fd chore: add watch for worker and gpu updating 2025-05-26 16:17:17 +08:00
jialin 9efacefba3 fix: remove worker-loader 2025-05-25 19:45:57 +08:00
jialin f288c9a9ad perf: embedding calc in worker process 2025-05-25 11:15:50 +08:00
jialin ec45f23485 chore: adjust the delay for scroller 2025-05-20 19:33:10 +08:00
jialin 1c11e73ff8 fix: playground parameters area resizable 2025-05-20 19:33:10 +08:00
EVGENY Mandjialin 174f93fe11 Update common.ts 2025-05-17 22:21:40 +08:00
jialin 6340f1fb54 fix: refresh selection after deleting by batch 2025-05-16 16:07:55 +08:00
jialin febdc5b8e2 fix: add delete action for table watch 2025-05-16 11:51:07 +08:00
jialin b5044b8753 fix: clear query do not trigger a search 2025-05-16 10:10:03 +08:00
jialin 00a60b8948 chore: update error components for loading file failed 2025-05-15 22:57:24 +08:00
jialin 17ec70f839 chore: init mask theme 2025-05-15 19:04:40 +08:00
jialin ac03a93c64 chore: add dark mask, remove clear button in audio and image 2025-05-15 18:02:07 +08:00
jialin 5e8692eae5 fix: theme setting miss after refreshing 2025-05-15 11:53:10 +08:00
jialin 2e56e93d83 fix(style): rerank text overlap 2025-05-14 22:34:39 +08:00
jialin 11d0ff2ec3 chore: update iconfont files 2025-05-14 22:19:27 +08:00
jialin 8a59025132 fix: theme setting in login 2025-05-14 22:19:27 +08:00
jialin 5488a8ea82 fix: system theme 2025-05-14 20:13:54 +08:00
jialin 18cb82a0fe chore: preview field use switch 2025-05-14 14:54:32 +08:00
jialin f951358db4 fix: stop thinking when loading end 2025-05-13 20:29:32 +08:00
jialin a23162117c fix: remember me not worke in login 2025-05-13 20:29:32 +08:00
jialin b13e7b89e2 fix(style): downloading popover 2025-05-12 19:16:29 +08:00
jialin 2416fd1c71 fix: add version holder for backend 2025-05-12 15:13:44 +08:00
jialin 2b7eb832ed fix: remove evaluating for files 2025-05-12 15:13:44 +08:00
jialin 1532f47da1 fix: avoid sending all files when evaluating 2025-05-09 19:33:50 +08:00
jialin 2fa20de631 fix: message format with imgs, downloading table style 2025-05-09 15:59:24 +08:00
EVGENY Mandjialin b51e16bba8 Update models.ts 2025-05-09 09:32:14 +08:00
EVGENY Mandjialin 8ae6c4fd7a Update common.ts 2025-05-09 09:32:00 +08:00
jialin 53b51f0740 fix: remove all the slashes at the end 2025-05-08 20:36:34 +08:00
jialin 92b4be90d0 fix: wrong header content-type for curl tts 2025-05-08 18:25:53 +08:00
jialin 80a6658769 chore: update iconfont files 2025-05-08 18:25:53 +08:00
jialin f0f4bd8bf2 style: login modal 2025-05-08 15:22:53 +08:00
jialin e05b323489 chore: add some arguments for mindie 2025-05-08 11:20:25 +08:00
jialin 7f198b64f1 chore: ollama deprecated tips 2025-05-08 11:09:05 +08:00
jialin 1c87fef28d fix: fetch list page more than total page 2025-05-07 16:34:12 +08:00
jialin 816c22979f chore: catalog: scroll to load more data 2025-05-07 16:34:12 +08:00
jialin 42f9a27851 style: remove bg image in login 2025-05-06 20:19:19 +08:00
jialin 6a2d2dfdaf fix: ignore the fields when compare data 2025-05-06 17:42:14 +08:00
jialin efd2088745 style: login page dark style 2025-05-06 17:42:14 +08:00
jialin 56e8e84eaf fix: gpu selector sort 2025-05-06 13:19:06 +08:00
jialin eccebae596 chore: format message content by list 2025-04-30 19:25:06 +08:00
jialin edc36283d9 chore: doc link in compatible message 2025-04-30 19:25:06 +08:00
jialin 2409cfefc9 fix: form values check incorrect 2025-04-30 17:38:15 +08:00
jialin fae73913de style: gauge dark style 2025-04-30 17:38:15 +08:00
jialin 69f398c594 fix: update fields value 2025-04-30 14:56:34 +08:00
jialin fe52238ac2 chore: error handler in stream response 2025-04-30 12:26:37 +08:00
jialin a3501e5417 fix: update form value trigger tips 2025-04-30 11:01:14 +08:00
jialin 52add26905 style: login background 2025-04-29 18:27:38 +08:00
jialin 954b034b8b fix(style): dark style 2025-04-29 18:05:43 +08:00
jialin e5aa5ace15 chore: remove invalid files 2025-04-29 18:05:43 +08:00
EVGENY Mandjialin a6c5ec8581 Update common.ts 2025-04-29 09:35:39 +08:00
jialin beb395ce32 fix(style): global theme settings in login 2025-04-28 18:22:05 +08:00
jialin bcfa80a2d5 chore: remove custom ui 2025-04-28 10:05:06 +08:00
jialin 96e5398ad1 fix(style): dark css vars 2025-04-28 10:05:06 +08:00
jialin 4b748ee43a style: overlay scroller 2025-04-28 10:05:06 +08:00
jialin 838e0f9910 style: code viewer 2025-04-28 10:05:06 +08:00
jialin 548e1378f5 feat: add ThemeTag 2025-04-28 10:05:06 +08:00
jialin fb90d79eb3 chore: custom brand color 2025-04-28 10:05:06 +08:00
jialin 3fb3381a19 style: ajust dark theme 2025-04-28 10:05:06 +08:00
jialin 60d0750695 style: dark theme for scroller 2025-04-28 10:05:06 +08:00
jialin ce9c31f368 chore: dark config 2025-04-28 10:05:06 +08:00
jialin d4d7148f16 fix: create image custom size do not work 2025-04-27 10:51:13 +08:00
jialin b5aba6e007 style: distribution info table word wrap 2025-04-24 23:13:47 +08:00
jialin 2564fa7382 style: distribution info for vllm 2025-04-24 22:47:53 +08:00
jialin c07be7386c revert: remove evaluation loading 2025-04-24 22:17:49 +08:00
jialin c400003ce9 fix: model and file evaluate over close loading 2025-04-24 21:15:42 +08:00
jialin d4ffd8e8e4 fix: do not close loading when evaluating 2025-04-24 20:36:34 +08:00
jialin 9a6b907c11 chore: check default backend deploy from local path 2025-04-24 18:09:54 +08:00
jialin 84d4c7a6f2 chore: do not change the backend in local_path 2025-04-24 17:28:25 +08:00
jialin 0497e2052d fix: audio model use voxbox only 2025-04-24 17:23:25 +08:00
jialin dc8e9e3fef fix: gpus empty 2025-04-24 16:13:58 +08:00
jialin 518a593c2a fix: gpuoptions empty 2025-04-24 16:11:04 +08:00
jialin f7168cc814 fix: quant fp16 first in catalog 2025-04-24 15:49:08 +08:00
jialin 18217dadc3 chore: set mindie to backend in ascend 2025-04-24 15:06:16 +08:00
jialin 559a4969c1 fix: dashboard usage display datas for last 30 days 2025-04-24 12:43:55 +08:00
jialin 7bad6cd20a chore: display the error message of request header 2025-04-23 19:50:58 +08:00
jialin e3987c8aa3 chore: usage data 2025-04-23 16:39:32 +08:00
jialin a46286872d fix: accept image in edit page 2025-04-23 15:51:48 +08:00
jialin 9bb7a125e3 chore: adjust the date range for usage 2025-04-23 14:24:13 +08:00
jialin 3c196abccb style: format large number for chart label 2025-04-23 10:52:00 +08:00
jialin 2e8899dfef chore: remove qwq in ollama options 2025-04-22 19:57:32 +08:00
jialin 8337768c5a fix: check the error type and return as string 2025-04-22 17:03:24 +08:00
jialin 034b371ad4 fix: handle error as string when mismatch the field 2025-04-22 16:51:05 +08:00
jialin 1e68b75c68 chore: stop scrolling when wheeling in chat page 2025-04-22 16:07:44 +08:00
jialin ac1bf7c9bf style: parameter popover maxheight 2025-04-21 20:23:14 +08:00
jialin d60e052f05 fix: do not expand the instance list when request is pending 2025-04-21 18:18:23 +08:00
jialin 43b71c7532 style: bold text 2025-04-21 09:51:56 +08:00
Shion Tanakaandjialin 8ee0a99323 fix: update ja-JP 2025-04-21 09:36:56 +08:00
jialin 50febc342c fix: install command is incorrect after switching 310p 2025-04-18 20:48:24 +08:00
jialin c98667245e chore: update faq link 2025-04-18 20:16:41 +08:00
jialin 76fdf8e501 chore: update storage path holder text 2025-04-18 17:15:18 +08:00
jialin 6270226ead chore: avoid repeat rendering for clicking same image 2025-04-18 15:13:51 +08:00
jialin ed5afff5d7 fix: save mask on initial 2025-04-18 14:40:26 +08:00
jialin 5d8d38bdf0 fix: clear token usage when stoping chat 2025-04-18 11:16:48 +08:00
EVGENY Mandjialin 88af879d31 Update models.ts 2025-04-18 09:30:07 +08:00
EVGENY Mandjialin 91095f14fb Update resources.ts 2025-04-18 09:29:40 +08:00
jialin 65077088b6 fix: handle api error 2025-04-17 17:58:51 +08:00
jialin 47666b7193 fix: handle api error 2025-04-17 17:58:51 +08:00
jialin 353f73ed0b fix: set llamabox to backend for local gguf file 2025-04-17 14:56:49 +08:00
jialin 51ed9dab04 chore: disabled across workers for mindie 2025-04-17 12:34:53 +08:00
jialin c8b0faf05d style: model file source tooltip 2025-04-17 11:55:19 +08:00
jialin 3d7609e3f5 style: chat role 2025-04-16 20:16:26 +08:00
jialin d9611948d7 chore: evaluate error message 2025-04-16 18:02:59 +08:00
jialin 67053db9ba fix(style): api access info modal with 2025-04-16 15:16:31 +08:00
jialin c831c19848 chore: update vllm parameters, do not change backend when file type unknown 2025-04-16 12:34:18 +08:00
jialin ced45bec65 style: overflow layer padding 2025-04-15 11:08:01 +08:00
EVGENY Mandjialin 13ded5595a Update models.ts 2025-04-15 09:28:13 +08:00
EVGENY Mandjialin dfc4a8d713 Update playground.ts 2025-04-15 09:27:41 +08:00
EVGENY Mandjialin 31b9bfa590 Update resources.ts 2025-04-15 09:27:18 +08:00
jialin f93b3c7424 fix(style): see flickering in tooltip overflow dom 2025-04-14 20:39:38 +08:00
jialin 3d32ec7210 chore: allow switch backend between vllm and mindie 2025-04-14 19:29:55 +08:00
jialin b108d4e287 fix(style): tooltip overflow 2025-04-14 16:59:43 +08:00
jialin d8ea86fde1 chore: backend description 2025-04-14 14:08:59 +08:00
jialin 58e979bc45 chore: remove first login ux 2025-04-14 10:37:00 +08:00
jialin d5acf92f8a fix: description consistency 2025-04-14 09:34:16 +08:00
jialin ddd9b9ae97 chore: add parameter --truncation 2025-04-12 11:54:50 +08:00
jialin f77bf37be8 chore: init max tokens for mindie models 2025-04-12 11:48:02 +08:00
jialin fb8fcc5028 fix: set env null after change backend 2025-04-11 20:27:30 +08:00
jialin f80daf6cd4 fix: extra button control 2025-04-11 20:02:39 +08:00
jialin 092aeceb9d chore: clear backend parameters、backend_version、env on change backend 2025-04-11 18:49:06 +08:00
jialin 7542ac9d87 style: hidden expand button when no replicas 2025-04-11 18:49:06 +08:00
jialin b27ebaaccf chore: update iconfont static files 2025-04-11 12:16:35 +08:00
jialin 5b20a7edb8 fix: disabled across_workers for vllm in edit 2025-04-10 17:52:04 +08:00
jialin 48580cb5b0 feat: add api access info modal 2025-04-10 17:30:26 +08:00
jialin 7c79accda7 chore: display warning message after form change in edit model 2025-04-10 12:21:55 +08:00
jialin 1182fdeb43 chore: add backend field in check api 2025-04-09 18:43:42 +08:00
jialin 694ffb0a81 style: add model selection tips in playground 2025-04-09 17:46:18 +08:00
jialin fe8ff2b180 chore: npu ux for add worker 2025-04-09 15:46:30 +08:00
jialin ade1db5215 fix: omit distribution and cpu_offload fields from default spec 2025-04-08 20:58:24 +08:00
jialin 5269cbf862 fix: submit button is incorrect in edit model 2025-04-08 19:44:59 +08:00
jialin d62e43790c chore: extract a model file item 2025-04-08 19:19:41 +08:00
jialin 5766a50bc2 chore: add changes tips 2025-04-08 16:33:19 +08:00
jialin c09503aad8 fix: add backend field in evaluation payload 2025-04-08 14:37:31 +08:00
jialin f6bc183972 fix(style): unify casing of system terms 2025-04-08 13:28:13 +08:00
jialin a087677c6e chore: allow to config distribution in vllm 2025-04-08 09:47:10 +08:00
jialin 529a2ecd81 fix: validate gpu_selector value before check 2025-04-07 20:04:40 +08:00
jialin 3843d1bd3e fix: ollama model change trigger check action 2025-04-07 19:49:37 +08:00
jialin 6be7f6ad68 style: worker filter bar 2025-04-07 19:49:37 +08:00
jialin 30c4fb8032 fix: auto-complete width 2025-04-07 17:10:05 +08:00
jialin a7dee45720 fix: image edit invert mask 2025-04-07 17:10:05 +08:00
jialin f7c597d957 style: shortcut for start to search model 2025-04-07 13:38:53 +08:00
jialin b89e7c6c3d style: form components 2025-04-07 11:51:34 +08:00
jialin 0f7a619a34 chore: add mindie option 2025-04-07 10:31:22 +08:00
jialin 5c1cb120e1 fix: reset some fields for default spec 2025-04-07 10:08:56 +08:00
jialin 9f24e765be fix: check compatiblity after delete selecor 2025-04-05 10:42:24 +08:00
jialin a23f9881ee fix: some fields value are null after select model 2025-04-03 20:42:20 +08:00
EVGENY Mandjialin 80e85a571e Update resources.ts 2025-04-03 20:40:24 +08:00
EVGENY Mandjialin 82a412093e Update models.ts 2025-04-03 20:40:08 +08:00
EVGENY Mandjialin 8986e84dfe Update common.ts 2025-04-03 20:39:53 +08:00
jialin b6136a092d fix: the fields for trigger checking 2025-04-03 18:08:43 +08:00
jialin 939f46c7e6 fix: miss gpu_selector value in catalog 2025-04-03 13:36:09 +08:00
jialin dd8059693d fix: voice list do not be shown 2025-04-02 21:11:50 +08:00
jialin afacd2a8a0 fix: check form data after blur 2025-04-02 20:40:57 +08:00
jialin 4755cf6a25 fix: do not display modelscope source 2025-04-02 17:55:10 +08:00
jialin d3e58c85cf fix: categories value error 2025-04-02 17:55:10 +08:00
jialin 89a6d6a4c1 chore: update add worker command for container 2025-04-02 15:48:46 +08:00
jialin ecbfeb608b fix: can not select voxbox in editing 2025-04-02 14:50:21 +08:00
jialin f8a8314476 chore: check configuration on initial and change values 2025-04-02 14:19:06 +08:00
jialin b1d7bb9fd7 fix: backend is not updated after changing model 2025-04-02 11:24:26 +08:00
EVGENY Mandjialin 28a84a90cf Update resources.ts 2025-04-02 09:22:03 +08:00
EVGENY Mandjialin 067119eac7 Update models.ts 2025-04-02 09:21:27 +08:00
jialin 3fa19cf977 fix: submit form data error 2025-04-01 22:53:25 +08:00
jialin 5cbb5e7d40 style: check compatibility 2025-04-01 20:47:52 +08:00
jialin f5c4749954 chore: check compatibility in edit 2025-04-01 20:47:52 +08:00
jialin a3ee010286 fix: visit auto playground error from model list 2025-04-01 13:58:50 +08:00
jialin 7e8b5f1f63 fix: model filter by worker 2025-04-01 10:56:50 +08:00
jialin eff88528d3 chore: restart on error 2025-03-31 16:21:40 +08:00
jialin f83e49bd3d chore: check compatibility ux 2025-03-31 16:21:40 +08:00
jialin bd6c689d91 chore: compatibility api 2025-03-31 16:21:40 +08:00
jialin f9469a1ea3 fix(style): message toast bgcolor 2025-03-29 21:35:24 +08:00
jialin c0d92fbdd5 chore: add check locales keys 2025-03-28 19:15:57 +08:00
jialin 2079fc1801 fix(style): add worker 2025-03-28 19:15:57 +08:00
jialin 59d01a6bd9 style: table list col span 2025-03-28 19:15:57 +08:00
Shion Tanakaandjialin 4f29fe5040 feat: Add Japanese Language 2025-03-28 15:03:37 +08:00
jialin 455ae8aa05 style(font): font size 14 2025-03-27 18:32:44 +08:00
jialin db417e6a5f style: set theme token 2025-03-27 18:32:44 +08:00
jialin 8d444f24f8 style: table list col span 2025-03-27 16:11:49 +08:00
jialin 618cd667fc feat: row, col component 2025-03-27 16:11:49 +08:00
jialin f4d5419ba1 style: table style 2025-03-27 16:11:49 +08:00
jialin 0d80269e2d fix: windows path style 2025-03-27 12:17:52 +08:00
jialin 861e95673d fix: resource do not display in model files 2025-03-26 19:08:10 +08:00
jialin 997e4a1edd chore: cache resources active tab 2025-03-26 17:31:31 +08:00
jialin bfdf02f065 fix: copy patn button flickering 2025-03-26 17:31:31 +08:00
jialin f658b9d6d9 style: downloading progress 2025-03-26 11:59:49 +08:00
jialin 671fce2961 chore: cleanup file checkbox 2025-03-26 11:59:49 +08:00
EVGENY Mandjialin 93c9ffe109 Update models.ts
update ru
2025-03-26 10:00:04 +08:00
EVGENY Mandjialin 17cd414dde Update resources.ts
update ru
2025-03-26 09:58:33 +08:00
jialin 3bc10291c4 chore: set source as local_path deploy from model files 2025-03-25 21:24:03 +08:00
jialin 8f6c1d0e47 chore: init worker selector 2025-03-25 17:58:01 +08:00
jialin f882fe2c32 fix: deploy from model files source incorrect 2025-03-25 16:39:03 +08:00
jialin b25644c590 chore: worker downloading tooltip 2025-03-25 12:33:29 +08:00
jialin 626e29a001 style: form item style 2025-03-25 12:33:29 +08:00
jialin dca7a6f270 chore: add model file ux 2025-03-25 12:33:29 +08:00
jialin 05ef2b8eaa chore: add model api 2025-03-25 12:33:29 +08:00
jialin 1819649a3a style: backend check info 2025-03-20 12:16:46 +08:00
jialin b8f58b2cc1 fix: can not paste a text in env vars input box 2025-03-19 20:09:43 +08:00
jialin 2ceefab18c feat: download model 2025-03-19 20:09:43 +08:00
jialin 254dd75cd0 style: instance offload info 2025-03-19 11:43:52 +08:00
jialin 6e87d66932 chore: pasted multiple lines in env vars 2025-03-19 11:23:29 +08:00
jialin 3a05d74a36 fix: no undef var error 2025-03-19 11:23:29 +08:00
jialin 4b5f08ccc2 chore: add delete action in table fetch hook 2025-03-19 09:29:31 +08:00
EVGENY Mandjialin cc20050dc8 Update models.ts
update ru
2025-03-18 23:51:40 +08:00
jialin 25cf439f03 chore: extract fetch hooks for common table 2025-03-18 20:35:29 +08:00
EVGENY Mandjialin 2beae9ea5b Update playground.ts
update translation playground.chat.thinking
2025-03-18 18:08:33 +08:00
EVGENY Mandjialin 64a1ab5c60 Update playground.ts
Update RU
2025-03-18 17:19:38 +08:00
jialin 00ef7ab6ca fix: edit mask for undo 2025-03-18 17:15:55 +08:00
jialin 08b3c9d1ac fix: remove TODO tips in locales 2025-03-17 20:25:03 +08:00
jialin 2287196746 chore: load mask by pix 2025-03-17 20:25:03 +08:00
jialin 9ac679d437 fix: block body scroll when view version 2025-03-14 19:40:15 +08:00
jialin db0a5875fa fix: show success message when start model falied 2025-03-14 18:50:45 +08:00
jialin 38ff16f3cd fix(style): always display offloads info 2025-03-14 16:43:36 +08:00
jialin ccbc36f991 fix: do not call models watch after first loading 2025-03-14 16:43:36 +08:00
jialin b47b99bd28 fix: cpu offloads do not display 2025-03-14 10:22:23 +08:00
jialin 55a922427b fix: cot button can not operate 2025-03-13 21:11:53 +08:00
jialin a5525c065c style: chat message render markdown by default 2025-03-13 18:49:20 +08:00
EVGENY Mandjialin 840b3c2a84 Update common.ts
Update RU
2025-03-13 11:11:59 +08:00
EVGENY Mandjialin 3cef5f0bff Update models.ts
Update ru
2025-03-13 11:11:46 +08:00
jialin fa1463939e chore: verfiy docs 2025-03-12 18:47:46 +08:00
jialin b31099c94d chore: update the llamabox backend tips 2025-03-12 18:40:10 +08:00
jialin fc0899d9d3 fix: do not get the backend when local path blur 2025-03-12 17:41:38 +08:00
jialin a7170a4eee chore: merge fetch data request 2025-03-12 16:53:43 +08:00
jialin e04874d074 fix: remove test code 2025-03-12 10:57:29 +08:00
jialin 0fc570b138 chore: the backend matches the model file 2025-03-12 10:43:59 +08:00
jialin 5edbc1a7b5 style: model list buttons 2025-03-12 10:43:59 +08:00
jialin a7cfa05504 fix: markdown word bread 2025-03-10 17:40:55 +08:00
jialin c0988da176 fix: image viewer flick 2025-03-10 17:11:32 +08:00
jialin 12707793b1 chore: display vllm distributed info 2025-03-10 15:11:48 +08:00
jialin 06d6732ab2 docs: update readme.md 2025-03-10 12:33:43 +08:00
jialin ab6e98aae2 chore: press enter to submit chat message 2025-03-09 21:31:58 +08:00
jialin a2ef9d781d chore: display instance resource claim 2025-03-07 17:07:40 +08:00
jialin 106ee43171 fix: base64 convert to file for all image type 2025-03-06 20:27:34 +08:00
jialin 36d626c833 chore: ram vram usage in resource 2025-03-06 19:44:35 +08:00
jialin b0ac85d81d fix: edit image: image field is null 2025-03-06 16:34:55 +08:00
jialin 25d70b7b49 fix: preview field missing in image 2025-03-06 11:55:16 +08:00
jialin 4c5946e8da fix: can be cleared during loading in embedding and rerank 2025-03-06 09:49:07 +08:00
jialin 4de1a38c00 fix: do not init meta data when switch tab 2025-03-05 20:30:53 +08:00
jialin caadc02fb4 chore: reduce re-render times for params in playground 2025-03-05 15:09:47 +08:00
jialin 3e691aa31f fix: default model in embedding page 2025-03-05 14:05:12 +08:00
jialin fd7ce9027a feat: add expand all rows 2025-03-05 14:05:12 +08:00
EVGENY Mandjialin 3fdde5f8c4 Update playground.ts
Upd
2025-03-05 11:25:37 +08:00
jialin 4c5eb721fb chore: update TODO for locales 2025-03-04 19:18:13 +08:00
jialin f3bdb03cae chore: add TODO: Translate me in locales 2025-03-04 18:50:34 +08:00
jialin 9b6112d206 fix: common.ts locales miss comma in ru 2025-03-04 18:07:42 +08:00
EVGENY Mandjialin beab0fa6f6 Rename ru_RU.ts to ru-RU.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin fa60cab587 Create ru_RU.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 05da6b3c50 Create users.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 0b12f4e6fd Create usage.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin b9ddf29289 Create shortcuts.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 10e47b8305 Create resources.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 0e1c9487f0 Create playground.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 60f569dbf9 Create models.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin bdb803db21 Create menu.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 634fab4d9f Create dashboard.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin 34a14865eb Create common.ts 2025-03-04 18:01:29 +08:00
rjohny55andjialin abcd3a30ce Create apikeys.ts
add ru_RU
2025-03-04 18:01:29 +08:00
jialin 9f40bf6f5d chore: generate invertion mask in image editor 2025-03-04 17:52:14 +08:00
jialin 8a98aef28c chore: image edit invert painting area 2025-03-04 17:52:14 +08:00
jialin bb47b4475e chore: per-commit lint 2025-03-03 14:30:55 +08:00
jialin 1bb6c5fbfc chore: init chat params 2025-03-03 10:13:06 +08:00
jialin 35fe459420 chore: init wrapper scroller 2025-03-03 10:13:06 +08:00
jialin d75ac45c33 fix: expand rows in models after operating 2025-02-28 20:07:03 +08:00
jialin 9927da5088 fix: do not init audio model 2025-02-28 12:23:02 +08:00
jialin 295b4b3b72 chore: download progress 2025-02-27 18:03:28 +08:00
jialin 8464e0ea3f feat: add download instance log button 2025-02-27 18:03:28 +08:00
jialin 4b73457f7f style: gpuselector dropdown style 2025-02-26 16:00:29 +08:00
jialin eedcf6f1fe style: select component 2025-02-26 16:00:29 +08:00
jialin 5b130d9500 fix: update first login state after deploying from catalog 2025-02-25 17:49:33 +08:00
jialin 600f457bc2 chore: update /v1-openai to /v1 in playground test code 2025-02-25 16:10:40 +08:00
jialin e1959f74aa chore: first login to default page by localhost 2025-02-25 15:04:05 +08:00
jialin 0f7d04e0b9 fix: page error when edit error 2025-02-25 14:03:14 +08:00
jialin f91311144b fix(style): layout content padding 2025-02-24 14:10:50 +08:00
jialin 529b3cb18f chore: get start from deploying 2025-02-24 11:47:06 +08:00
jialin bcbefe29e7 chore: get start from deploying 2025-02-24 11:34:40 +08:00
jialin 1dd7037aa0 fix: params form render depds on fields 2025-02-23 13:32:03 +08:00
jialin 2c2e9396c4 fix: default route for logo 2025-02-23 13:32:03 +08:00
jialin 69feb6a4f1 chore: image edit init params 2025-02-23 13:32:03 +08:00
jialin 911b4b9138 chore: audio init parametes 2025-02-23 13:32:03 +08:00
jialin 21cd4b67f0 chore: rerank init params 2025-02-23 13:32:03 +08:00
jialin 8c7eebe76c chore: embedding init params 2025-02-23 13:32:03 +08:00
jialin bb8b00e547 refactor: create image hooks 2025-02-23 13:32:03 +08:00
jialin f8c7f88ac2 feat: add custom modal for lock body scroller 2025-02-23 13:32:03 +08:00
jialin c6cc5810ca feat: add env field in deploy form 2025-02-21 17:05:06 +08:00
jialin a89ff584fd chore: minimize static files 2025-02-21 12:21:21 +08:00
jialin 11d824a88d fix: vox-box gpu-selector 2025-02-21 11:45:50 +08:00
jialin f1ab927a7e chore: chat hooks 2025-02-19 20:50:32 +08:00
jialin 9e0634f449 refactor: models list table columns from props 2025-02-19 15:17:17 +08:00
jialin a9006f2954 fix: do not need to open the row after batch updating 2025-02-18 20:06:20 +08:00
jialin 3a021e7879 fix: batch start/stop selected counts 2025-02-18 19:51:22 +08:00
jialin 6ea8cc9986 chore: copy model-tag from user-view 2025-02-18 19:51:22 +08:00
jialin 4c77151877 chore: render katex in markdown 2025-02-18 19:51:22 +08:00
jialin ea34431cf7 chore: react-markdown 2025-02-17 13:49:55 +08:00
jialin 7c546ed6b8 chore: update ui tips 2025-02-17 13:49:55 +08:00
jialin db53920153 style: menu style 2025-02-17 13:49:55 +08:00
jialin c60015bd09 chore: stt player ui 2025-02-17 13:49:55 +08:00
jialin d2a3388058 chore: improve image edit 2025-02-17 13:49:55 +08:00
jialin 384a75622f style: lock body scroll bar when open a modal 2025-02-17 13:49:55 +08:00
jialin 68a62d689c chore: batch start/stop models 2025-02-17 13:49:55 +08:00
jialin 8f7968302d style: model source dropdown 2025-02-17 13:49:55 +08:00
jialin 35a5733e82 chore: gpu selector, source dropdown 2025-02-17 13:49:55 +08:00
jialin f6bbbc56ba chore: negative mask on edit image 2025-02-17 13:49:55 +08:00
jialin d1d601f028 chore: tag style 2025-02-17 13:49:55 +08:00
jialin bbb133d0f2 refactor: gpu selector 2025-02-17 13:49:55 +08:00
jialin 9671f57b65 fix: import lodash in component 2025-02-12 19:34:09 +08:00
jialin d3212c8b49 refactor: message content 2025-02-12 19:34:09 +08:00
jialin 1f01a5b1cc chore: reasoning content without <think> tag 2025-02-11 17:41:55 +08:00
jialin 95f7f5535f fix: do not rerender after editing the markdown 2025-02-11 15:31:16 +08:00
jialin 8ae031dbbd style: markdown render 2025-02-11 14:01:46 +08:00
jialin 4498f069f0 feat: render markdown in chat 2025-02-11 14:01:46 +08:00
jialin 349b895ac5 feat: dcu container worker guide 2025-02-11 10:35:25 +08:00
jialin 794f27a3c8 chore: chat reasoning content 2025-02-10 21:53:36 +08:00
jialin 1def9a6b19 chore: field description icon 2025-02-10 18:44:21 +08:00
jialin cbf1e42c73 chore: local path field tooltip 2025-02-10 11:59:57 +08:00
jialin 7c960bb899 fix: set distributed_inference_across_workers to false when selectingt vox-box 2025-02-09 21:53:46 +08:00
jialin 1a1807df96 fix: download image size on edit page 2025-02-06 15:04:15 +08:00
jialin 2d03f17bde chore: refine wording 2025-02-06 11:07:38 +08:00
jialin 8cbaa6b281 fix: download image size is incorrect 2025-02-05 16:27:01 +08:00
jialin 2c0ae66143 style: side menu raw scroll bar width 0 2025-01-22 18:28:44 +08:00
jialin a54c160d83 fix: remove the constant deps for watch logs 2025-01-22 17:38:24 +08:00
jialin 61fb7b0d86 fix(style): tooltip scroll bar color, auto input trim spaces 2025-01-22 16:32:46 +08:00
jialin b3beae44df fix: child row expand status sync 2025-01-22 15:33:12 +08:00
jialin d1369e765a style: table no data 2025-01-21 14:25:14 +08:00
jialin 6185485c68 fix: update instances with full data 2025-01-21 10:31:40 +08:00
jialin db733ac9ce fix: json stream data is truncated 2025-01-20 22:08:08 +08:00
jialin f94fcf58a7 fix: update instances by filter 2025-01-20 17:47:36 +08:00
jialin 7487841385 chore: save iconfont files locally 2025-01-20 17:47:36 +08:00
jialin 289c4b497d chore: save iconfont files locally 2025-01-20 10:37:54 +08:00
jialin c49bfb97dd fix: clear instances when page hidden 2025-01-19 20:51:06 +08:00
jialin 4643dbce17 fix(style): brush can not fully paint the image 2025-01-19 00:00:15 +08:00
jialin 82e8c01b95 chore: set image raw size to form 2025-01-18 16:03:48 +08:00
jialin 47ced11e03 fix: reset update instance list flag 2025-01-17 23:39:10 +08:00
jialin 04d35a3a83 chore: all models page count 2025-01-17 23:22:49 +08:00
jialin 1df3a0b8cb fix: model instances update when tab active 2025-01-17 23:16:38 +08:00
jialin faa13f58e0 fix: catalog form meta data update 2025-01-17 21:22:15 +08:00
jialin 58fcc90417 fix: clear models expand keys 2025-01-17 14:35:36 +08:00
jialin 0690612477 fix: dashboard usage display 2025-01-16 22:27:04 +08:00
jialin c0344b385a fix: failed to load image when pass empty mask 2025-01-16 22:20:32 +08:00
jialin 214c5998c6 fix: image stream data error 2025-01-16 21:51:22 +08:00
jialin b14c01f9f7 fix: expand the model after deploying from catalog 2025-01-16 17:24:37 +08:00
jialin 4a838e7bdb chore: completions error handler 2025-01-16 14:25:03 +08:00
jialin 4a748e894d chore: rename copy button less 2025-01-16 10:33:18 +08:00
jialin b576491156 fix: remove storkes when use a new base image 2025-01-16 10:14:05 +08:00
jialin 17da790651 fix: update origin image uid for second edit 2025-01-15 22:58:27 +08:00
jialin c599cab14a fix: image display new image 2025-01-15 21:37:28 +08:00
jialin 4240bad627 fix: reranker selection 2025-01-15 19:36:44 +08:00
jialin 0017de7b21 fix: fixed point line width 2025-01-15 17:07:44 +08:00
jialin 53a4ebb66e fix: paste text can not overwrite the selection 2025-01-15 16:11:57 +08:00
jialin 5bfca0f86f chore: message link 2025-01-15 12:42:09 +08:00
jialin 9553aa944a fix: image progress display 2025-01-15 09:51:29 +08:00
jialin 35991ff5d6 chore: llama-box parameters 2025-01-14 18:58:07 +08:00
jialin 42d703ca1d chore: image edit scale 2025-01-14 17:11:46 +08:00
jialin e089ddb223 chore: image edit scale 2025-01-14 17:11:46 +08:00
jialin c323b6a8a9 chore: instance unreachable state 2025-01-14 09:40:15 +08:00
jialin bf461f59de fix: do not edit a image in loading 2025-01-13 19:25:30 +08:00
jialin 9974fa831c fix: image size options 2025-01-13 16:34:08 +08:00
jialin 73e41b0de0 fix: update page number of new logs 2025-01-13 15:59:32 +08:00
jialin 9aa297a804 chore: model file description 2025-01-13 15:51:34 +08:00
jialin 5c1bcdbd74 fix: clear screen logs after loading all 2025-01-13 11:15:34 +08:00
jialin 8c4997fa37 chore: image meta data 2025-01-13 08:22:23 +08:00
jialin d099a22bd9 fix: set last page after loading all logs 2025-01-12 20:01:27 +08:00
jialin 0de0426f72 fix: logs page by the data after parsing 2025-01-12 17:04:12 +08:00
jialin 04d5b692b8 fix: miss logs 2025-01-10 20:23:15 +08:00
jialin da6cdcef84 fix(style): gpu selector closable 2025-01-10 16:55:24 +08:00
jialin fbf2671eaf fix(style): gpu selector closable 2025-01-10 16:51:55 +08:00
jialin 67e050bd6f chore: unreachable state is not ready 2025-01-10 15:53:14 +08:00
jialin 8282f48cdb fix: calc log lines 2025-01-10 15:41:01 +08:00
jialin 5d903de2a2 chore: worker unreachable state 2025-01-10 12:25:06 +08:00
jialin 78deee2de3 chore: llm ctx max value 2025-01-10 10:49:06 +08:00
jialin a09403328e fix: do not update when scrolling to view logs 2025-01-10 10:42:41 +08:00
jialin 45e92e7b7a fix: image preview 2025-01-09 20:23:36 +08:00
jialin ebe8a40202 chore: update lock.yaml 2025-01-09 18:00:07 +08:00
jialin bd0747ff6f revert: image editor ux 2025-01-09 17:53:26 +08:00
jialin b37ec98e17 fix: set max_tokens to meta's half 2025-01-09 16:34:49 +08:00
jialin a93db0a038 fix(style): instance status message 2025-01-09 14:56:48 +08:00
jialin 08f9029bf8 chore: audio file .m4a support 2025-01-09 14:11:26 +08:00
jialin fb9d2d99d4 fix: display current page's logs 2025-01-09 11:27:44 +08:00
jialin a374d4d40f fix: body scrolls to top after opening a modal 2025-01-09 11:27:44 +08:00
jialin fb4666195c fix(style): catalog size tags overflow 2025-01-09 09:26:42 +08:00
jialin 39b6c00c6d fix: stop auto scrolling on logs 2025-01-08 15:43:03 +08:00
jialin decb3ef2f1 fix: add a button to last page on logs 2025-01-08 15:26:03 +08:00
jialin 2a9c501ee2 fix: form data merge with raw data 2025-01-08 13:45:05 +08:00
jialin 60d25a18fb chore: remove source in the form 2025-01-08 12:34:53 +08:00
jialin 39a71d51cf fix: image edit ux 2025-01-06 20:45:32 +08:00
jialin 5c401f3858 chore: catalog page size 100 2025-01-06 08:29:56 +08:00
jialin cc45ec85fd chore: models filter by category 2025-01-03 20:51:22 +08:00
jialin 1d66becff5 fix: default category 2025-01-03 20:51:22 +08:00
jialin 2a4ebcaf9c chore: search by debounce 2025-01-03 20:51:22 +08:00
jialin 1a792254ca chore: catalog card style 2025-01-03 20:51:22 +08:00
jialin d9319f5734 chore: catalog api 2025-01-03 20:51:22 +08:00
jialin 81d96fe3a9 style: catalog item style 2025-01-03 20:51:22 +08:00
jialin 39fff0e57d feat: catalog page 2025-01-03 20:51:22 +08:00
jialin 95058f7890 fix: do not update logs when viewing pre page 2025-01-03 20:38:44 +08:00
jialin 472ef1a231 chore: audio meta data 2025-01-03 17:27:19 +08:00
jialin d7de302393 fix: automatically update the page when scrolling 2025-01-02 12:22:09 +08:00
jialin e4b1dfc77a chore: audio page keepalive 2024-12-31 11:04:30 +08:00
jialin aed7267253 chore: model meta data 2024-12-30 16:07:30 +08:00
jialin a24b7433a5 chore: update edit api 2024-12-30 08:37:58 +08:00
jialin 0b423a6fe0 chore: image edit api 2024-12-30 08:37:58 +08:00
jialin cb98812444 chore: select multiple gpus by manual 2024-12-29 18:31:42 +08:00
jialin b4e30565d2 feat: model category 2024-12-27 19:12:51 +08:00
jialin 9f40e2c71e chore: watch request counts 2024-12-27 14:56:06 +08:00
jialin fb0a2798e7 chore: update img args 2024-12-27 14:56:06 +08:00
jialin 9912b5037d chore: disable editor when loading 2024-12-27 14:56:06 +08:00
jialin bca56bfaad chore: painting cache 2024-12-27 14:56:06 +08:00
jialin 542f048a67 chore: image edit ux 2024-12-27 14:56:06 +08:00
jialin 91c5e7e06c feat: image edit 2024-12-27 14:56:06 +08:00
jialin 972ee69fac style: view instance logs 2024-12-26 12:15:12 +08:00
jialin 00c9ef47e9 chore: search models tips 2024-12-25 16:48:20 +08:00
jialin dcad5f4b5f chore: success toast show twice after deleting 2024-12-25 11:36:02 +08:00
jialin e74cda3c0f chore: image args update 2024-12-24 16:43:19 +08:00
jialin bafbb39883 fix: backend input focus not work 2024-12-24 14:31:38 +08:00
jialin 02391e66dc chore: adjust the deletion modal config 2024-12-24 10:34:08 +08:00
jialin 762ab30c71 chore: add worker command paramters 2024-12-23 19:13:55 +08:00
jialin 6ce6a932e1 chore: add worker labels 2024-12-23 16:44:57 +08:00
jialin e7de1896ea chore: display the direct command in add worker 2024-12-23 16:33:08 +08:00
jialin 4391705c9a chore: model item action list order 2024-12-20 18:27:29 +08:00
jialin b40bf76270 chore: the text on the recreate button 2024-12-19 19:42:57 +08:00
jialin f44aee0af4 feat: model list add stop and start button 2024-12-18 16:13:58 +08:00
jialin 57a1af9392 fix: convert recording to mp3 2024-12-16 18:35:03 +08:00
jialin b16ae64105 fix: modelscope model tasks field null 2024-12-13 18:42:36 +08:00
jialin 0912d583c7 style: open in playground icon 2024-12-13 13:41:02 +08:00
jialin 6eff89aca1 style: audio tab icon 2024-12-13 13:17:07 +08:00
jialin 6e5e195a30 style: model list type tag 2024-12-13 12:13:17 +08:00
jialin e3efda2764 fix: deploy model page error 2024-12-13 10:15:46 +08:00
jialin 5360c3dbaf chore: mp3 converter exec load after init 2024-12-12 19:33:02 +08:00
jialin f6427c50f2 chore: update audio catalog for modelscope 2024-12-12 17:37:14 +08:00
jialin 22035d1383 chore: fetch model list after deleting action 2024-12-12 17:04:07 +08:00
jialin 85949c5927 fix: delay in updating model list 2024-12-12 16:28:14 +08:00
jialin dde746a5fe fix: do not collapse other models, after creating a new 2024-12-12 14:12:33 +08:00
jialin c2ae30575e fix: disable vox-box item when deploy ollama model 2024-12-12 11:49:46 +08:00
jialin cb5d9f626e fix: query is not updated on deploy page when tap enter 2024-12-12 10:03:16 +08:00
jialin c6d82409a8 chore: remove upload on image page 2024-12-11 18:44:00 +08:00
jialin fef5d0e688 fix: audio, recording format error 2024-12-11 18:39:36 +08:00
jialin f64e696f7a chore: support editing the model repoID, filename, model path 2024-12-10 15:43:29 +08:00
jialin acf99a6a9e refactor: generate code viewer 2024-12-09 15:28:18 +08:00
gitlawr a76560c38a chore: add LICENSE 2024-12-09 11:37:37 +08:00
jialin e1eec66e04 fix: text to speech python code 2024-12-05 20:54:28 +08:00
jialin 96d1ee2347 revert: speech to text code use openai lib 2024-12-05 19:50:29 +08:00
jialin 2a06463d07 fix: audio speech to text nodjs code 2024-12-05 18:20:13 +08:00
jialin 899862dc57 fix: audio view code 2024-12-05 17:54:20 +08:00
jialin c8d585889b fix: image random seed uncheck not work 2024-12-05 13:44:16 +08:00
jialin d044423330 fix: image handleonload callback 2024-12-04 21:03:42 +08:00
jialin 273a9a5e66 fix: convert recording to mp3 2024-12-04 20:37:09 +08:00
jialin 0294e0d2b3 fix: recording format for browser 2024-12-04 20:13:08 +08:00
jialin 38daa0826e fix: audio api error message 2024-12-04 19:02:10 +08:00
jialin b9e8dea5d9 chore: update audio docs link 2024-12-04 17:01:43 +08:00
jialin 241930cefe chore: rerank documents verify non-empty 2024-12-04 15:15:35 +08:00
jialin bed3684a0c fix: reset backend parameter options in deploy model modal 2024-12-04 11:15:29 +08:00
jialin e1bb684c4c chore: image size tooltips 2024-12-03 21:54:59 +08:00
jialin 4c75a5b421 fix: audio player pause 2024-12-03 20:21:20 +08:00
jialin 20f809740c fix: audio error message 2024-12-03 17:33:49 +08:00
jialin 83294abaf0 fix: audio set progress error 2024-12-03 16:17:54 +08:00
jialin 687dabc777 fix: audio set progress error 2024-12-03 16:17:54 +08:00
jialin 605d6f0f9b chore: update llama-box parameters 2024-12-03 16:17:54 +08:00
jialin b1d13f5a71 fix(style): image size cache' 2024-12-02 22:15:43 +08:00
jialin 50c740d219 fix(style): image wraper full size 2024-12-02 18:56:27 +08:00
jialin a7456d1182 fix: add cancel for generate text 2024-12-02 17:50:21 +08:00
jialin 804cbb8c56 style: image display ratio 2024-12-02 17:00:26 +08:00
jialin cb13f66b3d chore: voice language default value by current language 2024-12-02 10:44:39 +08:00
jialin ec2cd29096 chore: slider to change width and height in image 2024-12-02 08:20:30 +08:00
jialin 412c6dadca fix(style): stt loading 2024-11-29 22:10:52 +08:00
jialin 1ac3768468 fix: speech size 0, do not create url 2024-11-29 21:09:03 +08:00
jialin e622636686 style: show voice error message 2024-11-29 20:02:54 +08:00
jialin 2ddeca8f9d chore: add copy button 2024-11-29 17:03:46 +08:00
jialin d1a80e1a64 fix: tts show error message 2024-11-29 17:03:46 +08:00
jialin 4cfe41ca9e fix: image view code modal logs 2024-11-29 17:03:46 +08:00
jialin cd1d3005e9 fix: playground view code 2024-11-29 15:26:39 +08:00
jialin 435161854c fix: model logs loading 2024-11-29 13:41:28 +08:00
jialin 890287ce72 fix: image request error message 2024-11-29 10:58:16 +08:00
jialin 0cc2a40f26 chore: docker add worker command 2024-11-28 23:19:31 +08:00
jialin 849150c9ab fix: auto bgcolor remove 2024-11-28 22:03:33 +08:00
jialin 75ac74ca5e fix: image autosize 2024-11-28 22:03:33 +08:00
jialin 9822479d24 fix: remove image auto background 2024-11-28 21:58:28 +08:00
jialin c456f63e8c fix: jump to playground from model list 2024-11-28 21:58:28 +08:00
jialin 51ae3a16bd fix: jump to playground from model list 2024-11-28 21:48:14 +08:00
jialin bd418b23dc fix(style): playground image incomplete 2024-11-28 17:13:51 +08:00
jialin af789ee886 fix: rerank clear failed 2024-11-27 22:47:53 +08:00
jialin 324bd57e3d fix: image chunk data broken 2024-11-27 21:13:31 +08:00
jialin 146bcdcf1c fix: recording do not pass file type 2024-11-27 19:42:29 +08:00
jialin 60c92b8a98 chore: audio api adjust 2024-11-27 19:35:19 +08:00
jialin 230120fff5 chore: image custom params 2024-11-27 17:07:22 +08:00
jialin ba76498acc chore: audio model deploy 2024-11-27 11:04:31 +08:00
jialin 5e0772fae9 chore: images style 2024-11-25 20:07:17 +08:00
jialin 7fcd005f06 chore: audio 2024-11-25 20:07:17 +08:00
jialin 961d420c95 chore: encode url for hf 2024-11-25 12:58:27 +08:00
jialin a42df69a7f feat: add backend version field 2024-11-25 12:10:12 +08:00
jialin 59d2c5ae3c fix: safari page blank 2024-11-22 20:16:23 +08:00
jialin a6f0f554f0 fix: model be cleared when apply config to all in compare 2024-11-22 18:25:07 +08:00
jialin c8c22b3f22 fix: model markdown parse failed 2024-11-22 11:55:19 +08:00
jialin 67febd3e25 style: create images ux 2024-11-22 10:00:34 +08:00
jialin dde937f460 chore: images create 2024-11-22 10:00:34 +08:00
jialin c3473673da chore: speech to text 2024-11-22 10:00:34 +08:00
jialin f24aef300b feat: generate images 2024-11-22 10:00:34 +08:00
jialin 84e0c46711 feat: playground tts 2024-11-22 10:00:34 +08:00
jialin 16e2ac5b6c feat: playground embedding 2024-11-22 10:00:34 +08:00
jialin 01214077d3 fix: rebase main 2024-11-22 10:00:34 +08:00
jialin 1e3957bdd5 refactor: playground rerank layout 2024-11-22 10:00:34 +08:00
jialin 8e7fa486db style: submenu title color 2024-11-22 10:00:34 +08:00
jialin a72088b7a8 chore: rebase main 2024-11-22 10:00:34 +08:00
jialin 419f1e07d1 refactor: rerank item 2024-11-22 10:00:34 +08:00
jialin e05ec3d2d5 feat: playground add submenu 2024-11-22 10:00:34 +08:00
jialin 22f90a505c chore: hf api use proxy 2024-11-20 17:51:28 +08:00
jialin 66e506db07 fix: deploy model shardfile file name 2024-11-18 18:56:47 +08:00
jialin fa4041e560 style: status tooltip 2024-11-18 17:24:07 +08:00
jialin f8a8225b8c fix: close deploy modal reset form 2024-11-13 19:23:31 +08:00
jialin 75636b2575 fix: form fields do not update when toggle backend 2024-11-13 11:45:03 +08:00
jialin e44cc4c145 fix: update form options when open modal 2024-11-12 20:53:07 +08:00
jialin 343ada135d fix: backend updateable when local path 2024-11-08 15:09:25 +08:00
jialin 93a6a77c2b chore: deploy model from local path 2024-11-07 17:35:07 +08:00
jialin 8bc67b323c style: replace virtual inner with logs list 2024-11-06 09:29:12 +08:00
jialin f684a3b0ae fix: large logs page stuck 2024-11-05 19:46:47 +08:00
jialin 5f2115fff6 fix: page got stuck when logs size too large 2024-11-05 19:46:47 +08:00
jialin 9d654bcd66 fix: show upgrade check 2024-10-31 11:06:58 +08:00
jialin 5f8e72c6ae chore: update vllm arguments 2024-10-31 11:06:58 +08:00
jialin 5dde5cae50 fix: filter gguf files in modelscope 2024-10-29 17:55:40 +08:00
jialin f730548c8f fix(style): tooltip shown when no content 2024-10-25 22:36:26 +08:00
jialin a185bd9463 style: viewcode scrollbar color 2024-10-25 20:52:15 +08:00
jialin abdc78e123 fix(style): text overflow 2024-10-25 18:36:18 +08:00
jialin ba49fa6c88 chore: completion payload format 2024-10-25 12:15:26 +08:00
jialin 870ffa9dc1 chore: add button for copy logs 2024-10-24 23:23:47 +08:00
jialin 8afaf1a60e chore: update playground payload format 2024-10-24 22:40:38 +08:00
jialin 6974ac8e14 chore: parse bibtex 2024-10-24 22:40:38 +08:00
jialin 9f274ee17e chore: remove metadata of readme 2024-10-24 17:25:31 +08:00
jialin 673bcde719 fix: playground error message field 2024-10-24 14:52:57 +08:00
jialin 6eaf5bb463 style: comparsion model card title 2024-10-24 11:54:21 +08:00
jialin b463eedb3f fix: remove invalid parameter 2024-10-24 10:25:18 +08:00
jialin 90350f99ad style: table column size 2024-10-23 19:36:29 +08:00
jialin 8576015633 fix: chart resize 2024-10-23 18:04:34 +08:00
jialin f5edf39027 chore: worker ip 2024-10-23 14:47:55 +08:00
jialin 751781e3e5 fix: distributed across workers 2024-10-23 14:27:49 +08:00
jialin bc9129bcbe fix(style): body scroll 2024-10-22 18:37:45 +08:00
jialin c442a33d5e fix: stream_options spelling 2024-10-22 15:46:35 +08:00
jialin 902bb06353 fix: logs viewer do not scroll after opened 2024-10-22 15:36:53 +08:00
jialin a0a1b8b3e8 chore: openai api error message 2024-10-22 10:17:29 +08:00
jialin eb5cf9da8d fix: logs scrollbar ux 2024-10-22 09:29:03 +08:00
jialin 57386eeb55 style: body and side menu scrollbar style 2024-10-21 11:36:54 +08:00
jialin 33a21f2aaa fix: scrollbar ux 2024-10-20 12:47:14 +08:00
jialin d6fd498489 chore: logs style 2024-10-20 10:40:17 +08:00
jialin 386f2969eb chore: upgrade check 2024-10-18 19:35:47 +08:00
jialin 2fea3a4b40 chore: parse ansi 2024-10-18 15:20:55 +08:00
jialin b9c9fe7a97 fix: model instances limit 2024-10-18 12:09:09 +08:00
jialin 594c4cd670 fix: select same model will clear filename 2024-10-18 10:59:01 +08:00
jialin e10c5e28b5 fix: upload a image can clear other message 2024-10-17 16:06:38 +08:00
jialin fbefb2014d fix: do not clear system message, on clear button 2024-10-17 15:49:41 +08:00
jialin 909da7edb5 style: model tag style 2024-10-17 14:59:42 +08:00
jialin 349676d908 fix: adjust image size when paste 2024-10-17 13:32:38 +08:00
jialin a47f7cc0ce fix: no request, search model 2024-10-15 17:52:02 +08:00
jialin 8fbdb884c7 chore: hide rerank tab 2024-10-14 10:46:32 +08:00
jialin ec68ce082e fix(style): document list 2024-10-14 10:40:13 +08:00
jialin 2b61e3dfd3 fix(style): input text hover 2024-10-14 10:33:00 +08:00
jialin 4c3dddc486 feat: playground rerank 2024-10-14 10:27:46 +08:00
jialin ecc683a6bd fix: cpu_offload is not shown when editting model 2024-10-09 19:23:29 +08:00
jialin 7b2098cf33 fix: playground params do not update after being changed 2024-10-09 15:07:12 +08:00
jialin 3784d86344 chore: update antd 2024-10-09 14:08:27 +08:00
jialin 2b22481443 style: change select to radio group 2024-10-09 14:08:27 +08:00
jialin c041d768b2 style: model readme.md table cell align left 2024-09-30 20:12:01 +08:00
jialin 5df6154abe fix: system message can not sync to others 2024-09-30 19:51:01 +08:00
jialin 1fed02d23f fix: modal title does not upate after change language 2024-09-30 19:10:13 +08:00
jialin 2d34d07da4 fix: language does not refresh after toggling 2024-09-30 18:54:48 +08:00
jialin ea25291d75 fix(style): model readme img render 2024-09-30 16:33:38 +08:00
jialin 5f77a0082c fix: models compare system message sync to all 2024-09-30 11:45:42 +08:00
jialin 3978ec683d style: no available models tips 2024-09-29 20:34:11 +08:00
jialin 458f467985 chore: modelscope downloading state 2024-09-29 18:58:12 +08:00
jialin 69ffdd4604 fix(style): markdown viewer text-intent 2024-09-29 16:54:00 +08:00
jialin 5c2503b2a8 chore: add no available models notes 2024-09-29 11:52:50 +08:00
jialin bc921ff835 fix(style): hide table cell content if too long 2024-09-28 19:23:56 +08:00
jialin 197488bff6 fix: chat error message 2024-09-27 19:19:56 +08:00
jialin e48d21c8cb fix: overlayscrollbars.css 2024-09-27 17:13:53 +08:00
jialin 7e992fad99 fix: update lock.yaml 2024-09-27 17:00:22 +08:00
jialin 49c0467428 fix: query modelscope files by recursive 2024-09-27 16:50:09 +08:00
jialin 7e985991cd fix(style): compare sroll 2024-09-26 19:52:54 +08:00
jialin cc7d2ded6c fix: update model backend not show 2024-09-26 14:04:19 +08:00
jialin f813f56c1f chore: vllm params 2024-09-26 10:30:40 +08:00
jialin a698c65a07 fix: playground edit message 2024-09-25 16:17:25 +08:00
jialin 3cd96ba2e8 feat: vllm support 2024-09-25 16:17:25 +08:00
jialin 0652f850d6 fix: modelscope model name 2024-09-25 16:17:25 +08:00
jialin 3ab29f9e01 feat: modelscope model 2024-09-22 14:55:23 +08:00
jialin b041cdc766 style: playground style 2024-09-22 14:55:23 +08:00
jialin c04b9342b8 refactor: playground add comparsion 2024-09-22 14:55:23 +08:00
jialin 1abaa164f5 refactor: playground chat 2024-09-22 14:55:23 +08:00
jialin 4f4f00f0c8 feat: add models compare 2024-09-22 14:55:23 +08:00
jialin 2e5f0d9210 fix: submit message 2024-09-22 14:55:23 +08:00
jialin af077c4706 style: upload image in message input 2024-09-22 14:55:23 +08:00
Arne Sundandjialin a5cebb88ef Fix typos in Resources table headings 2024-09-22 12:10:25 +08:00
513 changed files with 56006 additions and 10112 deletions
+2
View File
@@ -1,2 +1,4 @@
PORT=9000
UMI_DEV_SERVER_COMPRESS=none
DID_YOU_KNOW=none
+1
View File
@@ -0,0 +1 @@
src/components/icon-font/iconfont/iconfont.js
+7 -1
View File
@@ -3,7 +3,13 @@ module.exports = {
rules: {
'react/no-unstable-nested-components': 1,
'no-unused-vars': 'off',
'no-undef': 'error',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/class-name-casing': 'off'
}
},
globals: {
Global: 'readonly',
React: 'readonly'
},
ignorePatterns: ['public/static/']
};
+18 -8
View File
@@ -4,11 +4,13 @@ on:
push:
branches:
- 'main'
- 'v*-dev'
tags:
- '*.*.*'
pull_request:
branches:
- 'main'
- 'v*-dev'
env:
NODE_VERSION: '21'
@@ -72,11 +74,6 @@ jobs:
if: github.event_name == 'push'
timeout-minutes: 30
runs-on: ubuntu-22.04
strategy:
matrix:
version: ['latest', '${{ github.ref_name }}']
exclude:
- version: 'main'
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -92,10 +89,23 @@ jobs:
${{ github.workspace }}/node_modules
- name: Build
run: scripts/build
- name: Determine Version
id: version
run: |
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
echo "version=latest" >> $GITHUB_OUTPUT
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
elif [[ "${GITHUB_REF}" == refs/heads/v*-dev ]]; then
echo "version=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
else
echo "Not a publish target"
exit 1
fi
- name: Package
run: scripts/package
env:
VERSION: '${{ matrix.version }}'
VERSION: '${{ steps.version.outputs.version }}'
- name: Release
uses: TencentCloud/cos-action@b0aa648235fb35a1bdd6a77f529eb0ac4c2f1c25
with:
@@ -103,7 +113,7 @@ jobs:
secret_key: ${{ secrets.CI_TECENTCOS_SECRET_KEY }}
cos_bucket: ${{ secrets.COS_BUCKET }}
cos_region: ${{ secrets.COS_REGION }}
local_path: dist/${{ matrix.version }}.tar.gz
remote_path: releases/${{ matrix.version }}.tar.gz
local_path: dist/${{ steps.version.outputs.version }}.tar.gz
remote_path: releases/${{ steps.version.outputs.version }}.tar.gz
accelerate: true
clean: false
+1
View File
@@ -12,3 +12,4 @@
/.mfsu
.swc
.DS_Store
.idea
+4 -1
View File
@@ -1 +1,4 @@
npx --no-install lint-staged --quiet
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+4 -8
View File
@@ -1,11 +1,6 @@
{
"*.{md,json}": [
"prettier --cache --write"
],
"*.{js,jsx}": [
"max lint --fix --eslint-only",
"prettier --cache --write"
],
"*.{md,json}": ["prettier --cache --write"],
"*.{js,jsx}": ["max lint --fix --eslint-only", "prettier --cache --write"],
"*.{css,less}": [
"max lint --fix --stylelint-only",
"prettier --cache --write"
@@ -13,5 +8,6 @@
"*.ts?(x)": [
"max lint --fix --eslint-only",
"prettier --cache --parser=typescript --write"
]
],
"src/locales/**/*.ts": ["npx tsx src/locales/check.ts"]
}
+6
View File
@@ -1,3 +1,9 @@
node_modules
.umi
.umi-production
public/static/*.js
public/static/*.css
src/components/icon-font/iconfont/iconfont.js
src/components/icon-font/iconfont/*.css
+2 -1
View File
@@ -2,5 +2,6 @@ module.exports = {
extends: require.resolve('@umijs/max/stylelint'),
rules: {
'selector-class-pattern': null
}
},
ignoreFiles: ['public/static/*.css']
};
+190
View File
@@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright (c) 2024 The GPUStack authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+9
View File
@@ -44,3 +44,12 @@ npm run dev
```
npm run build
```
## Bugs & Issues
- Please submit [bugs and issues](https://github.com/gpustack/gpustack/issues) with a label `ui`
## Links
- Official website: https://gpustack.ai/
- Documents: https://docs.gpustack.ai/latest/overview/
+16 -7
View File
@@ -1,7 +1,7 @@
import { defineConfig } from '@umijs/max';
import keepAlive from './keep-alive';
import proxy from './proxy';
import routes from './routes';
import theme from './theme';
import { getBranchInfo } from './utils';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
@@ -14,11 +14,23 @@ const isProduction = env === 'production';
const t = Date.now();
export default defineConfig({
proxy: {
...proxy('http://192.168.50.166:8080')
...proxy(process.env.PROXY_HOST)
},
history: {
type: 'hash'
},
analyze: {
analyzerMode: 'server',
analyzerPort: 8888,
openAnalyzer: true,
generateStatsFile: false,
statsFilename: 'stats.json',
logLevel: 'info',
defaultSizes: 'parsed' // stat // gzip
},
mfsu: {
exclude: ['lodash', 'ml-pca']
},
base: process.env.npm_config_base || '/',
...(isProduction
? {
@@ -65,17 +77,14 @@ export default defineConfig({
presets: ['umi-presets-pro'],
clickToComponent: {},
antd: {
style: 'less',
configProvider: {
componentSize: 'large',
theme
}
style: 'less'
},
hash: true,
access: {},
model: {},
initialState: {},
request: {},
keepalive: keepAlive,
locale: {
antd: true,
baseNavigator: true,
+6
View File
@@ -0,0 +1,6 @@
export const keepAliveRoutes = {
text2images: '/playground/text-to-image',
speech: '/playground/speech'
};
export default [keepAliveRoutes.text2images, keepAliveRoutes.speech];
+10 -1
View File
@@ -1,4 +1,12 @@
const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai', 'version'];
const proxyTableList = [
'cli',
'v1',
'auth',
'v1-openai',
'version',
'proxy',
'update'
];
// @ts-ingore
export default function createProxyTable(target?: string) {
@@ -10,6 +18,7 @@ export default function createProxyTable(target?: string) {
changeOrigin: true,
secure: false,
ws: true,
log: 'debug',
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
headers: {
origin: newTarget,
+151 -26
View File
@@ -1,58 +1,183 @@
import { keepAliveRoutes } from './keep-alive';
export default [
{
name: 'dashboard',
path: '/dashboard',
key: 'dashboard',
icon: 'AppstoreOutlined',
icon: 'icon-dashboard',
selectedIcon: 'icon-dashboard-filled',
defaultIcon: 'icon-dashboard',
access: 'canSeeAdmin',
component: './dashboard'
component: './dashboard',
routes: []
},
{
name: 'playground',
title: 'Playground',
icon: 'icon-experiment',
selectedIcon: 'icon-experiment-filled',
defaultIcon: 'icon-experiment',
path: '/playground',
key: 'playground',
icon: 'Comment',
component: './playground'
routes: [
{
path: '/playground',
redirect: '/playground/chat'
},
{
name: 'chat',
title: 'Chat',
path: '/playground/chat',
key: 'chat',
icon: 'icon-chat',
selectedIcon: 'icon-chat-filled',
defaultIcon: 'icon-chat',
component: './playground/index'
},
{
name: 'embedding',
title: 'embedding',
path: '/playground/embedding',
key: 'embedding',
icon: 'icon-embedding',
selectedIcon: 'icon-embedding-filled',
defaultIcon: 'icon-embedding',
component: './playground/embedding'
},
{
name: 'rerank',
title: 'Rerank',
path: '/playground/rerank',
key: 'rerank',
icon: 'icon-reranker',
selectedIcon: 'icon-reranker-filled',
defaultIcon: 'icon-reranker',
component: './playground/rerank'
},
{
name: 'text2images',
title: 'Text2Images',
path: keepAliveRoutes.text2images,
key: 'text2images',
icon: 'icon-image1',
selectedIcon: 'icon-image-filled',
defaultIcon: 'icon-image1',
component: './playground/images'
},
{
name: 'speech',
title: 'Speech',
path: keepAliveRoutes.speech,
key: 'speech',
icon: 'icon-audio1',
selectedIcon: 'icon-audio-filled',
defaultIcon: 'icon-audio1',
component: './playground/speech'
}
]
},
{
name: 'models',
path: '/models',
key: 'models',
icon: 'Block',
access: 'canSeeAdmin',
component: './llmodels'
routes: [
{
path: '/models',
redirect: '/models/deployments'
},
{
name: 'modelCatalog',
path: '/models/catalog',
key: 'modelsCatalog',
icon: 'icon-layers',
selectedIcon: 'icon-layers-filled',
defaultIcon: 'icon-layers',
access: 'canSeeAdmin',
component: './llmodels/catalog'
},
{
name: 'deployment',
path: '/models/deployments',
key: 'modelDeployments',
icon: 'icon-rocket-launch1',
selectedIcon: 'icon-rocket-launch-fill',
defaultIcon: 'icon-rocket-launch1',
access: 'canSeeAdmin',
component: './llmodels/index'
}
]
},
{
name: 'resources',
path: '/resources',
key: 'resources',
icon: 'CloudServer',
access: 'canSeeAdmin',
component: './resources'
routes: [
{
path: '/resources',
redirect: '/resources/workers'
},
{
name: 'workers',
path: '/resources/workers',
key: 'workers',
icon: 'icon-resources',
selectedIcon: 'icon-resources-filled',
defaultIcon: 'icon-resources',
component: './resources/components/workers'
},
{
name: 'gpus',
path: '/resources/gpus',
key: 'gpus',
icon: 'icon-gpu1',
selectedIcon: 'icon-gpu-filled',
defaultIcon: 'icon-gpu1',
component: './resources/components/gpus'
},
{
name: 'modelfiles',
path: '/resources/modelfiles',
key: 'modelfiles',
icon: 'icon-files',
selectedIcon: 'icon-files-filled',
defaultIcon: 'icon-files',
component: './resources/components/model-files'
}
]
},
{
name: 'accessControl',
path: '/access-control',
key: 'accessControl',
access: 'canSeeAdmin',
routes: [
{
path: '/access-control',
redirect: '/access-control/users'
},
{
name: 'users',
path: '/access-control/users',
key: 'users',
icon: 'icon-users',
selectedIcon: 'icon-users-filled',
defaultIcon: 'icon-users',
component: './users'
}
]
},
// {
// name: 'usage',
// path: '/usage',
// key: 'usage',
// icon: 'BarChartOutlined',
// component: './usage'
// },
{
name: 'apikeys',
path: '/api-keys',
key: 'apikeys',
icon: 'LockOutlined',
hideInMenu: true,
selectedIcon: 'icon-key-filled',
icon: 'icon-key',
defaultIcon: 'icon-key',
component: './api-keys'
},
{
name: 'users',
path: '/users',
key: 'users',
icon: 'Team',
access: 'canSeeAdmin',
component: './users'
},
{
name: 'profile',
path: '/profile',
@@ -73,7 +198,7 @@ export default [
name: '404',
path: '*',
key: '404',
layout: false,
hideInMenu: true,
component: './404'
}
];
-49
View File
@@ -1,49 +0,0 @@
export default {
'root-entry-name': 'variable',
cssVar: true,
hashed: false,
components: {
Input: {
inputFontSize: 12
},
Table: {
cellFontSize: 12,
rowSelectedHoverBg: 'rgba(230, 230, 230, 0.88)',
rowSelectedBg: 'rgb(244, 245, 244)'
},
Menu: {
iconSize: 16,
iconMarginInlineEnd: 12,
itemHeight: 44,
itemColor: 'rgba(0,0,0,1)',
itemHoverBg: 'rgba(0,0,0,0.04)',
itemActiveBg: 'rgba(0,0,0,0.04)'
},
Progress: {
lineBorderRadius: 4
},
Select: {
itemSelectedBg: 'rgba(0,0,0,0.06)'
},
Message: {
contentPadding: '12px 16px'
},
Slider: {
handleSize: 8,
handleSizeHover: 8,
trackBg: 'rgba(0,0,0,0.15)',
handleColor: 'rgba(0,0,0,0.2)',
trackHoverBg: 'rgba(0,0,0,0.25)',
handleActiveColor: 'rgba(0,0,0,0.3)',
dotActiveBorderColor: 'rgba(0,0,0,0.25)',
handleActiveOutlineColor: 'rgba(0,0,0,0.25)',
dotBorderColor: 'rgba(0,0,0,0.25)'
}
},
token: {
colorPrimary: '#007BFF',
borderRadius: 8,
fontSize: 12,
motion: true
}
};
+27
View File
@@ -1 +1,28 @@
// only for third party libraries styles
.simplebar-scrollbar.simplebar-visible::before {
opacity: 1;
}
.simplebar-scrollbar::before {
background: var(--scrollbar-handle-bg);
width: var(--scrollbar-size);
}
.simplebar-scrollbar.simplebar-visible.simplebar-hover {
&::before {
background: var(--scrollbar-handle-hover-bg);
}
}
.simplebar-scrollbar.scrollbar-handle-light {
&::before {
background-color: var(--scrollbar-handle-light-bg);
}
}
.simplebar-scrollbar.simplebar-visible.simplebar-hover.scrollbar-handle-light {
&::before {
background: var(--scrollbar-handle-light-bg);
}
}
+48 -7
View File
@@ -1,55 +1,88 @@
{
"private": true,
"author": "jialin <jialinkuang@126.com>",
"author": "jialin",
"scripts": {
"build": "max build",
"check:locales": "npx tsx ./src/locales/check.ts",
"dev": "max dev",
"format": "prettier --cache --write .",
"postinstall": "max setup",
"prepare": "husky",
"preview": "max preview",
"setup": "max setup",
"start": "npm run dev"
},
"resolutions": {
"immer": "^9.0.6"
},
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@ant-design/pro-components": "^2.7.1",
"@ant-design/icons": "^5.5.1",
"@ant-design/pro-components": "^2.7.19",
"@braintree/sanitize-url": "^7.1.1",
"@huggingface/gguf": "^0.1.7",
"@huggingface/hub": "^0.15.1",
"@huggingface/tasks": "^0.11.6",
"@monaco-editor/react": "^4.6.0",
"@orcid/bibtex-parse-js": "^0.0.25",
"@react-hook/resize-observer": "^2.0.2",
"@types/lodash": "^4.17.4",
"@umijs/max": "^4.2.11",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"ahooks": "^3.8.5",
"ansi-to-html": "^0.7.2",
"antd": "^5.18.3",
"antd": "^5.21.6",
"antd-style": "^3.6.2",
"axios": "^1.7.2",
"axios": "^1.8.2",
"classnames": "^2.5.1",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
"dompurify": "^3.2.6",
"driver.js": "^1.3.1",
"echarts": "^5.5.1",
"epubjs": "^0.3.93",
"file-saver": "^2.0.5",
"has-ansi": "^5.0.1",
"highlight.js": "^11.10.0",
"jotai": "^2.8.4",
"jszip": "^3.10.1",
"katex": "^0.16.21",
"lamejs": "github:zhuker/lamejs",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"mammoth": "^1.8.0",
"marked": "^14.1.0",
"ml-dataset-iris": "^1.2.1",
"ml-pca": "^4.1.1",
"numeral": "^2.0.6",
"overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6",
"pdfjs-dist": "^4.7.76",
"query-string": "^9.0.0",
"rc-resize-observer": "^1.4.0",
"rc-virtual-list": "^3.14.8",
"re-resizable": "^6.10.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"react-markdown": "^9.0.3",
"rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"simplebar-react": "^3.2.6",
"umi-presets-pro": "^2.0.3"
"styled-components": "^6.1.15",
"umi-presets-pro": "^2.0.3",
"wavesurfer.js": "^7.8.8",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@types/marked": "^6.0.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
"@umijs/plugins": "^4.4.11",
"babel-plugin-named-asset-import": "^0.3.8",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"compression-webpack-plugin": "^11.1.0",
@@ -66,7 +99,15 @@
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-packagejson": "^2.5.0",
"prettier-plugin-two-style-order": "^1.0.1",
"tsx": "^4.19.3",
"typescript": "^5.4.5",
"url-loader": "^4.1.1"
"url-loader": "^4.1.1",
"webpack-bundle-analyzer": "^4.10.2",
"worker-loader": "^3.0.8"
},
"pnpm": {
"overrides": {
"elliptic": "^6.6.1"
}
}
}
+4 -3
View File
@@ -4,11 +4,12 @@ export default (api: IApi) => {
api.modifyHTML(($) => {
const info = JSON.parse(process.env.VERSION || '{}');
const env = process.env.NODE_ENV;
$('html').attr('data-env', env);
$('html').attr(
'data-version',
env === 'production'
? info.version || info.commitId
: `dev-${info.commitId}`
env === 'production' ? info.version || info.commitId : `${info.commitId}`
);
if (env === 'production') {
$('script[src^="/js/umi"]').first?.().remove?.();
+3904 -1783
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+43 -4
View File
@@ -1,14 +1,33 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils';
import { requestConfig } from '@/request-config';
import {
queryCurrentUserState,
queryVersionInfo
queryVersionInfo,
updateCheck
} from '@/services/profile/apis';
import { isOnline } from '@/utils';
import {
IS_FIRST_LOGIN,
readState,
writeState
} from '@/utils/localstore/index';
import { RequestConfig, history } from '@umijs/max';
const loginPath = '/login';
// only for the first login and access from http://localhost
const checkDefaultPage = async (userInfo: any) => {
const isFirstLogin = await readState(IS_FIRST_LOGIN);
if (isFirstLogin === null && isOnline()) {
writeState(IS_FIRST_LOGIN, true);
if (userInfo && userInfo?.is_admin) {
history.push('/models/deployments');
}
}
};
// runtime configuration
export async function getInitialState(): Promise<{
fetchUserInfo: () => Promise<Global.UserInfo>;
@@ -16,11 +35,27 @@ export async function getInitialState(): Promise<{
}> {
const { location } = history;
const getUpdateCheck = async () => {
try {
const data = await updateCheck();
setAtomStorage(UpdateCheckAtom, {
...data
});
return data;
} catch (error) {
console.error('updateCheck error', error);
}
};
const fetchUserInfo = async (): Promise<Global.UserInfo> => {
try {
const data = await queryCurrentUserState({
skipErrorHandler: true
});
if (data.is_admin) {
getUpdateCheck();
}
return data;
} catch (error) {
history.push(loginPath);
@@ -31,7 +66,11 @@ export async function getInitialState(): Promise<{
const getAppVersionInfo = async () => {
try {
const data = await queryVersionInfo();
setAtomStorage(GPUStackVersionAtom, data);
const isProduction = data.version?.indexOf('0.0.0') === -1;
setAtomStorage(GPUStackVersionAtom, {
...data,
isProduction
});
} catch (error) {
console.error('queryVersionInfo error', error);
}
@@ -41,7 +80,7 @@ export async function getInitialState(): Promise<{
if (![loginPath].includes(location.pathname)) {
const userInfo = await fetchUserInfo();
checkDefaultPage(userInfo);
return {
fetchUserInfo,
currentUser: userInfo
Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755020187776" class="icon" viewBox="0 0 1105 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7392" xmlns:xlink="http://www.w3.org/1999/xlink" width="138.125" height="128"><path d="M507.2896 133.44768l130.98496-71.4496v820.9408l-130.97984 69.71392V133.44768z" fill="#F38019" p-id="7393"></path><path d="M479.39072 323.968a724.30592 724.30592 0 0 0-77.056 16.6144C118.35904 417.01376 37.77536 603.8016 67.95776 701.696c30.11584 97.8944 161.03424 263.2192 439.2704 250.19904 0.13824-27.41248 0.13824-57.73824 0-90.89536-200.49408-26.37824-302.67904-88.82688-306.55488-187.40736-5.74464-147.74272 134.1696-216.90368 217.87136-229.0176 16.82432-2.49344 37.66272-5.67808 61.19936-8.6528l-0.27648-111.9488h-0.07168z m185.12384 107.65312c52.1984 5.6832 103.57248 18.69312 147.0464 44.44672-10.65984 5.19168-34.26816 19.73248-70.82496 43.47904l290.49344 66.39104-16.54272-206.58176-78.37184 44.30336c-116.10112-57.8048-205.19936-91.0336-267.29984-99.6864l-4.50048 107.648z" fill="#ADAFB3" p-id="7394"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M140-180v-88.92q0-29.39 15.96-54.43 15.96-25.03 42.66-38.49 59.3-29.08 119.65-43.62Q378.62-420 440-420q21.06 0 42.12 1.89 21.05 1.88 42.11 5.65-1.69 51.46 20.81 96.81Q567.54-270.31 609-240v60H140ZM753.85-72.31l-53.35-53.14v-164.73q-39.11-11.51-63.85-43.97-24.73-32.46-24.73-74.7 0-51.46 36.38-87.84t87.85-36.38q51.46 0 87.66 36.39Q860-460.28 860-408.79q0 39.94-22.42 70.71-22.43 30.77-57.2 44.23l44.23 44.23-53.07 53.2 53.07 53.19-70.76 70.92ZM440-484.62q-57.75 0-98.87-41.12Q300-566.86 300-624.61q0-57.75 41.13-98.88 41.12-41.12 98.87-41.12 57.75 0 98.87 41.12Q580-682.36 580-624.61q0 57.75-41.13 98.87-41.12 41.12-98.87 41.12Zm296.15 93.47q14.7 0 25.04-10.54 10.35-10.54 10.35-25.23 0-14.69-10.35-25.04-10.34-10.35-25.04-10.35-14.69 0-25.23 10.35-10.54 10.35-10.54 25.04t10.54 25.23q10.54 10.54 25.23 10.54Z"/></svg>

After

Width:  |  Height:  |  Size: 934 B

+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
<g fill="#C22E33">
<path d="M7.754 2l.463.41c.343.304.687.607 1.026.915C11.44 5.32 13.3 7.565 14.7 10.149c.072.132.137.268.202.403l.098.203-.108.057-.081-.115-.21-.299-.147-.214c-1.019-1.479-2.04-2.96-3.442-4.145a6.563 6.563 0 00-1.393-.904c-1.014-.485-1.916-.291-2.69.505-.736.757-1.118 1.697-1.463 2.653-.045.123-.092.245-.139.367l-.082.215-.172-.055c.1-.348.192-.698.284-1.049.21-.795.42-1.59.712-2.356.31-.816.702-1.603 1.093-2.39.169-.341.338-.682.5-1.025h.092z"/>
<path d="M8.448 11.822c-1.626.77-5.56 1.564-7.426 1.36C.717 11.576 3.71 4.05 5.18 2.91l-.095.218a4.638 4.638 0 01-.138.303l-.066.129c-.76 1.462-1.519 2.926-1.908 4.53a7.482 7.482 0 00-.228 1.689c-.01 1.34.824 2.252 2.217 2.309.67.027 1.347-.043 2.023-.114.294-.03.587-.061.88-.084.108-.008.214-.021.352-.039l.231-.028z"/>
<path d="M3.825 14.781c-.445.034-.89.068-1.333.108 4.097.39 8.03-.277 11.91-1.644-1.265-2.23-2.97-3.991-4.952-5.522.026.098.084.169.141.239l.048.06c.17.226.348.448.527.67.409.509.818 1.018 1.126 1.578.778 1.42.356 2.648-1.168 3.296-1.002.427-2.097.718-3.18.892-1.03.164-2.075.243-3.119.323z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+169
View File
@@ -6,6 +6,10 @@
margin-bottom: 8px;
}
.m-b-6 {
margin-bottom: 6px;
}
.m-b-5 {
margin-bottom: 5px;
}
@@ -38,6 +42,22 @@
margin-left: 20px;
}
.m-l-40 {
margin-left: 40px;
}
.m-t-16 {
margin-top: 16px;
}
.m-t-10 {
margin-top: 10px;
}
.m-0 {
margin: 0;
}
.m-r-10 {
margin-right: 10px;
}
@@ -66,9 +86,18 @@
padding-left: 6px;
}
.p-l-8 {
padding-left: 8px;
}
.m-t-6 {
margin-top: 6px;
}
.flex {
display: flex;
}
// space-between
.flex-between {
display: flex;
@@ -79,12 +108,27 @@
display: flex;
justify-content: center;
}
.justify-start {
display: flex;
justify-content: flex-start;
}
.justify-end {
display: flex;
justify-content: flex-end;
}
// align-items: center
.flex-center {
display: flex;
align-items: center;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-gap-2 {
display: flex;
gap: 2px;
@@ -98,6 +142,11 @@
opacity: 0.8;
}
.flex-end {
display: flex;
justify-content: flex-end;
}
.flex-column {
display: flex;
flex-direction: column;
@@ -115,14 +164,34 @@
gap: 8px;
}
.gap-20 {
gap: 20px;
}
.gap-6 {
gap: 6px;
}
.line-24 {
line-height: 24px;
}
.relative {
position: relative;
}
.font-size-0 {
font-size: 0;
}
.font-size-12 {
font-size: var(--font-size-base);
}
.font-size-13 {
font-size: 13px;
}
.font-size-14 {
font-size: var(--font-size-middle);
}
@@ -131,6 +200,34 @@
font-size: var(--font-size-large);
}
.font-size-20 {
font-size: 20px;
}
.font-size-18 {
font-size: 18px;
}
.font-size-24 {
font-size: 24px;
}
.font-size-32 {
font-size: 32px;
}
.m-b-0 {
margin-bottom: 0;
}
.m-b-16 {
margin-bottom: 16px;
}
.m-r-16 {
margin-right: 16px;
}
.font-400 {
font-weight: var(--font-weight-normal);
}
@@ -139,6 +236,10 @@
font-weight: var(--font-weight-medium);
}
.font-500 {
font-weight: var(--font-weight-500);
}
.font-700 {
font-weight: var(--font-weight-bold);
}
@@ -150,3 +251,71 @@
.text-tertiary {
color: var(--ant-color-text-tertiary);
}
.tips-desc-list {
list-style-type: decimal;
padding-left: 16px;
}
.gap-16 {
gap: 16px;
}
.color-white-tertiary {
color: var(--color-white-tertiary);
}
.color-white-quaternary {
color: var(--color-white-quaternary);
}
.color-white-light-1 {
color: var(--color-white-light-1);
}
.color-white-light-2 {
color: var(--color-white-light-2);
}
.color-white-light-3 {
color: var(--color-white-light-3);
}
.color-white-light-4 {
color: var(--color-white-light-4);
}
textarea {
padding-inline: 6px 0;
padding-block: 0;
background-color: transparent;
}
textarea::-webkit-scrollbar {
width: var(--scrollbar-size);
}
textarea::-webkit-scrollbar-track {
background-color: transparent;
}
textarea::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 6px;
}
textarea:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 6px;
}
}
.bold-text {
color: var(--ant-color-text);
font-weight: var(--font-weight-medium);
}
.ant-result-extra {
color: var(--ant-color-text-secondary);
}
+9
View File
@@ -0,0 +1,9 @@
.driver-popover {
color: var(--ant-color-text);
.driver-popover-title {
font-size: 16px;
font-weight: var(--font-weight-bold);
color: var(--ant-color-text);
}
}
-4
View File
@@ -1,10 +1,6 @@
.ant-pro-layout {
.ant-pro-sider {
// &.ant-layout-sider {
// background: var(--color-white-1);
// }
.ant-layout-sider-children {
background-color: var(--color-fill-1);
border-inline: none;
border-radius: 0;
// padding-inline: 16px;
+18
View File
@@ -0,0 +1,18 @@
import { atom, getDefaultStore } from 'jotai';
// models expand keys: create, update , delete,
export const modelsExpandKeysAtom = atom<string[]>([]);
export const requestIdAtom = atom<number>(0);
export const setRquestId = () => {
const store = getDefaultStore();
const id = Date.now();
store.set(requestIdAtom, id);
return id;
};
export const getRequestId = () => {
const store = getDefaultStore();
return store.get(requestIdAtom);
};
+17
View File
@@ -0,0 +1,17 @@
import { atom, getDefaultStore } from 'jotai';
export const routeCacheAtom = atom<any>(new Map());
export const setRouteCache = (key: string, value: any) => {
const store = getDefaultStore();
const cache = store.get(routeCacheAtom);
cache.set(key, value);
store.set(routeCacheAtom, cache);
};
export const deleteRouteCache = (key: string) => {
const store = getDefaultStore();
const cache = store.get(routeCacheAtom);
cache.delete(key);
store.set(routeCacheAtom, cache);
};
+54
View File
@@ -0,0 +1,54 @@
import { colorPrimary } from '@/config/theme';
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
type UserSettings = {
theme: 'light' | 'realDark';
mode: 'light' | 'realDark' | 'auto';
colorPrimary: string;
isDarkTheme: boolean;
collapsed: boolean;
};
const defaultSettings: UserSettings = {
theme: 'light',
mode: 'auto',
isDarkTheme: false,
colorPrimary: colorPrimary,
collapsed: false
};
export const getStorageUserSettings = () => {
if (typeof window === 'undefined') return defaultSettings;
try {
const savedSettings = JSON.parse(
localStorage.getItem('userSettings') || '{}'
);
return {
...defaultSettings,
...savedSettings
};
} catch {
return defaultSettings;
}
};
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
...getStorageUserSettings()
});
export const userSettingsHelperAtom = atom(
(get) => get(userSettingsAtom),
(get, set, update: Partial<UserSettings>) => {
const prev = get(userSettingsAtom);
const newSettings = {
...prev,
...(update || {})
};
set(userSettingsAtom, {
...newSettings,
colorPrimary: newSettings.colorPrimary || colorPrimary,
isDarkTheme: newSettings.theme === 'realDark'
});
}
);
+20
View File
@@ -0,0 +1,20 @@
import { getDefaultStore } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
export const tabActiveAtom = atomWithStorage<Map<string, any>>(
'tabActiveStatus',
new Map()
);
export const setActiveStatus = (key: string, value: any) => {
const store = getDefaultStore();
const cache = store.get(tabActiveAtom);
cache.set(key, value);
store.set(tabActiveAtom, cache);
};
export const getActiveStatus = (key: string) => {
const store = getDefaultStore();
const cache = store.get(tabActiveAtom);
return cache.get(key);
};
+9 -1
View File
@@ -6,9 +6,17 @@ export const userAtom = atomWithStorage<any>('userInfo', null);
export const GPUStackVersionAtom = atom<{
version: string;
git_commit: string;
isProduction: boolean;
}>({
version: '',
git_commit: ''
git_commit: '',
isProduction: false
});
export const UpdateCheckAtom = atom<{
latest_version: string;
}>({
latest_version: ''
});
export const initialPasswordAtom = atomWithStorage<string>(
+45
View File
@@ -0,0 +1,45 @@
interface WatchRequest {
id: number;
token: any;
cancel: () => void;
}
declare global {
interface Window {
__GPUSTACK_WATCH_REQUEST_CLEAR__: {
watchIDValue: number;
requestList: WatchRequest[];
};
}
}
window.__GPUSTACK_WATCH_REQUEST_CLEAR__ = {
watchIDValue: 0,
requestList: []
};
export const updateWatchIDValue = () => {
window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue =
window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue + 1;
return window.__GPUSTACK_WATCH_REQUEST_CLEAR__.watchIDValue;
};
export const updateWatchRequest = (watchToken: WatchRequest) => {
window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList.push(watchToken);
};
export const cancelWatchRequest = (n: number) => {
// cancel the before n requests
const requestList = window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList;
for (let i = 0; i < n; i++) {
requestList[i]?.cancel?.();
}
window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList = requestList.slice(n);
};
export const clearWatchRequestId = (id: number) => {
const requestList = window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList;
const newRequestList = requestList.filter((item) => item.id !== id);
window.__GPUSTACK_WATCH_REQUEST_CLEAR__.requestList = newRequestList;
};
+167
View File
@@ -0,0 +1,167 @@
import { WarningFilled } from '@ant-design/icons';
import { Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React from 'react';
import styled from 'styled-components';
import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller';
interface AlertInfoProps {
type: Global.MessageType;
message: React.ReactNode;
rows?: number;
icon?: React.ReactNode;
ellipsis?: boolean;
style?: React.CSSProperties;
contentStyle?: React.CSSProperties;
title?: React.ReactNode;
maxHeight?: number;
overlayScrollerProps?: OverlayScrollerOptions;
}
const useStyles = createStyles(({ token, css }) => {
return {
alertBlockInfo: css`
padding-block: 6px;
padding-inline: 10px 16px;
position: relative;
padding-left: 32px;
text-align: left;
border-radius: ${token.borderRadius}px;
margin: 0;
border: 1px solid transparent;
.ant-typography {
margin-bottom: 0;
}
&.danger {
border-color: ${token.colorErrorBorder};
background-color: ${token.colorErrorBg};
}
&.warning {
border-color: ${token.colorWarningBorder};
background-color: ${token.colorWarningBg};
}
&.transition {
color: ${token.geekblue7};
background: ${token.geekblue1};
border-color: ${token.geekblue3};
}
&.success {
border: 1px solid ${token.colorSuccess};
color: ${token.colorSuccessText};
background: ${token.colorSuccessBg};
.content.success {
font-weight: var(--font-weight-normal);
}
}
.title {
position: absolute;
left: 0;
top: 0;
display: flex;
height: 32px;
padding: 5px 10px;
border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0;
.info-icon {
&.danger {
color: ${token.colorErrorText};
}
&.warning {
color: ${token.colorWarningText};
}
&.transition {
color: ${token.geekblue7};
}
&.success {
color: ${token.colorSuccessText};
}
}
.text {
font-weight: var(--font-weight-bold);
}
}
`
};
});
const TitleWrapper = styled.div`
font-weight: 700;
color: var(--ant-color-text);
`;
const ContentWrapper = styled.div<{ $hasTitle: boolean }>`
word-break: break-word;
color: ${(props) =>
props.$hasTitle
? 'var(--ant-color-text-secondary)'
: 'var(--ant-color-text)'};
font-weight: var(--font-weight-500);
white-space: pre-line;
`;
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
const {
message,
type,
rows = 1,
ellipsis,
style,
title,
contentStyle,
icon,
maxHeight = 86,
overlayScrollerProps = {}
} = props;
const { styles } = useStyles();
return (
<>
{message ? (
<div
className={classNames(styles.alertBlockInfo, type)}
style={{ ...style }}
>
<Typography.Paragraph
ellipsis={
ellipsis ?? {
rows: rows,
tooltip: message
}
}
>
<div className={classNames('title', type)}>
<span className={classNames('info-icon', type)}>
{icon ?? <WarningFilled />}
</span>
</div>
{title && (
<TitleWrapper className="title-text">{title}</TitleWrapper>
)}
<OverlayScroller
maxHeight={maxHeight}
style={{ ...contentStyle }}
{...overlayScrollerProps}
>
<ContentWrapper
$hasTitle={!!title}
className={classNames('content', type)}
>
{message}
</ContentWrapper>
</OverlayScroller>
</Typography.Paragraph>
</div>
) : null}
</>
);
};
export default React.memo(AlertInfo);
+47
View File
@@ -0,0 +1,47 @@
import { WarningOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import React from 'react';
interface AlertInfoProps {
type: 'danger' | 'warning';
message: string;
rows?: number;
icon?: React.ReactNode;
ellipsis?: boolean;
style?: React.CSSProperties;
}
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
const { message, type, rows = 1, ellipsis, style } = props;
return (
<>
{message ? (
<Typography.Paragraph
type={type}
ellipsis={
ellipsis !== undefined
? ellipsis
: {
rows: rows,
tooltip: message
}
}
style={{
textAlign: 'center',
padding: '2px 5px',
borderRadius: 'var(--border-radius-base)',
margin: 0,
backgroundColor: 'var(--ant-color-error-bg)',
...style
}}
>
<WarningOutlined className="m-r-8" />
{message}
</Typography.Paragraph>
) : null}
</>
);
};
export default React.memo(AlertInfo);
+19
View File
@@ -0,0 +1,19 @@
.canvas-wrap {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
width: 100%;
canvas {
display: block;
width: 100%;
image-rendering: crisp-edges;
}
}
.scroller-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
}
+192
View File
@@ -0,0 +1,192 @@
import useResizeObserver from '@/components/logs-viewer/use-size';
import React, { useEffect, useState } from 'react';
import './index.less';
interface AudioAnimationProps {
width: number;
height: number;
maxWidth?: number;
scaleFactor?: number;
maxBarCount?: number;
amplitude?: number;
fixedHeight?: boolean;
analyserData: {
data: Uint8Array;
analyser: any;
};
}
const AudioAnimation: React.FC<AudioAnimationProps> = (props) => {
const {
scaleFactor = 1.2,
maxBarCount = 128,
amplitude = 40,
maxWidth,
fixedHeight = true,
analyserData,
width: initialWidth,
height: initialHeight
} = props;
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const animationId = React.useRef<number>(0);
const isScaled = React.useRef<boolean>(false);
const oscillationOffset = React.useRef(0);
const direction = React.useRef(1);
const scrollerRef = React.useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(initialWidth);
const [height, setHeight] = useState(initialHeight);
const containerRef = React.useRef<any>(null);
const size = useResizeObserver(scrollerRef);
const calculateJitter = (
i: number,
timestamp: number,
baseHeight: number,
minJitter: number,
jitterAmplitude: number
) => {
//
const jitterFactor = Math.sin(timestamp / 200 + i) * 0.5 + 0.5;
const jitter =
minJitter +
jitterFactor * (jitterAmplitude - minJitter) * (baseHeight / maxBarCount);
return jitter;
};
const startAudioVisualization = () => {
if (!canvasRef.current || !analyserData.data?.length) return;
const canvas = canvasRef.current;
const canvasCtx = canvas.getContext('2d');
if (!canvasCtx) return;
const WIDTH = (canvas.width = width * 2);
const HEIGHT = (canvas.height = height * 2);
if (!isScaled.current) {
canvasCtx.scale(2, 2);
isScaled.current = true;
}
const barWidth = 4;
const barSpacing = 6;
const centerLine = Math.floor(HEIGHT / 2);
const jitterAmplitude = amplitude;
const minJitter = 10;
let lastFrameTime = 0;
const gradient = canvasCtx.createLinearGradient(0, 0, 0, HEIGHT);
gradient.addColorStop(0, '#007BFF');
gradient.addColorStop(1, '#0069DA');
canvasCtx.fillStyle = gradient;
const draw = (timestamp: number) => {
const elapsed = timestamp - lastFrameTime;
if (elapsed < 16) {
animationId.current = requestAnimationFrame(draw);
return;
}
lastFrameTime = timestamp;
analyserData.analyser?.current?.getByteFrequencyData(analyserData.data);
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
const barCount = Math.min(maxBarCount, analyserData.data.length);
const totalWidth = barCount * (barWidth + barSpacing) - barSpacing;
let x = WIDTH / 2 - totalWidth / 2 + oscillationOffset.current;
oscillationOffset.current += direction.current;
if (Math.abs(oscillationOffset.current) > 20) {
direction.current *= -1;
}
for (let i = 0; i < barCount; i++) {
const baseHeight = Math.floor(analyserData.data[i] / 2) * scaleFactor;
const jitter = calculateJitter(
i,
timestamp,
baseHeight,
minJitter,
jitterAmplitude
);
const barHeight = baseHeight + Math.round(jitter);
const topY = Math.round(centerLine - barHeight / 2);
const bottomY = Math.round(centerLine + barHeight / 2);
canvasCtx.beginPath();
canvasCtx.moveTo(x, bottomY);
canvasCtx.lineTo(x, topY + 2);
canvasCtx.arcTo(x + barWidth, topY + 2, x + barWidth, bottomY, 2);
canvasCtx.lineTo(x + barWidth, bottomY);
canvasCtx.closePath();
canvasCtx.fill();
x += barWidth + barSpacing;
}
animationId.current = requestAnimationFrame(draw);
};
draw(performance.now());
};
React.useEffect(() => {
if (size) {
if (maxWidth) {
setWidth(Math.min(size.width, maxWidth));
} else {
setWidth(size?.width || 0);
}
if (!fixedHeight) {
setHeight(size?.height || 0);
}
}
}, [size, maxWidth]);
useEffect(() => {
if (!canvasRef.current) return;
const clearCanvas = () => {
if (canvasRef.current) {
const ctx = canvasRef.current.getContext('2d');
if (ctx) ctx.clearRect(0, 0, width * 2, height * 2);
}
};
if (!analyserData.data?.length || !analyserData.analyser?.current) {
clearCanvas();
cancelAnimationFrame(animationId.current);
animationId.current = 0;
return;
}
startAudioVisualization();
return () => {
cancelAnimationFrame(animationId.current);
clearCanvas();
};
}, [analyserData, width, height]);
return (
<div
className="scroller-wrapper"
ref={scrollerRef}
style={{ width: '100%', height: '100%' }}
>
<div
ref={containerRef}
className="canvas-wrap"
style={{ width: '100%', height: '100%' }}
>
<canvas ref={canvasRef} style={{ display: 'block' }}></canvas>
</div>
</div>
);
};
export default React.memo(AudioAnimation);
@@ -0,0 +1,22 @@
import React from 'react';
import styled from 'styled-components';
const AudioWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
`;
const AudioElement: React.FC<any> = (props) => {
return (
<div>
<AudioWrapper>
<audio {...props} controls></audio>
</AudioWrapper>
</div>
);
};
export default AudioElement;
@@ -0,0 +1,39 @@
export type AudioEvent =
| 'play'
| 'playing'
| 'pause'
| 'timeupdate'
| 'ended'
| 'loadedmetadata'
| 'audioprocess'
| 'canplay'
| 'ended'
| 'loadeddata'
| 'seeked'
| 'seeking'
| 'volumechange';
export interface AudioPlayerProps {
controls?: boolean;
autoplay?: boolean;
url: string;
speed?: number;
ref?: any;
height?: number;
width?: number;
duration?: number;
onPlay?: () => void;
onPlaying?: () => void;
onPause?: () => void;
onTimeUpdate?: () => void;
onEnded?: () => void;
onLoadedMetadata?: (duration: number) => void;
onAudioProcess?: (current: number) => void;
onCanPlay?: () => void;
onLoadedData?: () => void;
onSeeked?: () => void;
onSeeking?: () => void;
onVolumeChange?: () => void;
onReady?: (duration: number) => void;
onAnalyse?: (analyseData: any, frequencyBinCount: any) => void;
}
+104
View File
@@ -0,0 +1,104 @@
.player-wrap {
width: 100%;
display: flex;
// background-color: var(--ant-color-fill-quaternary);
border-radius: 6px;
.player-ui {
padding: 8px 16px;
flex: 1;
display: flex;
justify-content: flex-start;
align-items: center;
}
.controls {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.play-btn {
margin-inline: 30px;
height: 22px;
width: 22px;
overflow: hidden;
.ant-btn {
height: 22px;
width: 22px;
}
}
.backward,
.forward {
background: none !important;
}
.slider {
display: flex;
justify-content: center;
align-items: center;
.slider-inner {
flex: 1;
}
}
.play-content {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
}
.time {
width: 52px;
text-align: right;
&.current {
text-align: left;
}
}
.progress-bar {
margin-inline: 10px;
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.slider {
width: 100%;
}
.file-name {
margin-bottom: 6px;
line-height: 20px;
height: 20px;
display: flex;
align-items: center;
align-self: center;
justify-content: center;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.ant-slider-horizontal {
margin-block: 5px;
}
}
.speaker {
margin-left: 10px;
position: relative;
.volume-slider {
position: absolute;
bottom: 30px;
}
}
}
+311
View File
@@ -0,0 +1,311 @@
import { formatTime } from '@/utils/index';
import {
FastBackwardOutlined,
FastForwardOutlined,
PauseCircleFilled,
PlayCircleFilled
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Slider, Tooltip } from 'antd';
import { round } from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle
} from 'react';
import './index.less';
interface AudioPlayerProps {
autoplay?: boolean;
url: string;
speed?: number;
ref?: any;
name: string;
height?: number;
width?: number;
duration?: number;
extra?: React.ReactNode;
}
const speedOptions = [
{ label: '1x', value: 1 },
{ label: '2x', value: 2 },
{ label: '3x', value: 3 },
{ label: '4x', value: 4 }
];
const speedConfig = {
min: 0.5,
max: 2,
step: 0.25
};
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
const intl = useIntl();
const { autoplay = false, speed: defaultSpeed = 1, extra } = props;
const audioRef = React.useRef<HTMLAudioElement>(null);
const [audioState, setAudioState] = React.useState<{
currentTime: number;
duration: number;
}>({
currentTime: 0,
duration: 0
});
const [playOn, setPlayOn] = React.useState<boolean>(false);
const [speakerOn, setSpeakerOn] = React.useState<boolean>(false);
const [volume, setVolume] = React.useState<number>(1);
const [speed, setSpeed] = React.useState<number>(defaultSpeed);
const timer = React.useRef<any>(null);
useImperativeHandle(ref, () => ({
play: () => {
audioRef.current?.play();
},
pause: () => {
audioRef.current?.pause();
}
}));
const handleShowVolume = useCallback(() => {
setSpeakerOn(!speakerOn);
}, [speakerOn]);
const handleSeepdChange = useCallback((value: number | string) => {
setSpeed(value as number);
audioRef.current!.playbackRate = value as number;
}, []);
const handleAudioOnPlay = useCallback(() => {
console.log('audio play');
timer.current = setInterval(() => {
setAudioState((prestate) => {
return {
currentTime: Math.ceil(audioRef.current?.currentTime || 0),
duration:
prestate.duration || Math.ceil(audioRef.current?.duration || 0)
};
});
if (audioRef.current?.paused || audioRef.current?.ended) {
clearInterval(timer.current);
setPlayOn(false);
setAudioState((prestate: any) => {
return {
currentTime: audioRef.current?.ended ? 0 : prestate.currentTime,
duration: prestate.duration
};
});
}
}, 500);
}, []);
const handlePlay = useCallback(() => {
setPlayOn(!playOn);
if (playOn) {
audioRef.current?.pause();
} else {
audioRef.current?.play();
}
}, [playOn]);
const handleFormatVolume = (val?: number) => {
if (val === undefined) {
return `${round(volume * 100)}%`;
}
return `${round(val * 100)}%`;
};
const handleVolumeChange = useCallback((value: number) => {
audioRef.current!.volume = round(value, 2);
setVolume(round(value, 2));
}, []);
const initPlayerConfig = () => {
if (audioRef.current) {
audioRef.current!.volume = volume;
audioRef.current!.playbackRate = speed;
}
};
const handleLoadedMetadata = useCallback(
(data: any) => {
const duration = Math.ceil(audioRef.current?.duration || 0);
setAudioState({
currentTime: 0,
duration:
duration && duration !== Infinity ? duration : props.duration || 0
});
setPlayOn(autoplay);
},
[autoplay, props.duration]
);
const handleCurrentChange = useCallback((val: number) => {
audioRef.current!.currentTime = val;
setAudioState((prestate) => {
return {
currentTime: val,
duration: prestate.duration
};
});
}, []);
const handleReduceSpeed = () => {
setSpeed((pre) => {
if (pre - speedConfig.step < speedConfig.min) {
return speedConfig.min;
}
const next = pre - speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleAddSpeed = () => {
setSpeed((pre) => {
if (pre + speedConfig.step > speedConfig.max) {
return speedConfig.max;
}
const next = pre + speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleOnLoad = (e: any) => {
console.log('onload', e);
};
const onDownload = useCallback(() => {
const url = props.url || '';
const filename = props.name;
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
}, [props.url, props.name]);
useEffect(() => {
if (audioRef.current) {
initPlayerConfig();
}
}, [audioRef.current]);
useEffect(() => {
return () => {
clearInterval(timer.current);
};
}, []);
return (
<div className="player-wrap" style={{ width: props.width || '100%' }}>
<div className="player-ui">
<div className="play-content">
<div className="progress-bar">
<span className="file-name">{props.name}</span>
<div className="slider">
{/* <span className="time current">
{' '}
{formatTime(audioState.currentTime)}
</span> */}
<div className="slider-inner">
<Slider
tooltip={{ open: false }}
min={0}
step={1}
styles={{
rail: {
// height: 6
}
}}
max={audioState.duration}
value={audioState.currentTime}
onChange={handleCurrentChange}
/>
</div>
{/* <span className="time">{formatTime(audioState.duration)}</span> */}
</div>
<div className="controls">
<div className="audio-control flex-center">
<span className="time current">
{' '}
{formatTime(audioState.currentTime)}
</span>
<Tooltip
title={intl.formatMessage({
id: 'playground.audio.button.slow'
})}
>
<Button
type="text"
size="small"
className="backward"
disabled={
speed === speedConfig.min || speed < speedConfig.min
}
onClick={handleReduceSpeed}
>
<FastBackwardOutlined className="font-size-20" />
</Button>
</Tooltip>
<span className="play-btn">
<Button
size="middle"
type="text"
onClick={handlePlay}
disabled={!audioState?.duration}
icon={
!playOn ? (
<PlayCircleFilled
style={{ fontSize: '22px' }}
></PlayCircleFilled>
) : (
<PauseCircleFilled
style={{ fontSize: '22px' }}
></PauseCircleFilled>
)
}
></Button>
</span>
<Tooltip
title={intl.formatMessage({
id: 'playground.audio.button.fast'
})}
>
<Button
type="text"
size="small"
className="forward"
disabled={
speed === speedConfig.max || speed > speedConfig.max
}
onClick={handleAddSpeed}
>
<FastForwardOutlined className="font-size-20" />
</Button>
</Tooltip>
<span className="time">{formatTime(audioState.duration)}</span>
</div>
{extra}
</div>
</div>
</div>
</div>
<audio
crossOrigin="anonymous"
autoPlay={autoplay}
src={props.url}
ref={audioRef}
preload="metadata"
style={{ opacity: 0, position: 'absolute', left: '-9999px' }}
onPlay={handleAudioOnPlay}
onLoadedMetadata={handleLoadedMetadata}
></audio>
</div>
);
});
export default React.memo(AudioPlayer);
@@ -0,0 +1,165 @@
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef
} from 'react';
import { AudioPlayerProps } from './config/type';
const RawAudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
const { autoplay = false } = props;
const audioRef = React.useRef<HTMLAudioElement>(null);
// =================== audio context ======================
const audioContext = useRef<any>(null);
const analyser = useRef<any>(null);
const dataArray = useRef<any>(null);
// ========================================================
const initAudioContext = useCallback(() => {
audioContext.current = new (window.AudioContext ||
window.webkitAudioContext)();
analyser.current = audioContext.current.createAnalyser();
analyser.current.fftSize = 512;
dataArray.current = new Uint8Array(analyser.current.frequencyBinCount);
}, []);
const generateVisualData = useCallback(() => {
const source = audioContext.current.createMediaElementSource(
audioRef.current
);
source.connect(analyser.current);
analyser.current.connect(audioContext.current.destination);
}, []);
const initEnvents = () => {
if (!audioRef.current) {
return;
}
audioRef.current.addEventListener('complete', () => {});
audioRef.current.addEventListener('play', () => {
props.onAnalyse?.(dataArray.current, analyser);
props.onPlay?.();
});
audioRef.current.addEventListener('pause', () => {
props.onAnalyse?.(dataArray.current, analyser);
props.onPause?.();
});
audioRef.current.addEventListener('timeupdate', () => {
props.onTimeUpdate?.();
});
audioRef.current.addEventListener('ended', () => {
props.onEnded?.();
});
// add all other events
audioRef.current.addEventListener('canplay', () => {
props.onCanPlay?.();
});
audioRef.current.addEventListener('loadeddata', () => {
initEnvents();
if (!audioContext.current) {
initAudioContext();
generateVisualData();
}
props.onLoadedData?.();
});
audioRef.current.addEventListener('seeked', () => {
props.onSeeked?.();
});
audioRef.current.addEventListener('seeking', () => {
props.onSeeking?.();
});
audioRef.current.addEventListener('volumechange', () => {
props.onVolumeChange?.();
});
audioRef.current.addEventListener('audioprocess', () => {
const current = audioRef.current?.currentTime || 0;
props.onAudioProcess?.(current);
});
audioRef.current.addEventListener('playing', () => {
props.onPlaying?.();
});
audioRef.current.addEventListener('loadedmetadata', () => {
const duration = audioRef.current?.duration || 0;
props.onLoadedMetadata?.(duration);
props.onReady?.(duration);
});
audioRef.current.addEventListener('ended', () => {
props.onEnded?.();
});
audioRef.current.addEventListener('loadeddata', () => {
props.onLoadedData?.();
});
};
useImperativeHandle(ref, () => ({
play: () => {
audioRef.current?.play();
},
pause: () => {
audioRef.current?.pause();
}
}));
useEffect(() => {
if (audioRef.current) {
console.log('audioRef.current', audioRef.current, props.url);
initEnvents();
}
return () => {
if (audioContext.current) {
audioContext.current.close();
}
// remove all events
audioRef.current?.removeEventListener('play', () => {});
audioRef.current?.removeEventListener('pause', () => {});
audioRef.current?.removeEventListener('timeupdate', () => {});
audioRef.current?.removeEventListener('ended', () => {});
audioRef.current?.removeEventListener('canplay', () => {});
audioRef.current?.removeEventListener('loadeddata', () => {});
audioRef.current?.removeEventListener('seeked', () => {});
audioRef.current?.removeEventListener('seeking', () => {});
audioRef.current?.removeEventListener('volumechange', () => {});
audioRef.current?.removeEventListener('audioprocess', () => {});
audioRef.current?.removeEventListener('playing', () => {});
audioRef.current?.removeEventListener('loadedmetadata', () => {});
audioRef.current?.removeEventListener('ended', () => {});
audioRef.current?.removeEventListener('loadeddata', () => {});
};
}, [audioRef.current]);
return (
<audio
controls
autoPlay={autoplay}
src={props.url}
ref={audioRef}
style={{
position: 'absolute',
left: '-9999px',
opacity: 0
}}
preload="metadata"
></audio>
);
});
export default RawAudioPlayer;
@@ -0,0 +1,406 @@
import { formatTime } from '@/utils/index';
import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Dropdown, Slider, type MenuProps } from 'antd';
import { createStyles } from 'antd-style';
import { round } from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo
} from 'react';
import styled from 'styled-components';
import AutoTooltip from '../auto-tooltip';
import IconFont from '../icon-font';
type ActionItem = 'download' | 'delete' | 'speed';
interface AudioPlayerProps {
autoplay?: boolean;
url: string;
speed?: number;
ref?: any;
name: string;
height?: number;
width?: number;
duration?: number;
actions?: ActionItem[];
onDelete?: () => void;
}
const SliderWrapper = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
.ant-slider {
flex: 1;
}
.time {
color: var(--ant-color-text-tertiary);
}
`;
const useStyles = createStyles(({ css, token }) => {
// @ts-ignore
const isDarkMode = token.darkMode as boolean;
return {
wrapper: css`
position: relative;
min-width: 360px;
height: 54px;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 8px 10px;
background-color: ${isDarkMode
? 'var(--ant-color-fill-secondary)'
: '#F1F3F4'};
border-radius: 28px;
.inner {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1;
gap: 8px;
.slider {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
.ant-slider {
margin: 0;
}
&:hover {
.ant-slider-handle {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
}
&:focus-within {
.ant-slider-handle {
opacity: 1;
}
}
}
.ant-slider-handle {
opacity: 0;
&::before {
background-color: var(--ant-color-bg-spotlight);
border-radius: 50%;
}
&::after {
display: none;
}
}
}
`
};
});
const sliderStyles = {
rail: {
borderRadius: '4px',
backgroundColor: 'var(--ant-color-fill-secondary)'
},
track: {
borderRadius: '4px',
backgroundColor: 'var(--ant-color-bg-spotlight)'
}
};
const speedOptions = [
{ label: '1x', value: 1 },
{ label: '2x', value: 2 },
{ label: '3x', value: 3 },
{ label: '4x', value: 4 }
];
const speedConfig = {
min: 0.5,
max: 2,
step: 0.25
};
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
const intl = useIntl();
const { styles } = useStyles();
const {
autoplay = false,
speed: defaultSpeed = 1,
actions = ['delete'],
name,
onDelete
} = props;
const audioRef = React.useRef<HTMLAudioElement>(null);
const [audioState, setAudioState] = React.useState<{
currentTime: number;
duration: number;
}>({
currentTime: 0,
duration: 0
});
console.log('audioState', name);
const [playOn, setPlayOn] = React.useState<boolean>(false);
const [speakerOn, setSpeakerOn] = React.useState<boolean>(false);
const [volume, setVolume] = React.useState<number>(1);
const [speed, setSpeed] = React.useState<number>(defaultSpeed);
const timer = React.useRef<any>(null);
useImperativeHandle(ref, () => ({
play: () => {
audioRef.current?.play();
},
pause: () => {
audioRef.current?.pause();
}
}));
const handleShowVolume = useCallback(() => {
setSpeakerOn(!speakerOn);
}, [speakerOn]);
const handleSeepdChange = useCallback((value: number | string) => {
setSpeed(value as number);
audioRef.current!.playbackRate = value as number;
}, []);
const handleAudioOnPlay = useCallback(() => {
timer.current = setInterval(() => {
setAudioState((prestate) => {
return {
currentTime: Math.ceil(audioRef.current?.currentTime || 0),
duration:
prestate.duration || Math.ceil(audioRef.current?.duration || 0)
};
});
if (audioRef.current?.paused || audioRef.current?.ended) {
clearInterval(timer.current);
setPlayOn(false);
setAudioState((prestate: any) => {
return {
currentTime: audioRef.current?.ended ? 0 : prestate.currentTime,
duration: prestate.duration
};
});
}
}, 500);
}, []);
const handlePlay = useCallback(() => {
setPlayOn(!playOn);
if (playOn) {
audioRef.current?.pause();
} else {
audioRef.current?.play();
}
}, [playOn]);
const handleFormatVolume = (val?: number) => {
if (val === undefined) {
return `${round(volume * 100)}%`;
}
return `${round(val * 100)}%`;
};
const handleVolumeChange = useCallback((value: number) => {
audioRef.current!.volume = round(value, 2);
setVolume(round(value, 2));
}, []);
const initPlayerConfig = () => {
if (audioRef.current) {
audioRef.current!.volume = volume;
audioRef.current!.playbackRate = speed;
}
};
const handleLoadedMetadata = useCallback(
(data: any) => {
const duration = Math.ceil(audioRef.current?.duration || 0);
setAudioState({
currentTime: 0,
duration:
duration && duration !== Infinity ? duration : props.duration || 0
});
setPlayOn(autoplay);
},
[autoplay, props.duration]
);
const handleCurrentChange = useCallback((val: number) => {
audioRef.current!.currentTime = val;
setAudioState((prestate) => {
return {
currentTime: val,
duration: prestate.duration
};
});
}, []);
const handleReduceSpeed = () => {
setSpeed((pre) => {
if (pre - speedConfig.step < speedConfig.min) {
return speedConfig.min;
}
const next = pre - speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleAddSpeed = () => {
setSpeed((pre) => {
if (pre + speedConfig.step > speedConfig.max) {
return speedConfig.max;
}
const next = pre + speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleOnLoad = (e: any) => {
console.log('onload', e);
};
const onDownload = useCallback(() => {
const url = props.url || '';
const filename = props.name;
const link = document.createElement('a');
link.href = url;
link.download = filename || 'audio.mp3'; // Default filename
document.body.appendChild(link);
link.click();
link.remove();
}, [props.url, props.name]);
const items: MenuProps['items'] = useMemo(() => {
return [
{
key: 'download',
label: intl.formatMessage({ id: 'common.button.download' }),
icon: <DownloadOutlined />,
onClick: onDownload
},
{
key: 'speed',
label: intl.formatMessage({ id: 'playground.params.speed' }),
icon: <IconFont type="icon-play-speed"></IconFont>,
children: speedOptions.map((item) => ({
key: item.value,
label: item.label,
onClick: () => handleSeepdChange(item.value)
}))
},
{
key: 'delete',
label: intl.formatMessage({ id: 'common.button.delete' }),
icon: <DeleteOutlined />,
danger: true,
onClick: () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.src = '';
audioRef.current.load();
}
setAudioState({ currentTime: 0, duration: 0 });
setPlayOn(false);
onDelete?.();
}
}
].filter((item) => actions.includes(item.key as ActionItem));
}, [actions, intl, onDownload, onDelete, handleSeepdChange]);
useEffect(() => {
if (audioRef.current) {
initPlayerConfig();
}
}, [audioRef.current]);
useEffect(() => {
return () => {
clearInterval(timer.current);
};
}, []);
return (
<div
className={styles.wrapper}
style={{
width: props.width || '100%',
height: props.height || '60px',
position: 'relative'
}}
>
<div className="inner">
<Button
size="middle"
type="text"
onClick={handlePlay}
shape="circle"
disabled={!audioState?.duration}
icon={
!playOn ? (
<IconFont
type="icon-playcircle-fill"
style={{ fontSize: '24px' }}
></IconFont>
) : (
<IconFont
type="icon-stopcircle-fill"
style={{ fontSize: '24px' }}
></IconFont>
)
}
></Button>
<div className="slider">
<div className="flex-center flex-between file-name">
<AutoTooltip ghost maxWidth={200}>
<span>{name}</span>
</AutoTooltip>
</div>
<SliderWrapper>
<span className="time">{formatTime(audioState.currentTime)}</span>
<Slider
tooltip={{ open: false }}
min={0}
step={1}
styles={sliderStyles}
max={audioState.duration}
value={audioState.currentTime}
onChange={handleCurrentChange}
/>
</SliderWrapper>
</div>
<Dropdown menu={{ items }} trigger={['click']}>
<Button
icon={<IconFont type="icon-more"></IconFont>}
type="text"
size="middle"
shape="circle"
></Button>
</Dropdown>
</div>
<audio
crossOrigin="anonymous"
autoPlay={autoplay}
src={props.url}
ref={audioRef}
preload="metadata"
style={{ opacity: 0, position: 'absolute', left: '-9999px' }}
onPlay={handleAudioOnPlay}
onLoadedMetadata={handleLoadedMetadata}
></audio>
</div>
);
});
export default React.memo(AudioPlayer);
+21
View File
@@ -0,0 +1,21 @@
.toolbar-wrapper {
padding: 0 24px;
color: rgba(255, 255, 255, 65%);
font-size: 16px;
background-color: rgba(0, 0, 0, 10%);
border-radius: 100px;
}
.toolbar-wrapper .anticon {
padding: 12px;
cursor: pointer;
}
.toolbar-wrapper .anticon[disabled] {
cursor: not-allowed;
opacity: 0.3;
}
.toolbar-wrapper .anticon:hover {
opacity: 0.3;
}
+130
View File
@@ -0,0 +1,130 @@
import fallbackImg from '@/assets/images/img_fallback.png';
import {
DownloadOutlined,
EyeOutlined,
RotateLeftOutlined,
RotateRightOutlined,
SwapOutlined,
UndoOutlined,
ZoomInOutlined,
ZoomOutOutlined
} from '@ant-design/icons';
import { Image as AntImage, ImageProps, Space } from 'antd';
import { round } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import './index.less';
const AutoImage: React.FC<
ImageProps & {
height: number | string;
width?: number | string;
autoSize?: boolean;
preview?: boolean;
onLoad?: () => void;
}
> = (props) => {
const { height = 100, width: w, autoSize, preview = true, ...rest } = props;
const [width, setWidth] = useState(w || 0);
const [isError, setIsError] = useState(false);
const getImgRatio = useCallback((url: string): Promise<{ ratio: number }> => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
resolve({ ratio: round(img.width / img.height, 2) });
};
img.onerror = () => {
resolve({ ratio: 1 });
};
img.src = url;
});
}, []);
const handleOnLoad = useCallback(async () => {
if (autoSize) {
return;
}
const { ratio } = await getImgRatio(props.src || '');
if (typeof height === 'number') {
setWidth(height * ratio);
} else {
throw new Error('Height must be a number');
}
}, [getImgRatio, height, props.src]);
const onDownload = useCallback(() => {
const url = props.src || '';
const filename = Date.now() + '';
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
}, [props.src]);
const handleImgLoad = useCallback(() => {
props.onLoad?.();
setIsError(false);
}, [props.onLoad]);
const handleOnError = useCallback(() => {
setIsError(true);
}, []);
useEffect(() => {
handleOnLoad();
}, [handleOnLoad]);
useEffect(() => {
setWidth(w || 0);
}, [w]);
return (
<AntImage
{...rest}
height={isError ? 'auto' : height}
width={isError ? '100%' : width}
onError={handleOnError}
onLoad={handleImgLoad}
fallback={fallbackImg}
crossOrigin="anonymous"
preview={
preview &&
!isError && {
mask: <EyeOutlined />,
toolbarRender: (
_,
{
transform: { scale },
actions: {
onFlipY,
onFlipX,
onRotateLeft,
onRotateRight,
onZoomOut,
onZoomIn,
onReset
}
}
) => (
<Space size={12} className="toolbar-wrapper">
<DownloadOutlined onClick={onDownload} />
<SwapOutlined rotate={90} onClick={onFlipY} />
<SwapOutlined onClick={onFlipX} />
<RotateLeftOutlined onClick={onRotateLeft} />
<RotateRightOutlined onClick={onRotateRight} />
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
<UndoOutlined onClick={onReset} />
</Space>
)
}
}
/>
);
};
export default React.memo(AutoImage);
@@ -0,0 +1,44 @@
.img-wrapper {
position: relative;
display: inline-block;
}
.img-wrapper .auto-image {
display: block;
}
.img-wrapper .progress-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.progress-square {
width: 100%;
height: 100%;
transform: rotate(0deg);
}
.progress-square-bg {
fill: none;
}
.progress-square-fg {
fill: none;
stroke-linecap: square;
stroke-dasharray: 400;
stroke-dashoffset: 400;
transition: stroke-dashoffset 0.3s ease;
}
.progress-text {
position: absolute;
color: black;
font-size: 20px;
font-weight: bold;
}
+144
View File
@@ -0,0 +1,144 @@
.thumb-img {
position: relative;
display: flex;
max-width: 100%;
max-height: 100%;
justify-content: center;
align-items: center;
border-radius: var(--border-radius-base);
overflow: hidden;
.label {
position: absolute;
top: 4px;
left: 4px;
height: 20px;
line-height: 20px;
border-radius: 12px;
padding: 0 8px;
background-color: var(--ant-geekblue-1);
z-index: 10;
transform: scale(0.9);
}
.progress-wrapper {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
}
.small-progress-wrap {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 30%);
.ant-progress-text {
color: var(--color-white-secondary);
}
}
.img {
display: flex;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
overflow: hidden;
border-radius: var(--border-radius-base);
cursor: pointer;
justify-content: center;
align-items: center;
}
.del {
position: absolute;
top: -4px;
right: -2px;
font-size: var(--font-size-middle);
cursor: pointer;
background-color: var(--color-white-1);
display: none;
border-radius: 50%;
height: 16px;
width: 16px;
overflow: hidden;
pointer-events: all;
}
&:hover {
.ant-image .ant-image-mask {
opacity: 1;
transition: opacity var(--ant-motion-duration-slow);
}
.del {
display: flex;
justify-content: center;
align-items: center;
}
}
}
.single-image {
// height: 100%;
width: inherit;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: var(--border-radius-base);
&.loading {
width: 100%;
height: 100%;
}
&.auto-bg-color {
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
filter: blur(100px);
backdrop-filter: blur(100px);
z-index: 5;
}
.thumb-img {
position: relative;
z-index: 10;
border-radius: 0;
.img {
border-radius: 0;
}
}
.ant-image {
border-radius: 0;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
bottom: 0;
right: 0;
z-index: 1;
}
}
}
+199
View File
@@ -0,0 +1,199 @@
import { CloseCircleOutlined } from '@ant-design/icons';
import { Progress, ProgressProps } from 'antd';
import classNames from 'classnames';
import { round } from 'lodash';
import ResizeObserver from 'rc-resize-observer';
import React, { useCallback } from 'react';
import AutoImage from './index';
import './single-image.less';
interface SingleImageProps {
loading?: boolean;
width?: number;
height?: number;
progress?: number;
maxHeight?: number;
maxWidth?: number;
dataUrl: string;
label?: React.ReactNode;
uid: number;
preview?: boolean;
autoSize?: boolean;
onDelete: (uid: number) => void;
onClick?: (item: any) => void;
autoBgColor?: boolean;
editable?: boolean;
style?: React.CSSProperties;
loadingSize?: ProgressProps['size'];
progressType?: 'line' | 'circle' | 'dashboard';
progressColor?: string;
progressWidth?: number;
}
const SingleImage: React.FC<SingleImageProps> = (props) => {
const {
editable,
onDelete: handleOnDelete,
onClick,
autoSize,
uid,
loading,
width,
height,
progress,
maxHeight,
maxWidth,
dataUrl = '',
label,
style,
autoBgColor,
preview = true,
loadingSize = 'default'
} = props;
const imgWrapper = React.useRef<HTMLSpanElement>(null);
const [imgSize, setImgSize] = React.useState({
width: width,
height: height
});
const thumImgWrapStyle = React.useMemo(() => {
return loading ? { width: '100%', height: '100%' } : {};
}, [loading, imgSize]);
const handleOnClick = useCallback(() => {
onClick?.(props);
}, [onClick, props]);
const handleResize = useCallback(
(size: { width: number; height: number }) => {
if (!autoSize || !size.width || !size.height) return;
const { width: containerWidth, height: containerHeight } = size;
const { width: originalWidth, height: originalHeight } = props;
if (!originalWidth || !originalHeight) return;
const widthRatio = containerWidth / originalWidth;
const heightRatio = containerHeight / originalHeight;
const scale = Math.min(widthRatio, heightRatio, 1);
const newWidth = originalWidth * scale;
const newHeight = originalHeight * scale;
if (newWidth === imgSize.width && newHeight === imgSize.height) {
return;
}
setImgSize({
width: newWidth,
height: newHeight
});
},
[autoSize, props.width, props.height]
);
const handleOnLoad = React.useCallback(async () => {}, []);
return (
<ResizeObserver onResize={handleResize}>
<div
style={{ ...style }}
key={uid}
className={classNames('single-image', {
'auto-bg-color': autoBgColor,
'auto-size': autoSize,
loading: loading
})}
>
{autoBgColor && (
<div
className="mask"
style={{
background: `url(${dataUrl}) center center / cover no-repeat`
}}
></div>
)}
<span
className="thumb-img"
style={{ ...thumImgWrapStyle }}
ref={imgWrapper}
>
<>
{label && <div className="label">{label}</div>}
{loading ? (
<span
className="progress-wrap"
style={{
width: '100%',
height: '100%',
display: 'flex',
border: '1px solid var(--ant-color-split)',
borderRadius: 'var(--border-radius-base)',
justifyContent: 'center',
alignItems: 'center',
padding: '10px',
overflow: 'hidden'
}}
>
<Progress
percent={round(progress, 0)}
type="dashboard"
size={loadingSize}
steps={{ count: 50, gap: 2 }}
format={() => (
<span className="font-size-20">{round(progress, 0)}%</span>
)}
trailColor="var(--ant-color-fill-secondary)"
/>
</span>
) : (
<span
onClick={handleOnClick}
className="img"
style={{
maxHeight: `min(${maxHeight}, 100%)`,
maxWidth: `min(${maxWidth}, 100%)`
}}
>
<AutoImage
style={{ objectFit: 'cover' }}
preview={preview}
autoSize={autoSize}
src={dataUrl}
width={imgSize.width || '100%'}
height={imgSize.height || 100}
onLoad={handleOnLoad}
/>
{progress && progress < 100 && (
<span className="small-progress-wrap">
<Progress
percent={round(progress, 0)}
type="dashboard"
size="small"
steps={{ count: 25, gap: 3 }}
format={() => (
<span className="font-size-12">
{round(progress, 0)}%
</span>
)}
strokeColor="var(--color-white-secondary)"
trailColor="var(--ant-color-fill-secondary)"
/>
</span>
)}
</span>
)}
</>
{editable && (
<span className="del" onClick={() => handleOnDelete(uid)}>
<CloseCircleOutlined />
</span>
)}
</span>
</div>
</ResizeObserver>
);
};
export default SingleImage;
+83 -15
View File
@@ -1,5 +1,6 @@
import { Tag, Tooltip } from 'antd';
import debounce from 'lodash/debounce';
import { CloseOutlined } from '@ant-design/icons';
import { Tag, Tooltip, type TagProps } from 'antd';
import { throttle } from 'lodash';
import React, {
useCallback,
useEffect,
@@ -7,46 +8,83 @@ import React, {
useRef,
useState
} from 'react';
import styled from 'styled-components';
import { TooltipOverlayScroller } from '../overlay-scroller';
interface AutoTooltipProps extends React.ComponentProps<typeof Tag> {
// type TagProps = React.ComponentProps<typeof Tag>;
interface AutoTooltipProps extends Omit<TagProps, 'title'> {
children: React.ReactNode;
maxWidth?: number | string;
minWidth?: number | string;
color?: string;
style?: React.CSSProperties;
ghost?: boolean;
title?: React.ReactNode;
showTitle?: boolean;
closable?: boolean;
radius?: number | string;
filled?: boolean;
tooltipProps?: React.ComponentProps<typeof Tooltip>;
}
const StyledTag = styled(Tag)`
&.tag-filled {
border: none;
background-color: var(--ant-color-fill-secondary);
}
`;
const AutoTooltip: React.FC<AutoTooltipProps> = ({
children,
maxWidth = '100%',
minWidth,
ghost = false,
title,
showTitle = false,
tooltipProps,
radius = 12,
filled = false,
...tagProps
}) => {
const contentRef = useRef<HTMLDivElement>(null);
const [isOverflowing, setIsOverflowing] = useState(false);
const resizeObserver = useRef<ResizeObserver | null>(null);
const checkOverflow = useCallback(() => {
if (contentRef.current) {
const { scrollWidth, clientWidth } = contentRef.current;
setIsOverflowing(scrollWidth > clientWidth);
}
}, []);
const debouncedCheckOverflow = useMemo(
() => debounce(checkOverflow, 200),
[checkOverflow]
);
}, [contentRef.current]);
useEffect(() => {
const element = contentRef.current;
if (!element) return;
resizeObserver.current?.disconnect();
resizeObserver.current = new ResizeObserver(() => {
checkOverflow();
});
resizeObserver.current?.observe(element);
// Initial check
checkOverflow();
return () => {
resizeObserver.current?.disconnect();
resizeObserver.current = null;
};
}, [checkOverflow]);
useEffect(() => {
const debouncedCheckOverflow = throttle(checkOverflow, 200);
window.addEventListener('resize', debouncedCheckOverflow);
return () => {
window.removeEventListener('resize', debouncedCheckOverflow);
debouncedCheckOverflow.cancel();
};
}, [checkOverflow, debouncedCheckOverflow]);
}, [checkOverflow]);
useEffect(() => {
checkOverflow();
@@ -55,6 +93,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
const tagStyle = useMemo(
() => ({
maxWidth,
minWidth,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap' as const,
@@ -64,17 +103,46 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
);
return (
<Tooltip title={isOverflowing || showTitle ? children : ''}>
<TooltipOverlayScroller
toolTipProps={{
...tooltipProps,
destroyTooltipOnHide: false
}}
title={isOverflowing || showTitle ? title || children : false}
>
{ghost ? (
<div ref={contentRef} style={tagStyle}>
<div ref={contentRef} style={tagStyle} data-overflow={isOverflowing}>
{children}
</div>
) : (
<Tag {...tagProps} ref={contentRef} style={tagStyle}>
<StyledTag
{...tagProps}
className={`${tagProps.className || ''} ${filled ? 'tag-filled' : ''}`}
ref={contentRef}
style={{
...tagStyle,
paddingInline: tagProps.closable ? '8px 22px' : 8,
borderRadius: radius
}}
closeIcon={
tagProps.closable ? (
<CloseOutlined
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)'
}}
/>
) : (
false
)
}
>
{children}
</Tag>
</StyledTag>
)}
</Tooltip>
</TooltipOverlayScroller>
);
};
+28
View File
@@ -0,0 +1,28 @@
import { OverlayScroller } from '@/components/overlay-scroller';
import React from 'react';
interface TitleTipProps {
isOverflowing: boolean;
showTitle: boolean;
title: React.ReactNode;
children: React.ReactNode;
}
const TitleTip: React.FC<TitleTipProps> = (props) => {
const { isOverflowing, showTitle, title, children } = props;
return (
<OverlayScroller maxHeight={200}>
<div
style={{
width: 'fit-content',
maxWidth: 'var(--width-tooltip-max)'
}}
>
{isOverflowing || showTitle ? title || children : ''}
</div>
</OverlayScroller>
);
};
export default React.memo(TitleTip);
+47
View File
@@ -0,0 +1,47 @@
import bibtexParse from '@orcid/bibtex-parse-js';
import { Typography } from 'antd';
import React from 'react';
/*
@inproceedings{Lysenko:2010:GMC:1839778.1839781,\
author = {Lysenko, Mikola and Nelaturi, Saigopal and Shapiro, Vadim},\
title = {Group morphology with convolution algebras},\
booktitle = {Proceedings of the 14th ACM Symposium on Solid and Physical Modeling},\
series = {SPM '10},\
year = {2010},\
isbn = {978-1-60558-984-8},\
location = {Haifa, Israel},\
pages = {11--22},\
numpages = {12},\
url = {http://doi.acm.org/10.1145/1839778.1839781},\
doi = {10.1145/1839778.1839781},\
acmid = {1839781},\
publisher = {ACM},\
address = {New York, NY, USA},\
}
*/
const BibTeXViewer: React.FC<{ data: string }> = ({ data }) => {
if (!data) {
return null;
}
const dataList = bibtexParse.toJSON(data);
return (
<ol>
{dataList.map((item: any, index: number) => (
<li key={index} style={{ lineHeight: 2 }}>
<Typography.Link href={item.entryTags?.url} target="_blank">
{item.entryTags?.title}.{' '}
</Typography.Link>
<Typography.Text>{item.entryTags?.author}. </Typography.Text>
<Typography.Text>[{item.entryTags?.year}] </Typography.Text>
{item.entryTags?.journal && (
<Typography.Text>.({item.entryTags?.journal})</Typography.Text>
)}
</li>
))}
</ol>
);
};
export default BibTeXViewer;
+46
View File
@@ -0,0 +1,46 @@
import { DoubleRightOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import React from 'react';
import styled from 'styled-components';
interface MoreButtonProps {
show: boolean;
loadMore: () => void;
loading?: boolean;
}
const MoreWrapper = styled.div`
display: flex;
justify-content: center;
margin-block: 16px;
opacity: 1;
transition: opacity 0.3s;
&.loading {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
`;
const MoreButton: React.FC<MoreButtonProps> = (props) => {
const { show, loading, loadMore } = props;
const intl = useIntl();
return (
<>
{show ? (
<MoreWrapper className={loading ? 'loading' : ''}>
<Button
onClick={loadMore}
size="middle"
type="text"
icon={<DoubleRightOutlined rotate={90} />}
>
{intl.formatMessage({ id: 'common.button.more' })}
</Button>
</MoreWrapper>
) : null}
</>
);
};
export default MoreButton;
+1 -1
View File
@@ -3,5 +3,5 @@
background-color: var(--color-white-1);
box-shadow: none;
padding: 10px;
border: 1px solid var(--color-border-1);
border: 1px solid var(--ant-color-border);
}
+109
View File
@@ -0,0 +1,109 @@
import { createStyles } from 'antd-style';
import React from 'react';
import styled from 'styled-components';
const SimpleCardItemWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
gap: 16px;
`;
const useStyles = createStyles(({ css, token }) => ({
wrapper: css`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: ${token.colorBgContainer};
border-radius: ${token.borderRadius}px;
padding: ${token.padding}px;
justify-content: center;
align-items: center;
gap: ${token.padding}px;
&.bordered {
border: 1px solid ${token.colorBorder};
}
.title {
font-size: ${token.fontSize}px;
font-weight: var(--font-weight-medium);
}
.content {
display: flex;
justify-content: center;
align-items: center;
font-size: ${token.fontSize}px;
color: ${token.colorTextSecondary};
gap: 8px;
.icon {
display: inline-block;
width: 10px;
height: 10px;
gap: 10px;
&.roundRect {
border-radius: 2px;
}
&.circle {
border-radius: 50%;
}
}
}
`
}));
export const SimpleCardItem: React.FC<{
title?: string;
content?: React.ReactNode;
style?: React.CSSProperties;
bordered?: boolean;
color?: string;
iconType?: string;
}> = (props) => {
const { styles, cx } = useStyles();
const { title, content, style, bordered, iconType, color } = props;
return (
<div className={cx({ bordered: bordered }, styles.wrapper)} style={style}>
<div className="title">{title}</div>
<div className="content">
<span
className={cx([iconType], 'icon')}
style={{
backgroundColor: color || 'transparent'
}}
></span>
<span>{content}</span>
</div>
</div>
);
};
export const SimpleCard: React.FC<{
dataList: {
label: string;
value: React.ReactNode;
color: string;
iconType: string;
}[];
height?: string | number;
bordered?: boolean;
}> = (props) => {
const { dataList, bordered } = props;
return (
<SimpleCardItemWrapper style={{ height: props.height || '100%' }}>
{dataList.map((item, index) => (
<SimpleCardItem
key={index}
title={item.label}
content={item.value}
bordered={bordered}
color={item.color}
iconType={item.iconType}
></SimpleCardItem>
))}
</SimpleCardItemWrapper>
);
};
+43
View File
@@ -0,0 +1,43 @@
import { Button } from 'antd';
import React from 'react';
interface CheckButtonsProps {
options: Global.BaseOption<string | number>[];
onChange: (value: string | number) => void;
cancelable?: boolean;
size?: 'small' | 'middle' | 'large';
type?: 'text' | 'primary' | 'default' | 'dashed' | 'link' | undefined;
}
const CheckButtons: React.FC<CheckButtonsProps> = (props) => {
const [type, setType] = React.useState(props.type || 'text');
const [active, setActive] = React.useState<string | number | null>(null);
const handleChange = (value: string | number) => {
props.onChange(value);
if (props.cancelable && active === value) {
setActive(null);
} else {
setActive(value);
}
};
return (
<div className="flex-center gap-6">
{props.options?.map?.((option, index) => {
return (
<Button
size={props.size}
key={option.value}
onClick={() => handleChange(option.value)}
variant="filled"
color={active === option.value ? 'default' : undefined}
type={type}
>
{option.label}
</Button>
);
})}
</div>
);
};
export default React.memo(CheckButtons);
+30 -4
View File
@@ -2,25 +2,41 @@ import { CheckCircleFilled, CopyOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip, message } from 'antd';
import ClipboardJS from 'clipboard';
import { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
type CopyButtonProps = {
children?: React.ReactNode;
text: string;
disabled?: boolean;
fontSize?: string;
type?: 'text' | 'primary' | 'dashed' | 'link' | 'default';
size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default';
tips?: string;
placement?:
| 'top'
| 'left'
| 'right'
| 'bottom'
| 'topLeft'
| 'topRight'
| 'bottomLeft'
| 'bottomRight';
btnStyle?: React.CSSProperties;
style?: React.CSSProperties;
};
const CopyButton: React.FC<CopyButtonProps> = ({
children,
tips,
text,
disabled,
type = 'text',
shape = 'circle',
shape = 'default',
fontSize = '14px',
style,
btnStyle,
placement,
size = 'middle'
}) => {
const intl = useIntl();
@@ -47,6 +63,13 @@ const CopyButton: React.FC<CopyButtonProps> = ({
}
};
const tipTitle = useMemo(() => {
if (copied) {
return intl.formatMessage({ id: 'common.button.copied' });
}
return tips ?? intl.formatMessage({ id: 'common.button.copy' });
}, [copied, intl, tips]);
useEffect(() => {
initClipboard();
return () => {
@@ -55,7 +78,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
}, [buttonRef]);
return (
<Tooltip title={intl.formatMessage({ id: 'common.button.copy' })}>
<Tooltip title={tipTitle} placement={placement}>
<Button
className="copy-button"
ref={buttonRef}
@@ -64,6 +87,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
size={size}
disabled={!!disabled}
data-clipboard-text={text}
style={{ ...btnStyle }}
icon={
copied ? (
<CheckCircleFilled
@@ -73,7 +97,9 @@ const CopyButton: React.FC<CopyButtonProps> = ({
<CopyOutlined style={{ fontSize: fontSize, ...style }} />
)
}
></Button>
>
{children}
</Button>
</Tooltip>
);
};
+85
View File
@@ -0,0 +1,85 @@
import { getStorageUserSettings } from '@/atoms/settings';
import { useEffect, useRef, useState } from 'react';
import styled, { keyframes } from 'styled-components';
const duration = 1;
const fadeOut = keyframes`
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(1.05);
}
`;
const Mask = styled.div<{ $isHiding: boolean }>`
display: flex;
justify-content: center;
align-items: center;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(59, 59, 59, 1);
z-index: 9999;
animation: ${({ $isHiding }) => ($isHiding ? fadeOut : 'none')} ${duration}s
ease forwards;
`;
const DarkModeMask = () => {
const { theme: savedTheme } = getStorageUserSettings();
const [visible, setVisible] = useState(savedTheme === 'realDark');
const [isHiding, setIsHiding] = useState(false);
const maskRef = useRef<HTMLDivElement>(null);
const timerRef = useRef<any>(null);
const initTheme = () => {
const html = document.querySelector('html');
if (html) {
html.setAttribute('data-theme', savedTheme);
}
};
initTheme();
useEffect(() => {
if (savedTheme === 'realDark') {
setVisible(true);
requestAnimationFrame(() => {
setIsHiding(true);
});
} else {
setIsHiding(true);
}
}, [savedTheme]);
useEffect(() => {
const el = maskRef.current;
if (!el) return;
const handleAnimationEnd = () => {
clearTimeout(timerRef.current);
setVisible(false);
};
el.addEventListener('animationend', handleAnimationEnd);
if (isHiding) {
timerRef.current = setTimeout(
() => {
setVisible(false);
},
duration * 1000 + 100
);
}
return () => el.removeEventListener('animationend', handleAnimationEnd);
}, [visible]);
return visible ? <Mask ref={maskRef} $isHiding={isHiding}></Mask> : null;
};
export default DarkModeMask;
-22
View File
@@ -1,22 +0,0 @@
:local(.delete-modal-content) {
display: flex;
font-size: var(--font-size-middle);
:global {
.anticon {
font-size: 20px;
margin-right: 10px;
color: var(--ant-color-warning);
}
.title {
display: flex;
align-items: center;
}
}
}
:local(.content) {
padding-top: 15px;
padding-left: 30px;
}
+159 -43
View File
@@ -1,43 +1,135 @@
import useBodyScroll from '@/hooks/use-body-scroll';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Modal, Space, message, type ModalFuncProps } from 'antd';
import {
Button,
Checkbox,
Modal,
Space,
message,
type ModalFuncProps
} from 'antd';
import { createStyles } from 'antd-style';
import { forwardRef, useImperativeHandle, useState } from 'react';
import Styles from './index.less';
import styled from 'styled-components';
const useStyles = createStyles(({ css }) => ({
'delete-modal-content': css`
display: flex;
font-size: var(--font-size-middle);
.anticon {
font-size: 20px;
margin-right: 10px;
color: var(--ant-color-warning);
}
.title {
display: flex;
align-items: center;
font-weight: var(--font-weight-500);
}
`,
content: css`
padding-top: 15px;
padding-left: 30px;
color: var(--ant-color-text-secondary);
white-space: pre-line;
word-break: break-all;
span {
color: var(--ant-color-text);
display: flex;
margin-top: 8px;
}
`
}));
const CheckboxWrapper = styled.div`
margin-top: 20px;
margin-left: 30px;
display: flex;
justify-content: flex-start;
align-items: center;
.check-text {
font-weight: 700;
color: var(--ant-color-warning);
}
`;
interface DataOptions {
content?: string;
selection?: boolean;
name?: string;
okText?: string;
cancelText?: string;
title?: string;
operation: string;
checkConfig?: {
checkText: string;
defautlChecked: boolean;
};
}
interface Configuration {
checked: boolean;
}
const DeleteModal = forwardRef((props, ref) => {
const intl = useIntl();
const { styles } = useStyles();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [visible, setVisible] = useState(false);
const [config, setConfig] = useState<
ModalFuncProps & { content: string; selection?: boolean; name?: string }
>({});
const [configuration, setConfiguration] = useState<Configuration>({
checked: false
});
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
useImperativeHandle(ref, () => ({
show: (
data: ModalFuncProps & {
content: string;
selection?: boolean;
name?: string;
}
) => {
setConfig(data);
setVisible(true);
},
hide: () => {
setVisible(false);
}
}));
const show = (data: ModalFuncProps & DataOptions) => {
saveScrollHeight();
setConfig(data);
setConfiguration({
checked: data.checkConfig?.defautlChecked || false
});
setVisible(true);
};
const hide = () => {
setVisible(false);
restoreScrollHeight();
};
const handleCancel = () => {
setVisible(false);
config.onCancel?.();
restoreScrollHeight();
};
const handleOk = async () => {
await config.onOk?.();
message.success(intl.formatMessage({ id: 'common.message.success' }));
setVisible(false);
try {
const res = await config.onOk?.();
const isArray = Array.isArray(res);
if (isArray) {
const allSuccess = res.every(
(item: any) => item?.status === 'fulfilled'
);
if (allSuccess) {
message.success(intl.formatMessage({ id: 'common.message.success' }));
}
} else {
message.success(intl.formatMessage({ id: 'common.message.success' }));
}
} catch (error) {
// Handle error if needed
} finally {
setVisible(false);
restoreScrollHeight();
}
};
useImperativeHandle(ref, () => ({
show,
hide,
configuration
}));
return (
<Modal
style={{
@@ -46,49 +138,73 @@ const DeleteModal = forwardRef((props, ref) => {
open={visible}
onOk={handleOk}
onCancel={handleCancel}
destroyOnClose={true}
destroyOnClose={false}
closeIcon={false}
maskClosable={false}
keyboard={false}
width={460}
styles={{}}
styles={{
footer: {
marginTop: '20px'
}
}}
footer={
<Space size={20}>
<Button onClick={handleCancel} size="middle">
{intl.formatMessage({ id: 'common.button.cancel' })}
{config.cancelText
? intl.formatMessage({ id: config.cancelText })
: intl.formatMessage({ id: 'common.button.cancel' })}
</Button>
<Button type="primary" onClick={handleOk} size="middle">
{intl.formatMessage({ id: 'common.button.delete' })}
{config.okText
? intl.formatMessage({ id: config.okText })
: intl.formatMessage({ id: 'common.button.delete' })}
</Button>
</Space>
}
>
<div className={Styles['delete-modal-content']}>
<div className={styles['delete-modal-content']}>
<span className="title">
<ExclamationCircleFilled />
<span>
{intl.formatMessage({ id: 'common.title.delete.confirm' })}
{config.title
? intl.formatMessage({ id: config.title })
: intl.formatMessage({ id: 'common.title.delete.confirm' })}
</span>
</span>
</div>
<div
className={Styles['content']}
className={styles['content']}
dangerouslySetInnerHTML={{
__html:
config.content &&
intl.formatMessage(
{
id: config.selection
? 'common.delete.confirm'
: 'common.delete.single.confirm'
},
{
type: intl.formatMessage({ id: config.content }),
name: config.name
}
)
__html: config.content
? intl.formatMessage(
{
id: config.operation || ''
},
{
type: intl.formatMessage({ id: config.content }),
name: config.name
}
)
: ''
}}
></div>
{config.checkConfig && (
<CheckboxWrapper>
<Checkbox
checked={configuration.checked}
onChange={(e) =>
setConfiguration({
checked: e.target.checked
})
}
>
<span className="check-text">
{intl.formatMessage({ id: config.checkConfig?.checkText })}
</span>
</Checkbox>
</CheckboxWrapper>
)}
</Modal>
);
});
+31 -29
View File
@@ -1,34 +1,36 @@
import * as icons from '@ant-design/icons';
import { Button, Dropdown, Space } from 'antd';
import type { MenuProps } from 'antd/es/menu';
import react from 'react';
import { useIntl } from '@umijs/max';
import { Dropdown, DropDownProps } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react';
type DropdownProps = {
trigger?: Array<'click' | 'hover' | 'contextMenu'>;
items: MenuProps['items'];
onClick: (e: any) => void;
};
const renderIcon = (icon: string) => {
if (!icon) {
return null;
}
// @ts-ignore
const Icon = icons[icon] as React.FC;
return react.createElement(Icon);
};
const DropDownActions: React.FC<DropdownProps> = (props) => {
const { trigger, items, onClick } = props;
const DropDownActions: React.FC<DropDownProps> = (props) => {
const {
menu,
trigger = ['hover'],
placement = 'bottomRight',
children,
...rest
} = props;
const intl = useIntl();
const items = useMemo(() => {
return menu?.items?.map((item: any) => ({
..._.omit(item, 'locale'),
label: item.locale ? intl.formatMessage({ id: item.label }) : item.label
}));
}, [menu?.items, intl]);
return (
<Space>
<Dropdown menu={{ items, onClick }} trigger={trigger}>
<Button
onClick={(e) => e.preventDefault()}
style={{ fontSize: '14px' }}
type="text"
icon={renderIcon('MoreOutlined')}
></Button>
</Dropdown>
</Space>
<Dropdown
menu={{
items: items,
onClick: menu?.onClick
}}
trigger={trigger}
placement={placement}
{...rest}
>
{children}
</Dropdown>
);
};
@@ -0,0 +1,4 @@
.dropdown-button.middle {
height: 28px;
width: 28px;
}
+71 -21
View File
@@ -1,23 +1,39 @@
import { MoreOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Dropdown, Tooltip, type MenuProps } from 'antd';
import classNames from 'classnames';
import _ from 'lodash';
import { memo } from 'react';
import React from 'react';
import './index.less';
type Trigger = 'click' | 'hover';
interface DropdownButtonsProps {
items: MenuProps['items'];
size?: 'small' | 'middle' | 'large';
trigger?: Trigger[];
showText?: boolean;
disabled?: boolean;
variant?: 'filled' | 'outlined';
color?: string;
extra?: React.ReactNode;
onSelect: (val: any, item?: any) => void;
}
const DropdownButtons: React.FC<DropdownButtonsProps> = ({
items,
size = 'small',
size = 'middle',
trigger = ['hover'],
showText,
disabled,
variant,
color,
extra,
onSelect
}) => {
const headItem = _.head(items);
const intl = useIntl();
const handleMenuClick = (item: any) => {
console.log('menu click', item.key);
const selectItem = _.find(items, { key: item.key });
onSelect(item.key, selectItem);
};
@@ -34,23 +50,26 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
return (
<>
{items?.length === 1 ? (
<Tooltip title={intl.formatMessage({ id: _.head(items)?.label })}>
<Tooltip title={intl.formatMessage({ id: headItem?.label })}>
<Button
{..._.head(items)}
icon={_.get(items, '0.icon')}
className={classNames('dropdown-button', size)}
icon={headItem?.icon}
size={size}
{..._.get(items, '0.props')}
{...headItem?.props}
onClick={handleButtonClick}
></Button>
</Tooltip>
) : (
<Dropdown.Button
disabled={disabled}
trigger={trigger}
type="primary"
dropdownRender={(menus: any) => {
return (
<div
className="flex flex-column "
style={{
backgroundColor: 'var(--color-white-1)',
backgroundColor: 'var(--ant-color-bg-elevated)',
padding: 5,
alignItems: 'flex-start',
borderRadius: 'var(--border-radius-base)',
@@ -62,9 +81,10 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
<Button
{...item.props}
type="text"
size="middle"
size={size}
icon={item.icon}
key={item.key}
disabled={item.disabled}
onClick={() => handleMenuClick(item)}
style={{ width: '100%', justifyContent: 'flex-start' }}
>
@@ -76,17 +96,47 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
);
}}
buttonsRender={([leftButton, rightButton]) => [
<Tooltip
title={intl.formatMessage({ id: _.head(items)?.label })}
key="leftButton"
>
<Button
onClick={handleButtonClick}
size={size}
icon={_.head(items)?.icon}
></Button>
</Tooltip>,
<Button icon={<MoreOutlined />} size={size} key="menu"></Button>
<>
{showText ? (
<Button
{...headItem?.props}
disabled={headItem?.disabled || disabled}
className={classNames('dropdown-button', size)}
onClick={handleButtonClick}
size={size}
icon={headItem?.icon}
variant={variant}
color={color}
>
{intl.formatMessage({
id: headItem?.label
})}
{extra}
</Button>
) : (
<Tooltip
title={intl.formatMessage({ id: headItem?.label })}
key="leftButton"
>
<Button
{...headItem?.props}
className={classNames('dropdown-button', size)}
onClick={handleButtonClick}
size={size}
icon={headItem?.icon}
disabled={headItem?.disabled}
></Button>
</Tooltip>
)}
</>,
<Button
icon={<MoreOutlined />}
size={size}
key="menu"
variant={variant}
color="default"
className={classNames('dropdown-button', size)}
></Button>
]}
></Dropdown.Button>
)}
@@ -94,4 +144,4 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
);
};
export default memo(DropdownButtons);
export default DropdownButtons;
+58 -49
View File
@@ -1,16 +1,8 @@
import Chart from '@/components/echarts/chart';
import {
barItemConfig,
grid,
legend,
title as titleConfig,
tooltip,
xAxis,
yAxis
} from '@/components/echarts/config';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@@ -23,39 +15,41 @@ const BarChart: React.FC<ChartProps> = (props) => {
legendData,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: {
text: ''
},
const {
barItemConfig,
grid,
tooltip: {
...tooltip
// axisPointer: {
// type: 'shadow'
// }
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
},
data: []
},
yAxis,
legend: {
...legend,
data: []
},
legend,
title: titleConfig,
tooltip,
xAxis,
yAxis
} = useChartConfig();
series: []
};
const setDataOptions = () => {
const dataOptions = useMemo((): any => {
const options = {
title: {
text: ''
},
grid,
tooltip: {
...tooltip
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
},
data: []
},
yAxis,
legend: {
...legend,
data: []
},
series: []
};
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -82,16 +76,31 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
};
const dataOptions: any = setDataOptions();
}, [
seriesData,
xAxisData,
title,
labelFormatter,
tooltip,
grid,
xAxis,
yAxis,
legend,
barItemConfig
]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+75
View File
@@ -0,0 +1,75 @@
import React from 'react';
import styled from 'styled-components';
const TooltipWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 4px;
font-size: 11px;
background-color: rgba(255, 255, 255, 80%);
min-width: 100px;
max-width: 360px;
.tooltip-x-name {
font-size: var(--font-size-base);
color: var(--ant-color-text-tertiary);
}
.tooltip-item {
color: var(--ant-color-text-secondary);
display: flex;
justify-content: space-between;
align-items: center;
.tooltip-item-title {
margin-right: 2px;
}
.tooltip-value {
margin-left: 10px;
color: var(--ant-color-text);
text-overflow: ellipsis;
overflow: hidden;
}
}
`;
const ItemSymbol = styled.span<{ $color: string }>`
background-color: ${(props) => props.$color};
display: inline-block;
marginright: 5px;
borderradius: 8px;
width: 8px;
height: 8px;
`;
interface ChartTooltipProps {
params: any[];
callback?: (val: any) => any;
}
const ChartTooltip: React.FC<ChartTooltipProps> = (props) => {
const { params, callback } = props;
console.log('params====', params);
return (
<TooltipWrapper>
<span className="tooltip-x-name">{params[0]?.axisValue}</span>
<>
{params.map((item: any, index: number) => {
let value = callback?.(item.data.value) || item.data.value;
return (
<span className="tooltip-item" key={index}>
<span className="tooltip-item-name">
<ItemSymbol $color={item.color}></ItemSymbol>
<span className="tooltip-title">{item.seriesName}</span>:
</span>
<span className="tooltip-value">{value}</span>
</span>
);
})}
</>
</TooltipWrapper>
);
};
export default ChartTooltip;
+117 -27
View File
@@ -1,58 +1,148 @@
import { throttle } from 'echarts/core';
import { useCallback, useEffect, useRef } from 'react';
import _, { throttle } from 'lodash';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef
} from 'react';
import echarts, { ECOption } from '.';
const Chart: React.FC<{
options: ECOption;
height: number | string;
width: number | string;
}> = ({ options, width, height }) => {
ref?: any;
}> = forwardRef(({ options, width, height }, ref) => {
const container = useRef<HTMLDivElement>(null);
const chart = useRef<echarts.EChartsType>();
const resizeable = useRef(false);
const resizeObserver = useRef<ResizeObserver>();
const finished = useRef(false);
const init = useCallback(() => {
useImperativeHandle(ref, () => {
return {
chart: chart.current
};
});
const init = () => {
if (container.current) {
chart.current?.clear();
chart.current = echarts.init(container.current);
}
}, []);
};
const resize = useCallback(() => {
chart.current?.resize();
}, []);
const setOption = useCallback((options: ECOption) => {
const setOption = (options: ECOption) => {
console.log('setOption', options);
chart.current?.clear();
chart.current?.setOption(options, {
notMerge: true,
lazyUpdate: true
});
if (Array.isArray(options.yAxis) && options.yAxis.length > 1) {
chart.current?.resize();
}
};
useEffect(() => {
const handleOnFinished = () => {
if (!chart.current || finished.current) return;
const currentChart = chart.current;
const optionsYAxis = currentChart.getOption()?.yAxis;
if (
!optionsYAxis ||
!Array.isArray(optionsYAxis) ||
optionsYAxis.length < 2
)
return;
// @ts-ignore
const model = currentChart.getModel();
const yAxisModels = [
model.getComponent('yAxis', 0),
model.getComponent('yAxis', 1)
];
if (!yAxisModels[0] || !yAxisModels[1]) return;
const axes = yAxisModels.map((m) => m.axis);
const intervals = axes.map((axis) => axis.scale.getInterval());
const ticksList = axes.map((axis) => axis.scale.getTicks());
const counts = ticksList.map((t) => t.length);
const unifiedCount = Math.max(counts[0], counts[1]);
const newMax0 = intervals[0] * (unifiedCount - 1);
const newMax1 = intervals[1] * (unifiedCount - 1);
// if newMax0 equal to maxValue0, and newMax1 equal to maxValue1, do not update yAxis
if (counts[0] === counts[1]) return;
const yAxis: any[] = [{}, {}];
if (counts[0] < unifiedCount) {
yAxis[0].max = _.round(newMax0, 2);
yAxis[0].interval = intervals[0];
yAxis[0].splitNumber = unifiedCount;
}
if (counts[1] < unifiedCount) {
yAxis[1].max = _.round(newMax1, 2);
yAxis[1].interval = intervals[1];
yAxis[1].splitNumber = unifiedCount;
}
finished.current = true;
currentChart.setOption({
yAxis: yAxis
});
};
if (container.current) {
init();
chart.current?.on('finished', handleOnFinished);
}
return () => {
chart.current?.dispose();
chart.current?.off('finished', handleOnFinished);
};
}, []);
useEffect(() => {
init();
return () => {
chart.current?.dispose();
};
}, [init]);
useEffect(() => {
resize();
resizeable.current = false;
finished.current = false;
setOption(options);
resizeable.current = true;
}, [options]);
// resize on window resize
useEffect(() => {
const handleResize = throttle(() => {
resize();
if (resizeable.current) {
chart.current?.resize();
}
}, 100);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [resize]);
return <div ref={container} style={{ width: width, height }}></div>;
};
if (container.current) {
resizeObserver.current = new ResizeObserver(handleResize);
resizeObserver.current.observe(container.current);
}
return () => {
if (container.current) {
resizeObserver.current?.unobserve(container.current);
}
};
}, []);
return (
<div className="chart-wrapper" style={{ width: width, height }}>
<div ref={container} style={{ width: width, height }}></div>
</div>
);
});
export default Chart;
+215 -170
View File
@@ -1,190 +1,235 @@
import useUserSettings from '@/hooks/use-user-settings';
import { formatLargeNumber } from '@/utils';
import { theme } from 'antd';
import { isFunction } from 'lodash';
const chartColorMap = {
tickLineColor: 'rgba(217,217,217,0.5)',
axislabelColor: 'rgba(0, 0, 0, 0.4)'
};
export const tooltip = {
trigger: 'axis',
// axisPointer: {
// type: 'shadow'
// }
formatter(params: any, callback?: (val: any) => any) {
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
params.forEach((item: any) => {
let value = isFunction(callback)
? callback?.(item.data.value)
: item.data.value;
result += `<span class="tooltip-item">
<span class="tooltip-item-name">
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
<span class="tooltip-title">${item.seriesName}</span>:
</span>
<span class="tooltip-value">${value}</span>
</span>`;
});
return `<div class="tooltip-wrapper">${result}</div>`;
}
};
import { useMemo } from 'react';
export const grid = {
left: 0,
right: 20,
right: 0,
bottom: 20,
containLabel: true
};
export const legend = {
itemWidth: 8,
itemHeight: 8
};
export default function useChartConfig() {
const { userSettings, isDarkTheme } = useUserSettings();
const { useToken } = theme;
const { token } = useToken();
export const xAxis = {
type: 'category',
axisTick: {
show: true,
lineStyle: {
color: chartColorMap.tickLineColor
}
},
axisLabel: {
color: chartColorMap.axislabelColor,
fontSize: 12
},
axisLine: {
show: false
}
};
console.log('token:', token);
export const yAxis = {
// max: 100,
// min: 0,
nameTextStyle: {
padding: [0, 0, 0, -20]
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed'
}
},
axisLabel: {
color: chartColorMap.axislabelColor,
fontSize: 12
},
axisTick: {
show: false
},
type: 'value'
};
const chartColorMap = useMemo(() => {
return {
titleColor: token.colorText,
splitLineColor: token.colorBorder,
tickLineColor: token.colorSplit,
axislabelColor: token.colorTextTertiary,
gaugeBgColor: token.colorFillSecondary,
gaugeSplitLineColor: isDarkTheme
? 'rgba(255,255,255,.3)'
: 'rgba(255, 255, 255, 1)',
gaugeSplitLineColor2: isDarkTheme
? 'rgba(255,255,255,.5)'
: 'rgba(255, 255, 255, 1)',
colorBgContainerHover: isDarkTheme ? '#424242' : '#fff'
};
}, [userSettings.theme, isDarkTheme]);
export const title = {
show: true,
left: 'center',
textStyle: {
fontSize: 12,
color: '#000'
},
text: ''
};
const tooltip = {
trigger: 'axis',
backgroundColor: chartColorMap.colorBgContainerHover,
borderColor: 'transparent',
formatter(params: any, callback?: (val: any) => any) {
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
export const barItemConfig = {
type: 'bar',
barMaxWidth: 20,
barMinWidth: 8,
// stack: 'total',
barGap: '30%',
barCategoryGap: '50%'
};
params.forEach((item: any) => {
let value = isFunction(callback)
? callback?.(item.data.value)
: item.data.value;
export const lineItemConfig = {
type: 'line',
smooth: true,
showSymbol: false,
itemStyle: {},
lineStyle: {
width: 1.5,
opacity: 0.7
}
};
const borderRadius = item.seriesType === 'bar' ? '2px' : '8px';
export const gaugeItemConfig = {
type: 'gauge',
radius: '88%',
center: ['50%', '65%'],
startAngle: 190,
endAngle: -10,
min: 0,
max: 100,
splitNumber: 5,
progress: {
show: true,
roundCap: false,
width: 12
},
pointer: {
length: '80%',
width: 4,
itemStyle: {
color: 'auto'
result += `<span class="tooltip-item">
<span class="tooltip-item-name">
<span style="display:inline-block;margin-right:5px;border-radius:${borderRadius};width:8px;height:8px;background-color:${item.color};"></span>
<span class="tooltip-title">${item.seriesName}</span>:
</span>
<span class="tooltip-value">${value}</span>
</span>`;
});
return `<div class="tooltip-wrapper">${result}</div>`;
}
},
axisLine: {
roundCap: false,
lineStyle: {
width: 12,
// color: [[1, 'rgba(221, 221, 221, 0.5)']],
color: [
[0.5, 'rgba(84, 204, 152, 80%)'],
[0.8, 'rgba(250, 173, 20, 80%)'],
[1, 'rgba(255, 77, 79, 80%)']
]
};
const legend = {
itemWidth: 8,
itemHeight: 8,
textStyle: {
color: chartColorMap.axislabelColor
}
},
axisTick: {
distance: -12,
length: 6,
splitNumber: 5,
lineStyle: {
width: 1.5,
color: 'rgba(255, 255, 255, 1)'
}
},
splitLine: {
distance: -12,
length: 12,
lineStyle: {
width: 1.5,
color: 'rgba(255, 255, 255, 1)'
}
},
axisLabel: {
distance: 14,
color: 'rgba(0, 0, 0, .5)',
fontSize: 12
},
detail: {
lineHeight: 40,
height: 40,
offsetCenter: [5, 30],
valueAnimation: false,
fontSize: 20,
color: 'rgba(0, 0, 0, 0.88)',
formatter(value: any) {
return '{value|' + value + '}{unit|%}';
};
const xAxis = {
type: 'category',
axisTick: {
show: true,
lineStyle: {
color: chartColorMap.tickLineColor
}
},
rich: {
value: {
fontSize: 16,
fontWeight: '500',
color: 'rgba(0, 0, 0, 0.88)'
axisLabel: {
color: chartColorMap.axislabelColor,
fontSize: 12
},
axisLine: {
show: false
}
};
const yAxis = {
nameTextStyle: {
padding: [0, 0, 0, -20]
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: chartColorMap.splitLineColor
}
},
axisLabel: {
color: chartColorMap.axislabelColor,
fontSize: 12,
formatter: formatLargeNumber
},
axisTick: {
show: false
},
type: 'value'
};
const title = {
show: true,
left: 'center',
textStyle: {
fontSize: 12,
color: chartColorMap.titleColor
},
text: ''
};
const barItemConfig = {
type: 'bar',
barMaxWidth: 20,
barMinWidth: 8,
// stack: 'total',
barGap: '30%',
barCategoryGap: '50%'
};
const lineItemConfig = {
type: 'line',
smooth: true,
showSymbol: false,
itemStyle: {},
lineStyle: {
width: 1.5,
opacity: 0.7
}
};
const gaugeItemConfig = {
type: 'gauge',
radius: '88%',
center: ['50%', '65%'],
startAngle: 190,
endAngle: -10,
min: 0,
max: 100,
splitNumber: 5,
progress: {
show: true,
roundCap: false,
width: 12
},
pointer: {
length: '80%',
width: 4,
itemStyle: {
color: 'auto'
}
},
axisLine: {
roundCap: false,
lineStyle: {
width: 12,
color: [
[0.5, 'rgba(84, 204, 152, 80%)'],
[0.8, 'rgba(250, 173, 20, 80%)'],
[1, 'rgba(255, 77, 79, 80%)']
]
}
},
axisTick: {
distance: -11,
length: 6,
splitNumber: 5,
lineStyle: {
width: 1.5,
color: chartColorMap.gaugeSplitLineColor
}
},
splitLine: {
distance: -5,
length: 5,
lineStyle: {
width: 1.5,
color: chartColorMap.gaugeSplitLineColor2
}
},
axisLabel: {
distance: 14,
color: chartColorMap.axislabelColor,
fontSize: 12
},
detail: {
lineHeight: 40,
height: 40,
offsetCenter: [5, 30],
valueAnimation: false,
fontSize: 20,
color: chartColorMap.titleColor,
formatter(value: any) {
return '{value|' + value + '}{unit|%}';
},
unit: {
fontSize: 14,
color: 'rgba(0, 0, 0, 0.88)',
fontWeight: '500',
padding: [0, 0, 0, 2]
rich: {
value: {
fontSize: 16,
fontWeight: '500',
color: chartColorMap.titleColor
},
unit: {
fontSize: 14,
color: chartColorMap.titleColor,
fontWeight: '500',
padding: [0, 0, 0, 2]
}
}
}
}
};
};
return {
tooltip,
grid,
legend,
xAxis,
yAxis,
title,
chartColorMap,
barItemConfig,
lineItemConfig,
gaugeItemConfig,
isDark: isDarkTheme
};
}
+8 -6
View File
@@ -1,15 +1,17 @@
import Chart from '@/components/echarts/chart';
import {
gaugeItemConfig,
title as titleConfig
} from '@/components/echarts/config';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import { memo } from 'react';
import React, { memo } from 'react';
import { ChartProps } from './types';
const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
props
) => {
const {
gaugeItemConfig,
title: titleConfig,
chartColorMap
} = useChartConfig();
const { value, height, width, labelFormatter, title, color } = props;
if (!value && value !== 0) {
return <EmptyData height={height} title={title}></EmptyData>;
@@ -32,7 +34,7 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
...gaugeItemConfig.axisLine.lineStyle,
color: [
[value / 100, color],
[1, 'rgba(221, 221, 221, 0.5)']
[1, chartColorMap.gaugeBgColor]
]
}
},
+76 -42
View File
@@ -1,18 +1,11 @@
import Chart from '@/components/echarts/chart';
import {
grid,
legend,
title as titleConfig,
tooltip,
xAxis,
yAxis
} from '@/components/echarts/config';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
const {
seriesData,
xAxisData,
@@ -20,35 +13,57 @@ const BarChart: React.FC<ChartProps> = (props) => {
width,
labelFormatter,
legendData,
maxItems,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: titleConfig,
const {
grid,
tooltip: {
...tooltip
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
}
},
yAxis,
legend: {
...legend,
data: []
},
legend,
title: titleConfig,
tooltip,
xAxis,
yAxis
} = useChartConfig();
series: []
};
const dataOptions = useMemo((): any => {
const options = {
title: {
...titleConfig,
left: 'start'
},
grid: {
...grid,
top: 0,
bottom: maxItems
? `${(1 / maxItems) * (maxItems - xAxisData.length) * 100}%`
: 0
},
tooltip: {
...tooltip
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
}
},
yAxis: {
...yAxis,
axisLabel: {
...yAxis.axisLabel,
overflow: 'truncate',
width: 75,
ellipsis: '...'
}
},
legend: {
...legend,
data: []
},
const setDataOptions = () => {
series: []
};
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -69,7 +84,6 @@ const BarChart: React.FC<ChartProps> = (props) => {
animation: false,
title: {
...options.title,
left: 'start',
text: title
},
yAxis: {
@@ -103,16 +117,36 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
};
}, [
seriesData,
xAxisData,
title,
labelFormatter,
tooltip,
grid,
xAxis,
yAxis,
legend
]);
const dataOptions: any = setDataOptions();
const isEmpty = useMemo(() => {
return seriesData?.every?.((item: any) => {
return !item?.data?.length;
});
}, [seriesData]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{isEmpty ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+9 -8
View File
@@ -1,25 +1,24 @@
import type {
// 系列类型的定义后缀都为 SeriesOption
BarSeriesOption,
GaugeSeriesOption,
LineSeriesOption
LineSeriesOption,
ScatterSeriesOption
} from 'echarts/charts';
import { BarChart, GaugeChart, LineChart } from 'echarts/charts';
import { BarChart, GaugeChart, LineChart, ScatterChart } from 'echarts/charts';
import type {
DatasetComponentOption,
GridComponentOption,
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
TooltipComponentOption
} from 'echarts/components';
import {
// 数据集组件
DataZoomComponent,
DatasetComponent,
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
// 内置数据转换器组件 (filter, sort)
// (filter, sort)
TransformComponent
} from 'echarts/components';
import type { ComposeOption } from 'echarts/core';
@@ -27,7 +26,6 @@ import * as echarts from 'echarts/core';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
type ECOption = ComposeOption<
| BarSeriesOption
| LineSeriesOption
@@ -36,9 +34,10 @@ type ECOption = ComposeOption<
| GridComponentOption
| DatasetComponentOption
| GaugeSeriesOption
| ScatterSeriesOption
>;
// 注册必须的组件
// register components and charts
echarts.use([
LegendComponent,
TitleComponent,
@@ -46,8 +45,10 @@ echarts.use([
GridComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
BarChart,
LineChart,
ScatterChart,
GaugeChart,
LabelLayout,
UniversalTransition,
+48 -26
View File
@@ -1,16 +1,8 @@
import Chart from '@/components/echarts/chart';
import {
grid,
legend,
lineItemConfig,
title as titleConfig,
tooltip,
xAxis,
yAxis
} from '@/components/echarts/config';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { ChartProps } from './types';
const LineChart: React.FC<ChartProps> = (props) => {
@@ -26,9 +18,25 @@ const LineChart: React.FC<ChartProps> = (props) => {
smooth,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const {
grid,
legend,
lineItemConfig,
title: titleConfig,
tooltip,
xAxis,
yAxis
} = useChartConfig();
const axisLabelFormatter = (value: string, index: number) => {
if (labelFormatter) {
return labelFormatter(value, index);
}
if (index === xAxisData.length - 1) {
return '';
}
return value;
};
const options = {
title: {
@@ -47,19 +55,24 @@ const LineChart: React.FC<ChartProps> = (props) => {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
formatter: axisLabelFormatter
}
},
yAxis,
legend: {
...legend,
data: legendData
data: legendData.map((item: any) => {
return {
name: item,
icon: 'circle'
};
})
},
series: []
};
const generateOptions = (): any => {
const dataOptions = useMemo((): any => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -75,7 +88,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
}
};
});
const optionsConfig = {
return {
...options,
animation: false,
title: {
@@ -84,7 +97,11 @@ const LineChart: React.FC<ChartProps> = (props) => {
},
yAxis: {
...options.yAxis,
name: yAxisName
name: yAxisName,
nameTextStyle: {
fontSize: 12,
align: 'right'
}
},
xAxis: {
...options.xAxis,
@@ -92,15 +109,20 @@ const LineChart: React.FC<ChartProps> = (props) => {
},
series: data
};
return optionsConfig;
};
const dataOptions = generateOptions();
}, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+152
View File
@@ -0,0 +1,152 @@
import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import React, { useMemo } from 'react';
import { ChartProps } from './types';
const MixLineBarChart: React.FC<
ChartProps & {
chartData: {
line: any[];
bar: any[];
};
}
> = (props) => {
const {
seriesData,
xAxisData,
yAxisName,
height,
width,
labelFormatter,
tooltipValueFormatter = null,
legendData = [],
smooth,
title,
chartData
} = props;
const {
grid,
legend,
lineItemConfig,
barItemConfig,
title: titleConfig,
tooltip,
xAxis,
yAxis
} = useChartConfig();
const { line: lineSeriesData, bar: barSeriesData } = chartData;
const options = {
title: {
text: ''
},
grid: {
...grid,
right: 0,
top: 20,
bottom: 10
},
tooltip: {
...tooltip,
formatter(params: any) {
return tooltipValueFormatter
? tooltip.formatter(params, tooltipValueFormatter)
: tooltip.formatter(params);
}
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
}
},
yAxis,
legend: {
...legend,
data: legendData,
itemGap: 20,
bottom: 5,
show: false
},
series: []
};
const dataOptions = useMemo((): any => {
const linedata = _.map(lineSeriesData, (item: any) => {
return {
...item,
...lineItemConfig,
smooth: smooth,
itemStyle: {
...lineItemConfig.itemStyle,
color: item.color
},
yAxisIndex: 1,
lineStyle: {
...lineItemConfig.lineStyle,
color: item.color
}
};
});
const barData = _.map(barSeriesData, (item: any) => {
return {
...item,
...barItemConfig,
stack: 'total',
yAxisIndex: 0,
itemStyle: {
...item.itemStyle,
color: item.color
}
};
});
return {
...options,
animation: false,
title: {
...titleConfig,
text: title
},
yAxis: [
{
...options.yAxis
},
{
...options.yAxis,
nameTextStyle: {
fontSize: 12,
align: 'right'
}
}
],
xAxis: {
...options.xAxis,
data: xAxisData
},
series: [...barData, ...linedata]
};
}, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]);
return (
<>
{!lineSeriesData.length && !barSeriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
export default MixLineBarChart;
+217
View File
@@ -0,0 +1,217 @@
import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import React, { memo, useCallback, useMemo, useRef } from 'react';
import { ChartProps } from './types';
const Scatter: React.FC<ChartProps> = (props) => {
const { grid, title: titleConfig, isDark, chartColorMap } = useChartConfig();
const { seriesData, xAxisData, height, width, showEmpty, title } = props;
const chart = useRef<any>(null);
const options = useMemo(() => {
const colorMap = isDark
? {
split: chartColorMap.splitLineColor,
axis: chartColorMap.axislabelColor,
label: chartColorMap.axislabelColor
}
: {
split: '#F2F2F2',
axis: '#dcdcdc',
label: '#dcdcdc'
};
return {
animation: false,
grid: {
...grid,
right: 10,
top: 10,
bottom: 2,
left: 2,
containLabel: true,
borderRadius: 4
},
xAxis: {
min: -1,
max: 1,
scale: false,
slient: true,
splitNumber: 15,
splitLine: {
lineStyle: {
color: colorMap.split
}
},
axisLine: {
show: true,
lineStyle: {
color: colorMap.axis
}
},
axisTick: {
show: false
},
axisLabel: {
show: true,
color: colorMap.label
},
boundaryGap: [0.05, 0.05]
},
yAxis: {
min: -1,
max: 1,
scale: false,
slient: true,
splitNumber: 10,
boundaryGap: [0.05, 0.05],
splitLine: {
lineStyle: {
color: colorMap.split
}
},
axisLine: {
show: true,
lineStyle: {
color: colorMap.axis
}
},
axisTick: {
show: false
},
axisLabel: {
show: true,
color: colorMap.label
}
},
symbol: 'roundRect',
label: {
show: true,
shadowColor: 'none',
textBorderColor: 'none',
formatter: (params: any) => {
return params.name;
}
},
series: []
};
}, [isDark]);
const findOverlappingPoints = useCallback(
(data: any[], currentPoint: any) => {
const overlappingPoints = [];
const symbolRadius = 16;
const [x1, y1] = chart.current.chart?.convertToPixel(
'grid',
currentPoint.value
);
const pixelPoints = data.map((point) => {
return {
...point,
value: chart.current.chart?.convertToPixel('grid', point.value)
};
});
for (let j = 0; j < pixelPoints.length; j++) {
if (currentPoint.name === pixelPoints[j].name) {
overlappingPoints.push({ ...pixelPoints[j] });
continue;
}
const [x2, y2] = pixelPoints[j].value;
const distance = Math.sqrt(
Math.pow(_.round(x2 - x1, 2), 2) + Math.pow(_.round(y2 - y1, 2), 2)
);
if (distance <= symbolRadius) {
overlappingPoints.push({ ...pixelPoints[j] });
}
}
return overlappingPoints;
},
[]
);
const renderNameInTooltip = useCallback((dataList: any[]) => {
if (!dataList.length || dataList.length < 2) {
return null;
}
const renderText = (item: any) => {
return `<span class="tooltip-item-name">
<span style="display:flex;justify-content:center;align-items: center;color:#fff;
margin-right:0;border-radius:4px;width:14px;
height:14px;background-color:${item?.itemStyle?.color};"
>${item.name}</span>
</span>`;
};
return renderText;
}, []);
const dataOptions = useMemo((): any => {
const seriseDataList = seriesData.map((item: any, index: number) => {
return {
...item,
itemStyle: {
color: '#5470c6'
},
symbolSize: 16
};
});
return {
...options,
tooltip: {
trigger: 'item',
borderWidth: 0,
backgroundColor: chartColorMap.colorBgContainerHover,
borderColor: 'transparent',
formatter(params: any, callback?: (val: any) => any) {
const dataList = findOverlappingPoints(seriseDataList, params.data);
let result = '';
const renderText: any = renderNameInTooltip(dataList);
dataList.forEach((item: any) => {
result += `
<span class="tooltip-item" style="justify-content: flex-start;">
${renderText ? renderText(item) : ''}
<span class="tooltip-value">${item.text}</span>
</span>`;
});
return `<div class="tooltip-wrapper scatter">${result}</div>`;
}
},
title: {
...titleConfig,
text: title
},
series: {
type: 'scatter',
labelLayout: {
hideOverlap: true
},
data: seriseDataList
}
};
}, [seriesData, xAxisData, title, options, findOverlappingPoints]);
return (
<>
{!seriesData.length && showEmpty ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
ref={chart}
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
export default memo(Scatter);
+5 -3
View File
@@ -1,9 +1,11 @@
import type { LegendComponentOption } from 'echarts/components';
export interface ChartProps {
seriesData: any[];
showEmpty?: boolean;
xAxisData: string[];
legendData?: string[];
labelFormatter?: (val?: any) => string;
tooltipValueFormatter?: (val?: any) => string;
legendData?: LegendComponentOption['data'];
labelFormatter?: (val?: any, index?: number) => string;
tooltipValueFormatter?: (val: any) => string;
height: string | number;
width?: string | number;
title?: string;
+8 -16
View File
@@ -1,29 +1,21 @@
.editor-wrap {
border-radius: var(--border-radius-mini);
overflow: hidden;
font-size: 0;
.code-pre {
margin-bottom: 0;
}
.editor-header {
display: flex;
padding: 6px 10px;
padding-block: 0;
padding-inline: 12px 10px;
justify-content: space-between;
align-items: center;
background-color: rgb(56, 56, 56);
.ant-select-selector {
background-color: rgba(255, 255, 255, 9%) !important;
color: rgba(255, 255, 255, 70%);
.ant-select-selection-item {
color: rgba(255, 255, 255, 70%);
}
}
.ant-select-arrow {
color: rgba(255, 255, 255, 70%);
}
background-color: var(--color-editor-header-bg);
}
// set scrollbar style
.scrollbar {
.slider {
border-radius: 6px;
+25 -13
View File
@@ -1,27 +1,40 @@
import { Select } from 'antd';
import React from 'react';
import styled from 'styled-components';
import CopyButton from '../copy-button';
import SegmentLine from '../segment-line';
import './index.less';
const Wrapper = styled.div<{ $height: number }>`
height: ${(props) => props.$height}px;
`;
interface EditorwrapProps {
headerHeight?: number;
header?: React.ReactNode;
children: React.ReactNode;
showHeader?: boolean;
copyText: string;
defaultValue?: string;
langOptions?: { label: string; value: string }[];
onChangeLang?: (value: string) => void;
langOptions?: Global.BaseOption<string | number>[];
styles?: {
wrapper?: React.CSSProperties;
header?: React.CSSProperties;
content?: React.CSSProperties;
};
onChangeLang?: (value: string | number) => void;
}
const EditorWrap: React.FC<EditorwrapProps> = ({
headerHeight = 40,
header,
children,
copyText,
langOptions,
langOptions = [],
onChangeLang,
defaultValue,
styles = {},
showHeader = true
}) => {
const handleChangeLang = (value: string) => {
const handleChangeLang = (value: string | number) => {
onChangeLang?.(value);
};
const renderHeader = () => {
@@ -30,15 +43,14 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
}
if (showHeader) {
return (
<div className="editor-header">
<Select
<Wrapper className="editor-header" $height={headerHeight}>
<SegmentLine
height={headerHeight}
defaultValue={defaultValue}
style={{ width: 120 }}
size="middle"
variant="filled"
size="small"
options={langOptions}
onChange={handleChangeLang}
></Select>
></SegmentLine>
<CopyButton
text={copyText}
size="small"
@@ -46,13 +58,13 @@ const EditorWrap: React.FC<EditorwrapProps> = ({
color: 'rgba(255,255,255,.7)'
}}
/>
</div>
</Wrapper>
);
}
return null;
};
return (
<div className="editor-wrap">
<div className="editor-wrap" style={{ ...styles.wrapper }}>
{renderHeader()}
<div className="editor-content">{children}</div>
</div>
+89
View File
@@ -0,0 +1,89 @@
import Editor from '@monaco-editor/react';
import React, { useEffect, useRef, useState } from 'react';
import EditorWrap from '../editor-wrap';
interface ViewerProps {
lang: string;
defaultLang?: string;
langOptions?: Global.BaseOption<string>[];
config?: any;
value: string;
height?: string | number;
theme?: string;
showHeader?: boolean;
}
const ViewerEditor: React.FC<ViewerProps> = (props) => {
const editorRef = useRef<any>(null);
const {
lang,
value,
config,
langOptions,
defaultLang,
height = 380,
showHeader
} = props;
const [langType, setLangType] = useState(defaultLang);
const handleBeforeMount = (monaco: any) => {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
diagnosticCodesToIgnore: [80001]
});
};
const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
console.log('loaded====', editor, monaco);
};
const handleOnChangeLang = (value: string) => {
setLangType(value);
};
const formatCode = () => {
if (editorRef.current) {
setTimeout(() => {
editorRef.current
?.getAction?.('editor.action.formatDocument')
?.run()
.then(() => {
console.log('format success');
});
}, 100);
}
};
useEffect(() => {
formatCode();
setTimeout(() => {
const lineCount = editorRef.current?.getModel().getLineCount(); // 获取总行数
editorRef.current?.revealLine(lineCount); // 滚动到最后一行
}, 100); // 可以调整延时,确保编辑器完全加载
}, [value]);
return (
<EditorWrap
copyText={value}
langOptions={langOptions}
defaultValue={lang}
showHeader={showHeader}
onChangeLang={handleOnChangeLang}
>
<Editor
height={height}
theme="vs-dark"
className="monaco-editor"
defaultLanguage={defaultLang}
language={langType}
value={value}
options={config}
beforeMount={handleBeforeMount}
onMount={handleEditorDidMount}
/>
</EditorWrap>
);
};
export default React.memo(ViewerEditor);
-16
View File
@@ -1,16 +0,0 @@
// footer all styles
.footer {
bottom: 0;
width: 100%;
background-color: transparent;
padding: 20px 0;
text-align: center;
font-size: var(--font-size-middle);
color: var(--color-text-2);
.footer-content-left-text {
display: flex;
justify-content: center;
align-items: center;
}
}
+46 -15
View File
@@ -3,30 +3,60 @@ import { getAtomStorage } from '@/atoms/utils';
import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links';
import { useIntl } from '@umijs/max';
import { Button, Modal, Space } from 'antd';
import './index.less';
import { Button, Divider, Modal } from 'antd';
import { createStyles } from 'antd-style';
import styled from 'styled-components';
const CompanyWrapper = styled.div`
display: flex;
align-items: center;
gap: 4px;
padding-inline: 0 8px;
`;
const useStyles = createStyles(({ token, css }) => ({
footer: css`
bottom: 0;
width: 100%;
background-color: transparent;
padding: 20px 0;
text-align: center;
font-size: var(--font-size-middle);
color: ${token.colorTextTertiary};
`,
'footer-content-left-text': {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
}));
const Footer: React.FC = () => {
const intl = useIntl();
const [modal, contextHolder] = Modal.useModal();
const { styles } = useStyles();
const showVersion = () => {
Modal.info({
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />
});
};
return (
<div className="footer">
<div className="footer-content">
<div className="footer-content-left">
<div className="footer-content-left-text">
<Space size={4}>
<span>&copy;</span>
<span> {new Date().getFullYear()}</span>
<span> {intl.formatMessage({ id: 'settings.company' })}</span>
</Space>
<Space size={8} style={{ marginLeft: 18 }}>
<>
{contextHolder}
<div className={styles.footer}>
<div className="footer-content">
<div className="footer-content-left">
<div className={styles['footer-content-left-text']}>
<CompanyWrapper>
<span>&copy;</span>
<span> {new Date().getFullYear()}</span>
<span> {intl.formatMessage({ id: 'settings.company' })}</span>
</CompanyWrapper>
<Divider type="vertical" />
<Button
type="link"
size="small"
@@ -35,14 +65,15 @@ const Footer: React.FC = () => {
>
{intl.formatMessage({ id: 'common.button.help' })}
</Button>
<Divider type="vertical" />
<Button type="link" size="small" onClick={showVersion}>
{getAtomStorage(GPUStackVersionAtom)?.version}
</Button>
</Space>
</div>
</div>
</div>
</div>
</div>
</>
);
};
@@ -4,17 +4,31 @@ import './styles/dark.less';
interface CodeViewerProps {
code: string;
copyValue?: string;
lang: string;
autodetect?: boolean;
ignoreIllegals?: boolean;
copyable?: boolean;
height?: string | number;
style?: React.CSSProperties;
}
const DarkViewer: React.FC<CodeViewerProps> = (props) => {
const { code, lang, autodetect, ignoreIllegals, copyable } = props || {};
const {
code,
copyValue,
lang,
autodetect,
ignoreIllegals,
copyable,
height = 'auto'
} = props || {};
return (
<CodeViewer
style={props.style}
height={height}
code={code}
copyValue={copyValue}
lang={lang}
theme="dark"
autodetect={autodetect}
@@ -4,17 +4,32 @@ import './styles/light.less';
interface CodeViewerProps {
code: string;
copyValue?: string;
lang: string;
autodetect?: boolean;
ignoreIllegals?: boolean;
copyable?: boolean;
height?: string | number;
style?: React.CSSProperties;
}
const LightViewer: React.FC<CodeViewerProps> = (props) => {
const { code, lang, autodetect, ignoreIllegals, copyable } = props || {};
const {
code,
copyValue,
lang,
autodetect,
ignoreIllegals,
copyable,
style,
height = 'auto'
} = props || {};
return (
<CodeViewer
style={style}
height={height}
code={code}
copyValue={copyValue}
lang={lang}
theme="light"
autodetect={autodetect}
+96 -22
View File
@@ -1,24 +1,86 @@
import classNames from 'classnames';
import hljs from 'highlight.js';
import { memo, useMemo } from 'react';
import styled from 'styled-components';
import CopyButton from '../copy-button';
import { escapeHtml } from './utils';
interface CodeViewerProps {
code: string;
copyValue?: string;
lang: string;
autodetect?: boolean;
ignoreIllegals?: boolean;
copyable?: boolean;
height?: string | number;
theme?: 'light' | 'dark';
style?: React.CSSProperties;
}
interface CodeHeaderProps {
copyValue: string;
copyable: boolean;
lang: string;
theme: 'light' | 'dark';
}
const CodeHeaderWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
padding: 0 12px;
font-size: 12px;
color: var(--ant-color-text-tertiary);
background-color: #fafafa;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&.dark {
background-color: var(--color-editor-header-bg);
color: rgba(255, 255, 255, 0.65);
}
`;
const Wrapper = styled.div`
border-radius: var(--border-radius-mini);
`;
const CodeHeader: React.FC<CodeHeaderProps> = ({
copyValue,
lang,
theme,
copyable
}) => {
if (!copyable) {
return null;
}
return (
<CodeHeaderWrapper
className={classNames({
dark: theme === 'dark',
light: theme === 'light'
})}
>
<span>{lang}</span>
<CopyButton
text={copyValue}
size="small"
style={{ color: '#abb2bf' }}
></CopyButton>
</CodeHeaderWrapper>
);
};
const CodeViewer: React.FC<CodeViewerProps> = (props) => {
const {
code,
code = '',
copyValue,
lang,
autodetect = true,
ignoreIllegals = true,
copyable = true
copyable = true,
height = 'auto',
style
} = props || {};
const highlightedCode = useMemo(() => {
@@ -57,27 +119,39 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
}, [code, lang, autodetect, ignoreIllegals]);
return (
<pre
className={classNames('code-pre', {
dark: props.theme === 'dark',
light: props.theme === 'light',
copyable: copyable
})}
>
<code
className={highlightedCode.className}
dangerouslySetInnerHTML={{
__html: highlightedCode.value
<Wrapper>
<CodeHeader
copyValue={copyValue || code}
lang={lang}
copyable={copyable}
theme={props.theme || 'light'}
></CodeHeader>
<pre
className={classNames(
'code-pre custome-scrollbar custom-scrollbar-horizontal ',
{
dark: props.theme === 'dark',
light: props.theme === 'light'
}
)}
style={{
marginBottom: 0,
height: height,
...style
}}
></code>
{copyable && (
<CopyButton
text={code}
size="small"
style={{ color: '#abb2bf' }}
></CopyButton>
)}
</pre>
>
<code
style={{ minHeight: height }}
className={classNames(highlightedCode.className, {
dark: props.theme === 'dark',
light: props.theme === 'light'
})}
dangerouslySetInnerHTML={{
__html: highlightedCode.value
}}
></code>
</pre>
</Wrapper>
);
};
+40 -5
View File
@@ -1,3 +1,5 @@
import useUserSettings from '@/hooks/use-user-settings';
import React from 'react';
import CodeViewerDark from './code-viewer-dark';
import CodeViewerLight from './code-viewer-light';
import './styles/index.less';
@@ -7,15 +9,48 @@ const HighlightCode: React.FC<{
lang?: string;
copyable?: boolean;
theme?: 'light' | 'dark';
fixedTheme?: 'light' | 'dark';
height?: string | number;
style?: React.CSSProperties;
copyValue?: string;
}> = (props) => {
const { code, lang = 'bash', copyable = true, theme = 'dark' } = props;
const {
style,
code,
copyValue,
lang = 'bash',
copyable = true,
theme,
height = 'auto'
} = props;
const { userSettings } = useUserSettings();
const currentTheme = React.useMemo(() => {
const res = theme || userSettings.theme === 'realDark' ? 'dark' : 'light';
return res;
}, [theme, userSettings.theme]);
return (
<div className="high-light-wrapper">
{theme === 'dark' ? (
<CodeViewerDark lang={lang} code={code} copyable={copyable} />
<div className="high-light-wrapper hj-wrapper">
{currentTheme === 'dark' ? (
<CodeViewerDark
lang={lang}
code={code}
copyValue={copyValue}
copyable={copyable}
height={height}
style={style}
/>
) : (
<CodeViewerLight lang={lang} code={code} copyable={copyable} />
<CodeViewerLight
style={style}
lang={lang}
code={code}
copyValue={copyValue}
copyable={copyable}
height={height}
/>
)}
</div>
);
@@ -31,7 +31,7 @@ hue-6-2: #e6c07b
.code-pre.dark {
.hljs {
color: #abb2bf;
background: #282c34;
background: var(--color-editor-dark);
}
.hljs-comment,
@@ -1,5 +1,6 @@
.high-light-wrapper {
text-align: left;
font-size: var(--font-size-code);
.hljs {
font-weight: var(--font-weight-normal);
@@ -7,27 +8,46 @@
padding-block: 1.2em;
&::-webkit-scrollbar {
height: 8px;
height: var(--scrollbar-size);
}
&::-webkit-scrollbar-thumb {
background-color: rgba(211, 211, 220, 50%);
border-radius: 6px;
&:hover {
background-color: rgba(190, 190, 190, 100%);
}
background-color: transparent;
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&.light {
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 4px;
}
}
}
&.dark {
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-handle-light-bg);
border-radius: 4px;
}
}
}
}
.code-pre {
padding-inline: 12px 12px;
border-radius: 0 0 var(--border-radius-mini) var(--border-radius-mini);
position: relative;
border-radius: var(--border-radius-mini);
white-space: pre-wrap;
code {
line-height: 1.5;
}
&.copyable {
padding-inline: 12px 32px;
@@ -40,11 +60,25 @@
}
&.dark {
background-color: #282c34;
background-color: var(--color-editor-dark);
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-handle-light-bg);
border-radius: 4px;
}
}
}
&.light {
background-color: rgb(250, 250, 250);
&:hover {
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar-thumb);
border-radius: 4px;
}
}
}
}
}
@@ -31,7 +31,7 @@ hue-6-2: #c18401
.code-pre.light {
.hljs {
color: #383a42;
background: #fafafa;
background: var(--color-editor-light);
}
.hljs-comment,
@@ -0,0 +1,591 @@
@font-face {
font-family: iconfont; /* Project id 4613488 */
src: url('iconfont.woff2?t=1755160439217') format('woff2'),
url('iconfont.woff?t=1755160439217') format('woff'),
url('iconfont.ttf?t=1755160439217') format('truetype');
}
.iconfont {
font-family: iconfont !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-admin-user::before {
content: "\e69d";
}
.icon-user::before {
content: "\e69e";
}
.icon-quick_reference_24dp_1F1F1F_FILL0_wght300_GRAD0_op::before {
content: "\e69b";
}
.icon-docker::before {
content: "\e69c";
}
.icon-digitalocean::before {
content: "\eb79";
}
.icon-rocket-launch1::before {
content: "\e699";
}
.icon-rocket-launch-fill::before {
content: "\e69a";
}
.icon-credential-filled::before {
content: "\e697";
}
.icon-credential-outline::before {
content: "\e698";
}
.icon-k8s-filled::before {
content: "\e63e";
}
.icon-k8s-outline::before {
content: "\e64b";
}
.icon-huggingface1::before {
content: "\e605";
}
.icon-modelscope_light::before {
content: "\e696";
}
.icon-catalog1::before {
content: "\e691";
}
.icon-chat-filled::before {
content: "\e692";
}
.icon-chat::before {
content: "\e693";
}
.icon-files-filled::before {
content: "\e694";
}
.icon-files::before {
content: "\e695";
}
.icon-models-filled::before {
content: "\e682";
}
.icon-image-filled::before {
content: "\e683";
}
.icon-audio1::before {
content: "\e684";
}
.icon-image1::before {
content: "\e685";
}
.icon-audio-filled::before {
content: "\e686";
}
.icon-reranker-filled::before {
content: "\e687";
}
.icon-embedding-filled::before {
content: "\e68a";
}
.icon-models::before {
content: "\e68b";
}
.icon-reranker::before {
content: "\e68c";
}
.icon-embedding::before {
content: "\e68d";
}
.icon-gpu-filled::before {
content: "\e68e";
}
.icon-catalog-filled::before {
content: "\e68f";
}
.icon-gpu1::before {
content: "\e690";
}
.icon-language::before {
content: "\e679";
}
.icon-help::before {
content: "\e67a";
}
.icon-key-filled::before {
content: "\e67b";
}
.icon-key::before {
content: "\e67d";
}
.icon-resources::before {
content: "\e67e";
}
.icon-users::before {
content: "\e67f";
}
.icon-resources-filled::before {
content: "\e680";
}
.icon-users-filled::before {
content: "\e681";
}
.icon-model::before {
content: "\e676";
}
.icon-model-filled::before {
content: "\e678";
}
.icon-layers-filled::before {
content: "\e671";
}
.icon-layers::before {
content: "\e673";
}
.icon-experiment::before {
content: "\e674";
}
.icon-experiment-filled::before {
content: "\e675";
}
.icon-dashboard::before {
content: "\e66d";
}
.icon-dashboard-filled::before {
content: "\e66e";
}
.icon-expand-left::before {
content: "\ea48";
}
.icon-expand-right::before {
content: "\ea49";
}
.icon-users-fill::before {
content: "\e677";
}
.icon-layers-fill::before {
content: "\e89d";
}
.icon-key-fill::before {
content: "\e80e";
}
.icon-server-fill::before {
content: "\e7a3";
}
.icon-model-fill::before {
content: "\e7b7";
}
.icon-left_panel_close::before {
content: "\e668";
}
.icon-left_panel_open::before {
content: "\e669";
}
.icon-playcircle-fill::before {
content: "\e665";
}
.icon-stopcircle-fill::before {
content: "\e666";
}
.icon-play-speed::before {
content: "\e856";
}
.icon-more::before {
content: "\e62e";
}
.icon-play::before {
content: "\e9f9";
}
.icon-pause::before {
content: "\e713";
}
.icon-dark_theme::before {
content: "\e646";
}
.icon-theme-auto-1::before {
content: "\e660";
}
.icon-theme-auto::before {
content: "\e663";
}
.icon-auto-theme-1::before {
content: "\e664";
}
.icon-auto-theme::before {
content: "\e662";
}
.icon-cols_3::before {
content: "\e65c";
}
.icon-cols_6::before {
content: "\e65d";
}
.icon-cols_2::before {
content: "\e65e";
}
.icon-cols_4::before {
content: "\e65f";
}
.icon-a-save1::before {
content: "\e65b";
}
.icon-uncollapse_all::before {
content: "\e657";
}
.icon-collapse::before {
content: "\e656";
}
.icon-rocket-launch::before {
content: "\e689";
}
.icon-user-filled::before {
content: "\e625";
}
.icon-assistant::before {
content: "\e62d";
}
.icon-assistant-filled::before {
content: "\e847";
}
.icon-save3::before {
content: "\e655";
}
.icon-fankuifaqs::before {
content: "\e7bf";
}
.icon-issues::before {
content: "\e816";
}
.icon-neicun::before {
content: "\e688";
}
.icon-collapse_all::before {
content: "\e66f";
}
.icon-down::before {
content: "\e654";
}
.icon-fenxiang::before {
content: "\e604";
}
.icon-mosaic-2::before {
content: "\e64f";
}
.icon-stars::before {
content: "\e8a8";
}
.icon-mosaic::before {
content: "\e636";
}
.icon-outline-play::before {
content: "\e653";
}
.icon-SelectionInverse::before {
content: "\eace";
}
.icon-justice1::before {
content: "\e652";
}
.icon-New_img::before {
content: "\e733";
}
.icon-new_release_outlined::before {
content: "\e66c";
}
.icon-new-releases::before {
content: "\e60f";
}
.icon-catalog::before {
content: "\e62b";
}
.icon-save2::before {
content: "\e635";
}
.icon-left-template::before {
content: "\e62a";
}
.icon-logs::before {
content: "\e6ec";
}
.icon-gpu::before {
content: "\e71e";
}
.icon-filled-gpu::before {
content: "\e6de";
}
.icon-outline-gpu::before {
content: "\e641";
}
.icon-ts-tubiao_webserver::before {
content: "\e716";
}
.icon-server::before {
content: "\e66a";
}
.icon-host::before {
content: "\e7c6";
}
.icon-playcircle::before {
content: "\e80f";
}
.icon-recreate::before {
content: "\e6a5";
}
.icon-save1::before {
content: "\e647";
}
.icon-save::before {
content: "\e67c";
}
.icon-upload_image::before {
content: "\e613";
}
.icon-sound-wave::before {
content: "\e619";
}
.icon-rank1::before {
content: "\e7cb";
}
.icon-cube::before {
content: "\e769";
}
.icon-speaker-slash::before {
content: "\ebb6";
}
.icon-random::before {
content: "\e603";
}
.icon-suijisenlin::before {
content: "\e60e";
}
.icon-stop2::before {
content: "\e8db";
}
.icon-stop::before {
content: "\e60b";
}
.icon-image::before {
content: "\e62c";
}
.icon-SpeakerSlash::before {
content: "\e661";
}
.icon-SpeakerHigh::before {
content: "\e670";
}
.icon-user_voice::before {
content: "\e667";
}
.icon-audio::before {
content: "\e985";
}
.icon-hard-disk::before {
content: "\eb1d";
}
.icon-new::before {
content: "\e612";
}
.icon-tu2::before {
content: "\e607";
}
.icon-robot::before {
content: "\e634";
}
.icon-robot1::before {
content: "\e602";
}
.icon-aizhineng::before {
content: "\e672";
}
.icon-copy::before {
content: "\e720";
}
.icon-AIzhineng::before {
content: "\e608";
}
.icon-clear::before {
content: "\e60a";
}
.icon-keyboard::before {
content: "\e61b";
}
.icon-networkerror::before {
content: "\e624";
}
.icon-external-link::before {
content: "\e66b";
}
.icon-huggingface::before {
content: "\e7d1";
}
.icon-ollama::before {
content: "\e601";
}
.icon-a-layout6-line::before {
content: "\e9ef";
}
.icon-a-Layout5::before {
content: "\e610";
}
.icon-English::before {
content: "\e8b3";
}
.icon-chinese::before {
content: "\e611";
}
.icon-yingguo::before {
content: "\e606";
}
.icon-code::before {
content: "\e84f";
}
.icon-stop1::before {
content: "\e783";
}
.icon-command::before {
content: "\e600";
}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More