Compare commits

...

400 Commits

Author SHA1 Message Date
手瓜一十雪
c7b9946d2f feat: doubt friends支持 2025-04-23 16:46:09 +08:00
手瓜一十雪
0caca473d6 feat: 34566 2025-04-23 16:18:48 +08:00
手瓜一十雪
3e5d35957d fix 2025-04-23 16:12:56 +08:00
手瓜一十雪
6b8b14aba2 fix: #963 2025-04-23 11:47:58 +08:00
手瓜一十雪
5db7a90a24 feat: 301 302自动跟随下载 2025-04-21 18:43:44 +08:00
Mlikiowa
88b86611a3 release: v4.7.34 2025-04-20 14:12:47 +00:00
手瓜一十雪
886fe2052e feat: 避免危险信息 2025-04-20 22:12:12 +08:00
手瓜一十雪
e4dd194d4a fix: #960
神经设计
2025-04-20 22:10:24 +08:00
手瓜一十雪
a47af60f58 feat: disband 2025-04-20 19:28:35 +08:00
Mlikiowa
35f24eb806 release: v4.7.33 2025-04-19 12:17:18 +00:00
手瓜一十雪
36e3119d34 feat: 支持https 面板 2025-04-19 20:16:24 +08:00
手瓜一十雪
8ff3ad824e feat: 支持环境变量禁用ffmpeg下载支持 2025-04-19 20:03:00 +08:00
手瓜一十雪
556000c002 feat: 优雅的回车登录 2025-04-19 19:59:11 +08:00
手瓜一十雪
fda050d3fe feat: 加强安全性 传输过程使用salt sha256 2025-04-19 19:50:52 +08:00
手瓜一十雪
b1047309c9 feat: 消息context增强识别 2025-04-19 11:36:27 +08:00
Mlikiowa
d766c4945e release: v4.7.32 2025-04-19 03:17:47 +00:00
手瓜一十雪
43c98c45b9 fix 2025-04-19 11:13:02 +08:00
手瓜一十雪
f7556b5af3 fix 2025-04-19 11:10:04 +08:00
手瓜一十雪
cd781c4cf6 feat: 回归ajv 2025-04-19 11:07:01 +08:00
手瓜一十雪
cd8698b157 fix 2025-04-19 11:03:03 +08:00
手瓜一十雪
d921dcddf1 Revert "package->dev"
This reverts commit 45d6ebf084.
2025-04-19 11:01:45 +08:00
手瓜一十雪
9f318ddaef Revert "feat: 区分resId和普通消息Id"
This reverts commit 7ecdd63bef.
2025-04-19 11:01:12 +08:00
手瓜一十雪
5c35ea11c3 Revert "fix: 修掉漏掉的"
This reverts commit 7a42f8c26f.
2025-04-19 11:01:06 +08:00
手瓜一十雪
3b16effff0 Revert "fix"
This reverts commit 40b06daf1e.
2025-04-19 10:59:04 +08:00
手瓜一十雪
d3a27ad701 Revert "fix:coerce"
This reverts commit dd895d7c17.
2025-04-19 10:58:56 +08:00
手瓜一十雪
2a4589e268 Revert "fix: checker"
This reverts commit 941978b578.
2025-04-19 10:58:46 +08:00
手瓜一十雪
80a34c82b9 Revert "fix: zod boolean强制转换"
This reverts commit 17ef3231df.
2025-04-19 10:58:39 +08:00
手瓜一十雪
fca7a65ee0 Revert "fix"
This reverts commit 3f6249f39c.
2025-04-19 10:57:36 +08:00
手瓜一十雪
30a75bc581 Revert "fix"
This reverts commit 54e6d5c3f2.
2025-04-19 10:57:32 +08:00
手瓜一十雪
7b365367f7 Revert "fix"
This reverts commit 41dccd98a9.
2025-04-19 10:57:28 +08:00
手瓜一十雪
3ed5f543e2 Revert "fix"
This reverts commit bd3e06520f.
2025-04-19 10:57:25 +08:00
手瓜一十雪
ceea50b116 Revert "fix: coerce"
This reverts commit fb20b2e16c.
2025-04-19 10:53:29 +08:00
手瓜一十雪
a5455e27d1 feat: 34467 2025-04-19 09:44:00 +08:00
手瓜一十雪
6f83d01321 feat: 34362 2025-04-18 18:19:12 +08:00
手瓜一十雪
c453b82e9f feat: #954 2025-04-18 12:12:18 +08:00
Mlikiowa
b7da316447 release: v4.7.31 2025-04-17 14:17:57 +00:00
手瓜一十雪
fb20b2e16c fix: coerce 2025-04-17 22:17:35 +08:00
Mlikiowa
9df7c341a9 release: v4.7.30 2025-04-17 10:07:28 +00:00
手瓜一十雪
7c113d6e04 fix: 一些问题 2025-04-17 18:07:07 +08:00
Mlikiowa
a6f22167ff release: v4.7.29 2025-04-17 09:59:29 +00:00
手瓜一十雪
d49e69735a fix: 自动化验证环境变量的ffmpeg 2025-04-17 17:59:06 +08:00
Mlikiowa
eca73eae18 release: v4.7.28 2025-04-17 09:49:05 +00:00
手瓜一十雪
d3a34dfdf9 feat: 增强异常处理 2025-04-17 17:48:13 +08:00
手瓜一十雪
623188d884 feat: 34362 2025-04-17 17:07:09 +08:00
Mlikiowa
f093f52792 release: v4.7.27 2025-04-17 06:39:48 +00:00
手瓜一十雪
d53607a118 fix 2025-04-17 14:39:30 +08:00
Mlikiowa
5f637e064a release: v4.7.26 2025-04-17 06:29:11 +00:00
手瓜一十雪
e4b21e94f5 feat: ffmpeg download auto 2025-04-17 14:28:51 +08:00
手瓜一十雪
fc37288827 fix: ffmpeg 2025-04-17 13:55:31 +08:00
Mlikiowa
dad7245a3a release: v4.7.25 2025-04-17 05:28:19 +00:00
手瓜一十雪
4190831081 fix: 扬了ffmpeg.wasm 2025-04-17 13:26:24 +08:00
Mlikiowa
c509a01d7d release: v4.7.24 2025-04-17 01:57:25 +00:00
手瓜一十雪
6d259593fd Merge pull request #953 from NapNeko/fix-zod-boolean
fix: zod boolean强制转换
2025-04-17 09:56:48 +08:00
手瓜一十雪
bd3e06520f fix 2025-04-17 09:56:12 +08:00
手瓜一十雪
41dccd98a9 fix 2025-04-17 09:54:12 +08:00
手瓜一十雪
54e6d5c3f2 fix 2025-04-17 09:52:03 +08:00
手瓜一十雪
3f6249f39c fix 2025-04-17 09:48:59 +08:00
手瓜一十雪
a888714629 fix: napcat log 2025-04-17 09:47:39 +08:00
手瓜一十雪
17ef3231df fix: zod boolean强制转换 2025-04-17 09:38:38 +08:00
Mlikiowa
cc30b51d58 release: v4.7.23 2025-04-15 10:38:47 +00:00
手瓜一十雪
9d40eacc15 fix: 34231 linux arm64 docker问题 2025-04-15 18:38:24 +08:00
Mlikiowa
a0415c5f4e release: v4.7.22 2025-04-15 04:35:48 +00:00
手瓜一十雪
941978b578 fix: checker 2025-04-15 12:34:33 +08:00
Mlikiowa
06538b9122 release: v4.7.21 2025-04-15 04:25:59 +00:00
手瓜一十雪
dd895d7c17 fix:coerce 2025-04-15 12:25:37 +08:00
Mlikiowa
9257a6cfde release: v4.7.20 2025-04-14 14:37:43 +00:00
手瓜一十雪
f8260067ab Merge pull request #944 from clansty/fix/isReverseOrder
fix: isReverseOrder
2025-04-14 21:44:42 +08:00
手瓜一十雪
40b06daf1e fix 2025-04-14 21:44:25 +08:00
手瓜一十雪
e30915a06b Merge pull request #946 from NapNeko/fix-923
feat: 区分resId和普通消息Id
2025-04-14 19:05:01 +08:00
手瓜一十雪
2ab3898d28 readme: new 2025-04-14 13:25:00 +08:00
Clansty
6e38e748b8 fix: isReverseOrder 2025-04-14 05:40:11 +08:00
手瓜一十雪
7ecdd63bef feat: 区分resId和普通消息Id 2025-04-13 20:33:25 +08:00
手瓜一十雪
8056962203 Merge pull request #943 from NapNeko/zod-refactor
fix: 修掉漏掉的
2025-04-13 20:17:08 +08:00
手瓜一十雪
7a42f8c26f fix: 修掉漏掉的 2025-04-13 20:14:35 +08:00
手瓜一十雪
6c510e42e8 Merge pull request #942 from NapNeko/zod-refactor
迁移类型校验到zod
2025-04-13 20:10:43 +08:00
手瓜一十雪
45d6ebf084 package->dev 2025-04-13 20:06:30 +08:00
手瓜一十雪
2147c4ffee 迁移类型校验到zod 2025-04-13 20:05:11 +08:00
Mlikiowa
d4ab191f34 release: v4.7.19 2025-04-12 04:25:39 +00:00
手瓜一十雪
a5e53a713b feat: 增强win 输出可读性 2025-04-12 12:23:38 +08:00
Mlikiowa
e3f965a9d6 release: v4.7.18 2025-04-12 04:04:09 +00:00
手瓜一十雪
dd00d4c8a5 fix: 小问题 2025-04-12 11:59:29 +08:00
手瓜一十雪
4d292a75fa fix 2025-04-12 11:36:46 +08:00
pk5ls20
50b6733f57 Merge pull request #938 from Fahaxikiii/patch-2
Update README.md
2025-04-12 00:11:23 +08:00
万里
19479b4b3c Update README.md
换成美国vps
2025-04-12 00:09:19 +08:00
pk5ls20
540f58d8ec Merge pull request #937 from Fahaxikiii/patch-1 2025-04-11 23:18:46 +08:00
万里
749f1dfcf9 Update README.md
服务器到期了呜呜呜
2025-04-11 22:03:28 +08:00
Mlikiowa
176691bb96 release: v4.7.17 2025-04-11 07:12:53 +00:00
手瓜一十雪
b9ec8ac9b1 feat: LL Framework适配 2025-04-11 15:12:32 +08:00
手瓜一十雪
28d973b9cb Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-04-11 14:40:31 +08:00
手瓜一十雪
a6be54937c feat: 删掉不必要的启动脚本 2025-04-11 14:40:22 +08:00
Mlikiowa
0664b9af84 release: v4.7.16 2025-04-11 06:21:19 +00:00
手瓜一十雪
407d8d1fd2 feat: 34231 2025-04-11 14:20:27 +08:00
手瓜一十雪
108897f6ad feat: shell 能力 launcher-user-34231 2025-04-11 14:18:12 +08:00
手瓜一十雪
3d2decb0ec feat: 34231 2025-04-11 12:47:25 +08:00
Mlikiowa
386b884f1b release: v4.7.15 2025-04-10 11:00:12 +00:00
手瓜一十雪
ace4da2297 fix: rkey server部署 2025-04-10 18:59:49 +08:00
Mlikiowa
a8fb48fb50 release: v4.7.14 2025-04-10 10:55:24 +00:00
手瓜一十雪
61f065c0c6 feat: rkey标准化&rkey server增强&简化rkey端部署 2025-04-10 18:54:18 +08:00
手瓜一十雪
d6cf6d120a feat: group_all_shut 2025-04-10 09:00:00 +08:00
手瓜一十雪
c20c19d8e0 feat: 更新类型 fetchUserDetailInfo 2025-04-08 10:12:18 +08:00
手瓜一十雪
bd8bbf76ab feat: moveGroupFile 2025-04-08 09:42:28 +08:00
手瓜一十雪
faccff1834 Merge pull request #927 from NapNeko/dependabot/npm_and_yarn/vite-plugin-cp-6.0.0
chore(deps-dev): bump vite-plugin-cp from 4.0.8 to 6.0.0
2025-04-08 09:19:58 +08:00
手瓜一十雪
99d3c5a117 Merge pull request #930 from clansty/feat/gfs
增加更多群文件相关功能
2025-04-08 09:19:41 +08:00
Clansty
31eb09edef feat: 重命名群文件 2025-04-08 05:14:53 +08:00
Clansty
4180c2d754 feat: 群文件转存永久 2025-04-08 04:40:34 +08:00
Clansty
68f5deedff feat: 移动群文件 2025-04-08 02:48:48 +08:00
dependabot[bot]
9f72196414 chore(deps-dev): bump vite-plugin-cp from 4.0.8 to 6.0.0
Bumps [vite-plugin-cp](https://github.com/fengxinming/vite-plugins/tree/HEAD/packages/vite-plugin-cp) from 4.0.8 to 6.0.0.
- [Commits](https://github.com/fengxinming/vite-plugins/commits/HEAD/packages/vite-plugin-cp)

---
updated-dependencies:
- dependency-name: vite-plugin-cp
  dependency-version: 6.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-07 09:33:04 +00:00
手瓜一十雪
b32efa9131 fix: 更新逻辑 2025-04-05 11:45:21 +08:00
pk5ls20
3cf502fea3 chore: improve log output for protocol fetch with multiple messages 2025-04-04 01:59:31 +08:00
手瓜一十雪
863a953ae1 style: lint 2025-04-03 15:06:34 +08:00
手瓜一十雪
a44104d8f7 Update OneBotAction.ts 2025-04-03 15:03:00 +08:00
手瓜一十雪
f602bbb0cf fix: 启用类型强制转换 2025-04-03 14:52:33 +08:00
手瓜一十雪
2807ff5927 fix: 刷新群头衔缓存 2025-04-03 14:46:56 +08:00
手瓜一十雪
4fb8e6a4da fix: typo 2025-04-03 14:44:59 +08:00
手瓜一十雪
7ec61f089d fix 2025-04-02 21:40:10 +08:00
手瓜一十雪
f7a500a8cf feat: GroupMemberTitle 2025-04-02 21:30:25 +08:00
Mlikiowa
2b319bd694 release: v4.7.13 2025-04-02 04:05:02 +00:00
手瓜一十雪
24cf7c01f8 fix: 清理文件 2025-04-02 12:04:05 +08:00
手瓜一十雪
aa50e73909 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-04-02 11:58:59 +08:00
手瓜一十雪
d9b33b5439 fix: file clean 2025-04-02 11:58:56 +08:00
Mlikiowa
da58c6bec0 release: v4.7.12 2025-04-02 03:54:37 +00:00
手瓜一十雪
c4cbac4331 fix: #918 2025-04-02 11:51:51 +08:00
手瓜一十雪
ac26a99143 fix: type 2025-04-02 10:49:48 +08:00
手瓜一十雪
640252d391 fix: 初始化后清理 2025-04-02 10:05:57 +08:00
Mlikiowa
258a1dda5e release: v4.7.11 2025-04-01 12:44:06 +00:00
手瓜一十雪
53a7ce2e46 feat: 优化webui快速登录&优化代码整体逻辑 2025-04-01 20:43:46 +08:00
手瓜一十雪
291e2fd8fd feat: 33800 2025-04-01 20:33:14 +08:00
手瓜一十雪
f180c7698f feat: 文件清理quene 2025-04-01 20:22:46 +08:00
手瓜一十雪
183d6f3011 feat: no_cache 2025-04-01 19:54:01 +08:00
bietiaop
ba71d7ad03 fix: #908 2025-03-29 21:26:34 +08:00
手瓜一十雪
26cfaac3bd fix: 增强异常处理 2025-03-29 10:50:27 +08:00
手瓜一十雪
556c8b24c0 fix 2025-03-28 16:55:19 +08:00
Mlikiowa
31f0f527b7 release: v4.7.10 2025-03-27 04:36:54 +00:00
手瓜一十雪
b2e0cab702 feat: 好友等级 2025-03-27 12:35:05 +08:00
Mlikiowa
3e3609e0f2 release: v4.7.9 2025-03-27 04:16:25 +00:00
手瓜一十雪
83e73d9842 fix: 修复一些数据问题 2025-03-27 12:15:53 +08:00
手瓜一十雪
673a175ddf fix: 烘焙raw 2025-03-26 21:51:51 +08:00
手瓜一十雪
12eacd3530 feat: 移除ws 2025-03-21 20:54:59 +08:00
手瓜一十雪
030f0551fd feat: 移除部分淘汰代码 2025-03-21 20:47:38 +08:00
Mlikiowa
47f5947410 release: v4.7.8 2025-03-21 12:43:58 +00:00
手瓜一十雪
aaefa2e83c fix: error 2025-03-21 20:43:41 +08:00
Mlikiowa
f8e92f7c8d release: v4.7.7 2025-03-21 11:48:29 +00:00
手瓜一十雪
b6430e6eb6 fix: 优化 2025-03-21 19:44:31 +08:00
手瓜一十雪
e60605c7bb feat: 简化代码逻辑 2025-03-21 19:40:47 +08:00
手瓜一十雪
58d2bd3c81 Revert "fix: #883"
This reverts commit 79aa1dc67f.
2025-03-20 10:57:57 +08:00
手瓜一十雪
6534d05b76 Revert "fix"
This reverts commit 2d7de174c5.
2025-03-20 10:57:54 +08:00
手瓜一十雪
2d7de174c5 fix 2025-03-20 10:32:30 +08:00
手瓜一十雪
79aa1dc67f fix: #883 2025-03-20 10:32:13 +08:00
Mlikiowa
7792ad9ea0 release: v4.7.6 2025-03-19 07:35:12 +00:00
手瓜一十雪
be6671923b feat: 33139 2025-03-19 15:34:33 +08:00
手瓜一十雪
0fa1b3f044 feat: 33139 2025-03-19 15:26:55 +08:00
手瓜一十雪
4ab751696b Revert "fix: image size"
This reverts commit 2759a34d96.
2025-03-19 11:58:19 +08:00
手瓜一十雪
dce4eedf7d Revert "fix: moduleResolution"
This reverts commit 9ab776d53a.
2025-03-19 11:58:16 +08:00
手瓜一十雪
129b67b751 Revert "chore(deps-dev): bump image-size from 1.2.0 to 2.0.1"
This reverts commit e3feb6a73c.
2025-03-19 11:57:55 +08:00
手瓜一十雪
9ab776d53a fix: moduleResolution 2025-03-19 11:54:29 +08:00
手瓜一十雪
2759a34d96 fix: image size 2025-03-19 11:09:57 +08:00
手瓜一十雪
2f9f42750e Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-03-19 10:45:59 +08:00
手瓜一十雪
30abd1f904 fix: #884 2025-03-19 10:45:56 +08:00
手瓜一十雪
008075466e Merge pull request #887 from NapNeko/dependabot/npm_and_yarn/eslint-import-resolver-typescript-4.0.0
chore(deps-dev): bump eslint-import-resolver-typescript from 3.9.1 to 4.0.0
2025-03-19 10:39:33 +08:00
手瓜一十雪
5b4035c320 Merge pull request #888 from NapNeko/dependabot/npm_and_yarn/image-size-2.0.1
chore(deps-dev): bump image-size from 1.2.0 to 2.0.1
2025-03-19 10:39:21 +08:00
dependabot[bot]
e3feb6a73c chore(deps-dev): bump image-size from 1.2.0 to 2.0.1
Bumps [image-size](https://github.com/image-size/image-size) from 1.2.0 to 2.0.1.
- [Release notes](https://github.com/image-size/image-size/releases)
- [Commits](https://github.com/image-size/image-size/compare/v1.2.0...v2.0.1)

---
updated-dependencies:
- dependency-name: image-size
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 09:23:32 +00:00
dependabot[bot]
40fe73317d chore(deps-dev): bump eslint-import-resolver-typescript
Bumps [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) from 3.9.1 to 4.0.0.
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/master/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-import-resolver-typescript/compare/v3.9.1...v4.0.0)

---
updated-dependencies:
- dependency-name: eslint-import-resolver-typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 09:21:48 +00:00
pk5ls20
073745030c fix: #843
- maybe a temporary solution
2025-03-16 20:07:47 +08:00
Mlikiowa
c523437506 release: v4.7.5 2025-03-16 08:08:15 +00:00
手瓜一十雪
9eef570d37 fix: network prepare 2025-03-16 16:07:51 +08:00
Mlikiowa
be37b8cbbd release: v4.7.4 2025-03-16 07:57:45 +00:00
手瓜一十雪
c635496677 fix: msf Status 2025-03-16 15:57:27 +08:00
Mlikiowa
8753ecfd92 release: v4.7.3 2025-03-16 03:57:48 +00:00
手瓜一十雪
5eda1f2870 fix: quick login 2025-03-16 11:57:28 +08:00
Mlikiowa
d5a60074f7 release: v4.7.2 2025-03-16 03:55:17 +00:00
手瓜一十雪
91df57d932 fix: async error 2025-03-16 11:55:00 +08:00
Mlikiowa
e27d4c4302 release: v4.7.1 2025-03-16 03:40:48 +00:00
手瓜一十雪
55847f6e10 fix: quick login 延迟问题 2025-03-16 11:39:38 +08:00
手瓜一十雪
b39d8bae27 fix: login timer / add:不规范的promise 2025-03-16 10:42:15 +08:00
手瓜一十雪
b0cf23f775 doc: security 2025-03-16 09:36:27 +08:00
手瓜一十雪
c641246056 doc: code of conduct 2025-03-16 09:33:06 +08:00
Mlikiowa
1e5bc9bbea release: v4.7.0 2025-03-16 01:13:17 +00:00
手瓜一十雪
99b504b5f6 fix: #880 2025-03-16 09:12:52 +08:00
Mlikiowa
1146454fec release: v4.6.9 2025-03-15 10:58:09 +00:00
手瓜一十雪
805e014a75 fix: #877 2025-03-15 18:54:51 +08:00
Mlikiowa
d3acd1efc1 release: v4.6.8 2025-03-14 10:13:29 +00:00
手瓜一十雪
9fcd218a5a fix: #873 2025-03-14 18:12:58 +08:00
手瓜一十雪
d6a0830cfe fix: #875 2025-03-14 18:07:03 +08:00
手瓜一十雪
40a63b9c66 fix: #870 2025-03-14 17:53:03 +08:00
手瓜一十雪
eeb19a04cc fix: packet异常 2025-03-14 17:39:37 +08:00
Mlikiowa
91e457eb03 release: v4.6.7 2025-03-09 08:31:07 +00:00
手瓜一十雪
78d1919d7f feat: 32896 2025-03-09 16:30:43 +08:00
手瓜一十雪
8393acf173 Merge pull request #856 from HDTianRu/main
feat: 额外返回原msgSeq条目
2025-03-09 10:09:41 +08:00
手瓜一十雪
bca152a047 feat: readme 翻新 2025-03-09 10:08:49 +08:00
HDTianRu
6a15908a93 feat: 额外返回原msgSeq条目 2025-03-08 16:36:17 +08:00
bietiaop
c626bbab74 fix: #854 2025-03-07 10:25:38 +08:00
Mlikiowa
c5c7dcc6f2 release: v4.6.6 2025-03-06 10:51:30 +00:00
手瓜一十雪
03dafe727e fix: win 2025-03-06 18:51:05 +08:00
Mlikiowa
744921c45e release: v4.6.5 2025-03-06 10:09:45 +00:00
手瓜一十雪
abc4a4dcba feat: 32793 2025-03-06 18:09:14 +08:00
Mlikiowa
7e0da2f929 release: v4.6.4 2025-03-05 13:15:11 +00:00
手瓜一十雪
a3b70d0f1f fix 2025-03-05 21:14:52 +08:00
Mlikiowa
d291724f06 release: v4.6.3 2025-03-03 09:17:03 +00:00
手瓜一十雪
122a9ca2cc feat: o3拦截 2025-03-03 17:16:36 +08:00
手瓜一十雪
48aaddd32b feat:rkey 2025-03-03 12:28:55 +08:00
手瓜一十雪
47401af856 feat: searchMsgWithKeywords 2025-03-02 16:07:27 +08:00
Mlikiowa
709adfd812 release: v4.6.2 2025-03-02 07:11:16 +00:00
手瓜一十雪
038d0c5412 fix: #785 2025-03-02 14:55:47 +08:00
手瓜一十雪
6bb4362ed4 feat: 32721 2025-03-02 14:36:11 +08:00
手瓜一十雪
e617f9452d fix: #841 2025-03-02 14:32:21 +08:00
手瓜一十雪
6d8bb49a37 fix: #837 2025-03-02 14:27:09 +08:00
手瓜一十雪
4f6073ee86 fix: 837 2025-03-02 14:26:28 +08:00
手瓜一十雪
2e7176304b fix: #843 2025-03-02 14:24:51 +08:00
Mlikiowa
e36cf11004 release: v4.6.1 2025-02-27 08:35:02 +00:00
手瓜一十雪
0e49e17f68 feat: 32690 2025-02-27 16:34:09 +08:00
手瓜一十雪
524de45f6b Merge pull request #827 from NapNeko/dependabot/npm_and_yarn/globals-16.0.0
chore(deps-dev): bump globals from 15.15.0 to 16.0.0
2025-02-27 16:18:17 +08:00
手瓜一十雪
85741a4b60 feat: 32690 2025-02-27 16:14:39 +08:00
dependabot[bot]
f9ccb8c978 chore(deps-dev): bump globals from 15.15.0 to 16.0.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.15.0 to 16.0.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.15.0...v16.0.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 09:06:38 +00:00
手瓜一十雪
ea3d069e49 feat: vsc build dev体验增强 2025-02-23 17:54:19 +08:00
Mlikiowa
3e6024f183 release: v4.6.0 2025-02-23 09:31:55 +00:00
Mlikiowa
337871693a release: v4.5.24 2025-02-23 09:31:19 +00:00
手瓜一十雪
2d921c4577 feat: sisi的妙妙rkey 2025-02-23 17:30:01 +08:00
手瓜一十雪
9accff7323 fix: ts warning 2025-02-23 17:28:30 +08:00
手瓜一十雪
88b1ee8c31 docs: todo #819 2025-02-23 17:17:52 +08:00
手瓜一十雪
3ac618bb4e fix: #822 2025-02-23 17:01:00 +08:00
手瓜一十雪
0051df3741 fix: #824 2025-02-23 16:57:55 +08:00
手瓜一十雪
7eb4e010b0 Merge pull request #823 from NapNeko/refactor-worker
refactor: 即刻起逐出piscina
2025-02-23 14:31:33 +08:00
手瓜一十雪
33cc23ada3 refactor: 即刻起逐出piscina 2025-02-23 14:29:26 +08:00
手瓜一十雪
e5aee372e3 fix: 调整依赖 2025-02-23 13:40:47 +08:00
手瓜一十雪
6b6ce4a761 fix: 依赖迁移到dev 2025-02-22 12:59:37 +08:00
手瓜一十雪
8c4ea7f8f2 fix: 异常代码 2025-02-22 11:57:48 +08:00
手瓜一十雪
c8b268b806 fix: #791 2025-02-22 11:50:54 +08:00
手瓜一十雪
cf5e0e0f14 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-18 17:08:26 +08:00
手瓜一十雪
7b79f9cc17 fix: 日志显示 2025-02-18 17:08:24 +08:00
Mlikiowa
708d599966 release: v4.5.23 2025-02-18 08:56:25 +00:00
手瓜一十雪
1ecd5b78e6 feat: 文件移除path字段增强部分能力 2025-02-18 16:55:43 +08:00
手瓜一十雪
fca2e3c51a style: remove debug 2025-02-18 16:52:30 +08:00
手瓜一十雪
95ea761b2d feat: get_private_file_url 2025-02-18 16:51:51 +08:00
手瓜一十雪
6b3bfa1ee9 fix #810 2025-02-18 13:24:37 +08:00
bietiaop
df3e302a9d fix: #802 2025-02-14 21:26:16 +08:00
pk5ls20
c88a68c9a8 fix: typo x2 2025-02-14 20:52:31 +08:00
Mlikiowa
92d01b9cdd release: v4.5.22 2025-02-14 10:36:03 +00:00
手瓜一十雪
fe04fa5986 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-14 17:41:40 +08:00
手瓜一十雪
c382f541b4 fix: 优化文件处理错误信息并简化下载逻辑 2025-02-14 17:41:25 +08:00
手瓜一十雪
f420527207 Update msg.ts 2025-02-14 17:41:03 +08:00
手瓜一十雪
e0c83ebf79 fix: #793 2025-02-14 17:15:19 +08:00
手瓜一十雪
c7fb18fc08 feat: 补全一些type 2025-02-14 15:39:06 +08:00
手瓜一十雪
2db8ab937d feat: GetUnidirectionalFriendList router 2025-02-14 15:06:36 +08:00
手瓜一十雪
819f5dd8e5 fix: #785 2025-02-14 14:50:00 +08:00
手瓜一十雪
d4a8ed735e fix: #789 2025-02-14 14:48:36 +08:00
手瓜一十雪
f07e3bb4d5 fix: type 2025-02-14 14:44:10 +08:00
手瓜一十雪
fa5ef0c221 fix: #797 2025-02-14 14:41:16 +08:00
手瓜一十雪
da7499ec0b Merge pull request #790 from NapNeko/dependabot/npm_and_yarn/esbuild-0.25.0
chore(deps-dev): bump esbuild from 0.24.0 to 0.25.0
2025-02-14 13:51:47 +08:00
Mlikiowa
d2f4327e44 release: v4.5.21 2025-02-12 18:57:14 +00:00
pk5ls20
2eba640180 fix: typo 2025-02-13 02:56:07 +08:00
dependabot[bot]
29ae55f340 chore(deps-dev): bump esbuild from 0.24.0 to 0.25.0
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 08:16:17 +00:00
Mlikiowa
3d2bca3f9f release: v4.5.20 2025-02-09 05:05:52 +00:00
手瓜一十雪
7fd8c0c822 style:lint 2025-02-09 13:00:54 +08:00
手瓜一十雪
a9e9c81505 refactor: data recv 2025-02-09 13:00:17 +08:00
手瓜一十雪
e8cc68bdea style:lint 2025-02-09 12:53:42 +08:00
手瓜一十雪
9e51a661a4 fix: #761 2025-02-09 12:53:10 +08:00
bietiaop
a167aaf55f style: 修改首页卡片色适配主题 2025-02-09 12:28:57 +08:00
bietiaop
a54ecbcaa0 style: 修改侧边栏标题色适配主题 2025-02-09 12:21:34 +08:00
bietiaop
788462cdfa fix: 修复heroui primary色 2025-02-09 12:13:43 +08:00
bietiaop
45c5965b99 style: 增加heroui主题色 2025-02-09 12:11:27 +08:00
bietiaop
ce7614de46 fix: 缺少default 2025-02-09 12:00:02 +08:00
bietiaop
9f78e1ce1e feat: 预定义主题 2025-02-09 11:58:46 +08:00
pk5ls20
2c7b0625e8 chore: format 2025-02-09 01:35:37 +08:00
pk5ls20
c3a5da9be1 feat: #768 2025-02-09 01:33:56 +08:00
bietiaop
ca796e1920 feat: 设置快速登录QQ & 自定义webui主题色
feat: 设置快速登录QQ & 自定义webui主题色
2025-02-09 00:54:27 +08:00
bietiaop
7ce04cf781 final 2025-02-09 00:47:00 +08:00
bietiaop
024a3eb760 fix 2025-02-09 00:18:14 +08:00
bietiaop
1702f429b4 fix 2025-02-09 00:17:49 +08:00
bietiaop
96d79cf495 fix 2025-02-08 23:45:33 +08:00
bietiaop
a6a11a7026 fix 2025-02-08 23:38:30 +08:00
bietiaop
970a49e2a5 fix: 猪咪 2025-02-08 23:05:48 +08:00
bietiaop
2e013ed4f5 fix 2025-02-08 22:43:53 +08:00
bietiaop
f8c396b1fe feat(webui): 快速登录config 2025-02-08 21:16:49 +08:00
手瓜一十雪
b54870cb60 fix 2025-02-08 21:03:59 +08:00
bietiaop
84318acb18 feat(webui): theme 2025-02-08 21:01:29 +08:00
手瓜一十雪
a11a042b93 docs: update 2025-02-08 20:22:51 +08:00
Mlikiowa
8a8aa8f62c release: v4.5.18 2025-02-08 09:43:06 +00:00
手瓜一十雪
93f78f4db5 feat: #780 2025-02-08 17:34:31 +08:00
手瓜一十雪
404bfdd5e6 fix: #783 2025-02-08 17:00:11 +08:00
Mlikiowa
e4577dc2f1 release: v4.5.17 2025-02-07 12:40:47 +00:00
pk5ls20
5c932e5a27 fix: native rkey 2025-02-07 19:20:35 +08:00
Mlikiowa
4bd63c6267 release: v4.5.16 2025-02-07 10:02:35 +00:00
bietiaop
aabe24f903 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-07 18:00:31 +08:00
bietiaop
69cebd7fbc feat: 提示修改默认密码 2025-02-07 18:00:22 +08:00
Mlikiowa
8da371176a release: v4.5.15 2025-02-07 09:52:51 +00:00
手瓜一十雪
dd08adf1d1 fix 2025-02-07 17:43:08 +08:00
手瓜一十雪
2f67bef139 fix: #775 2025-02-07 17:25:48 +08:00
手瓜一十雪
8968c51cdc fix: 砍掉mac pty 沙盒权限不足 2025-02-07 17:11:10 +08:00
手瓜一十雪
f2fdcc9289 feat: webui体验优化 2025-02-07 13:56:48 +08:00
手瓜一十雪
aa3a575cbe feat: 优化初始化步骤 2025-02-07 13:26:48 +08:00
bietiaop
11816d038d fix: #776 2025-02-06 20:10:11 +08:00
Mlikiowa
6a990edb38 release: v4.5.14 2025-02-06 09:17:22 +00:00
手瓜一十雪
fa12865924 fix: error 2025-02-06 17:10:30 +08:00
Mlikiowa
ecdd717742 release: v4.5.12 2025-02-06 08:23:07 +00:00
bietiaop
6851334af9 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-06 15:29:04 +08:00
bietiaop
9051b29565 feat: 字体修改#771 2025-02-06 15:28:42 +08:00
手瓜一十雪
95c7d3dfbd fix: remove __dirname 2025-02-06 15:28:24 +08:00
手瓜一十雪
bc1148c00a fix: require_dlopen 2025-02-06 15:25:47 +08:00
Mlikiowa
d4556d9299 release: v4.5.11 2025-02-06 03:13:17 +00:00
pk5ls20
5d389a2359 fix: fake forwardMsg construct 2025-02-06 01:09:23 +08:00
Mlikiowa
305116874b release: v4.5.10 2025-02-05 11:49:14 +00:00
bietiaop
b08a29897f fix: #769 2025-02-05 19:45:30 +08:00
Mlikiowa
b59c1d9122 release: v4.5.9 2025-02-05 11:14:25 +00:00
手瓜一十雪
adb9cea701 Merge pull request #765 from NapNeko/fix/multi-forward-protocol-fetch
fix: #721
2025-02-05 19:08:08 +08:00
Mlikiowa
5e148d2e82 release: v4.5.8 2025-02-05 11:02:28 +00:00
手瓜一十雪
a0d780558e fix 2025-02-05 19:01:14 +08:00
Mlikiowa
ad56065a4e release: v4.5.7 2025-02-05 07:10:27 +00:00
手瓜一十雪
f5dee80b6e Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-05 15:09:27 +08:00
手瓜一十雪
9cc75881b8 fix: arm64 2025-02-05 14:51:12 +08:00
bietiaop
593fb13b61 style: 语义化样式 2025-02-05 10:38:12 +08:00
pk5ls20
fca90592d6 try fix: #755 2025-02-05 08:29:37 +08:00
pk5ls20
d6848e2855 fix: #721 2025-02-05 08:07:58 +08:00
bietiaop
7539a4129f fix: 获取歌单 2025-02-04 22:14:23 +08:00
bietiaop
5402574266 feat: AI更新总结 2025-02-04 22:03:37 +08:00
Mlikiowa
853175aa1a release: v4.5.6 2025-02-04 13:24:46 +00:00
手瓜一十雪
feb84809ec fix: #761 2025-02-04 21:22:36 +08:00
bietiaop
a812c568e4 fix: 文件预览 2025-02-04 21:12:13 +08:00
bietiaop
11db25e355 fix: 文件预览 2025-02-04 21:08:28 +08:00
手瓜一十雪
ecd2fba629 fix: #762 2025-02-04 20:42:13 +08:00
Mlikiowa
a6763cf5a1 release: v4.5.5 2025-02-04 11:50:37 +00:00
手瓜一十雪
c9e91a9b94 fix: defalut config 2025-02-04 19:49:56 +08:00
Mlikiowa
43fb62c5bd release: v4.5.4 2025-02-04 11:35:51 +00:00
手瓜一十雪
cb8727d487 fix: reload and parse msg 2025-02-04 19:34:51 +08:00
Mlikiowa
a94e03e2fd release: v4.5.3 2025-02-04 10:16:07 +00:00
手瓜一十雪
425c3c6432 fix: 避免重复reload 2025-02-04 18:14:13 +08:00
手瓜一十雪
89b9610016 fix: 避免read异常 2025-02-04 18:13:42 +08:00
手瓜一十雪
62fe88f868 Merge pull request #760 from NapNeko/config-refactor
refactor
2025-02-04 18:09:57 +08:00
手瓜一十雪
11a7f5fade refactor 2025-02-04 18:09:30 +08:00
bietiaop
fbde997f7c style: 调整样式 2025-02-04 17:58:38 +08:00
bietiaop
26734a35ef fix: 文件下载 2025-02-04 15:31:10 +08:00
Mlikiowa
715c4ac534 release: v4.5.2 2025-02-04 06:52:11 +00:00
bietiaop
bd4b0885a1 fix: 预览 2025-02-04 14:47:38 +08:00
手瓜一十雪
e3c7af3d91 fix: 解决nonebot可能卡死问题 2025-02-04 14:42:14 +08:00
手瓜一十雪
a7ee21bfd8 fix: #757 2025-02-04 14:34:55 +08:00
手瓜一十雪
d0f51d92ac feat: tailwind css 2025-02-04 13:52:53 +08:00
手瓜一十雪
e6dc148ea2 fix: diy status问题 2025-02-04 13:44:35 +08:00
手瓜一十雪
514ab6637f feat: 全局字体优化 2025-02-04 13:37:11 +08:00
bietiaop
377794abe8 style: font & terminal
style: font & terminal
2025-02-04 13:09:00 +08:00
bietiaop
0f3251f35b fix: 字体、终端样式 2025-02-04 12:59:51 +08:00
手瓜一十雪
8002dc5bc5 fix 2025-02-04 00:16:59 +08:00
手瓜一十雪
c75a13dcf4 fix 2025-02-04 00:14:15 +08:00
Mlikiowa
91d153bb9d release: v4.5.1 2025-02-03 12:48:00 +00:00
bietiaop
b32f9fa397 feat: 文件下载/上传 2025-02-03 19:56:33 +08:00
Mlikiowa
80593730ae release: v4.4.20 2025-02-03 08:36:59 +00:00
手瓜一十雪
090d54a78d fix: typo 2025-02-03 16:36:25 +08:00
Mlikiowa
b7d1fb181c release: v4.4.19 2025-02-03 08:34:24 +00:00
手瓜一十雪
6e56693ca7 feat: 支持set_diy_online_status 2025-02-03 16:33:31 +08:00
Mlikiowa
7403db9b20 release: v4.4.18 2025-02-03 07:05:56 +00:00
手瓜一十雪
9d167cd883 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2025-02-03 15:05:16 +08:00
手瓜一十雪
197eec40ad fix 2025-02-03 15:05:12 +08:00
Mlikiowa
07819a6618 release: v4.4.17 2025-02-03 06:50:58 +00:00
手瓜一十雪
b72156866d fix: broken pipe 2025-02-03 14:50:11 +08:00
手瓜一十雪
59a7d12a8c style: lint 2025-02-03 14:29:38 +08:00
手瓜一十雪
179351b23a Merge pull request #754 from NapNeko/qrcode-refactor
fix: thumb残留
2025-02-03 14:11:22 +08:00
手瓜一十雪
790809e8e5 fix: thumb残留 2025-02-03 14:09:51 +08:00
手瓜一十雪
1414a8a8c9 Merge pull request #753 from NapNeko/qrcode-refactor
refactor: qrcode to ts
2025-02-03 14:02:48 +08:00
bietiaop
9ab41734a5 fix: piscina src 2025-02-03 13:33:18 +08:00
手瓜一十雪
03cace2ea1 优化依赖 2025-02-03 13:07:05 +08:00
手瓜一十雪
c7371ab869 refactor: qrcode to ts 2025-02-03 12:51:50 +08:00
手瓜一十雪
b32d4b618c fix: 必须清理并回收 2025-02-03 11:40:58 +08:00
手瓜一十雪
3a27f37686 fix: pcm清理 2025-02-03 11:37:39 +08:00
手瓜一十雪
fe2d21979d Merge pull request #749 from NapNeko/type-force
style: Type force
2025-02-03 11:13:55 +08:00
手瓜一十雪
48b1f3d4f0 Merge branch 'main' into type-force 2025-02-03 11:13:46 +08:00
手瓜一十雪
93ed589ac7 Merge pull request #745 from NapNeko/dev-terminal
feat: 文件管理 & 系统终端
2025-02-03 11:13:14 +08:00
手瓜一十雪
96de9e2c16 style: 简化loader 避免全局error 2025-02-03 10:40:33 +08:00
手瓜一十雪
b25f9d3bec feat: 摇树生成&多平台统一改造 2025-02-03 10:33:10 +08:00
手瓜一十雪
15854c605b style: 强类型大法 2025-02-02 23:22:21 +08:00
手瓜一十雪
ac193cc94a style: lint 2025-02-02 20:17:28 +08:00
手瓜一十雪
d626f872e6 feat: 类型规范 2025-02-02 20:16:11 +08:00
bietiaop
3eb66fa34a fix: 关闭终端启动QQ 2025-02-02 15:34:53 +08:00
bietiaop
0fdd0175b7 fix: 重复关闭 2025-02-02 15:09:38 +08:00
pk5ls20
dec9b477e0 fix: #747 2025-02-02 14:51:36 +08:00
bietiaop
a0a4b0dd1d fix: 频率限制 2025-02-02 14:47:34 +08:00
bietiaop
8dc6da56a7 fix: node-pty 2025-02-02 14:33:39 +08:00
bietiaop
b4e07aacfe chore(terminal): 使用prebuild 2025-02-02 12:59:00 +08:00
bietiaop
19b47f0f42 fix: id生成使用uuid 2025-02-02 12:00:55 +08:00
bietiaop
f9ef3d63c7 fix: id生成使用uuid 2025-02-02 11:57:28 +08:00
bietiaop
2b574d33b5 fix: 更换重命名图标防止误解 2025-02-02 11:46:04 +08:00
bietiaop
6039e9bb46 feat: 文件管理 2025-02-02 11:37:58 +08:00
手瓜一十雪
adfd4b043f docs: fix 2025-02-02 11:02:54 +08:00
bietiaop
719189be55 feat: file manager 2025-02-01 22:47:51 +08:00
bietiaop
ef9907f4b6 style: 优化样式 2025-02-01 20:51:45 +08:00
bietiaop
16b7447df1 style: 优化样式 2025-02-01 20:47:54 +08:00
bietiaop
4157746478 feat: 系统终端 2025-02-01 20:35:01 +08:00
bietiaop
5120786708 Merge branch 'dev-terminal' of https://github.com/NapNeko/NapCatQQ into dev-terminal 2025-02-01 13:44:18 +08:00
bietiaop
0176fa75ef dev: terminal 2025-02-01 13:41:20 +08:00
bietiaop
e6968f2d80 fix(webui): name重复问题 2025-02-01 11:44:30 +08:00
bietiaop
c0dd8a53e8 chore(dep): 更新依赖,移除overrides(strtok3已经修复) 2025-01-31 19:05:40 +08:00
bietiaop
3cb3135235 feat: 登录状态机 2025-01-31 18:48:46 +08:00
bietiaop
28182cac64 chore(dep): 更新webui依赖 2025-01-31 12:07:57 +08:00
bietiaop
73b80d2482 release: v4.4.16 2025-01-30 10:42:46 +08:00
bietiaop
f22eb22409 release: v4.4.16 2025-01-30 10:12:35 +08:00
bietiaop
4a95b17a47 feat(webui): 修改token 2025-01-29 22:08:45 +08:00
bietiaop
f4a71159fd fix(dep): 尝试解决peek-readable依赖问题 2025-01-29 21:34:59 +08:00
bietiaop
c0431e3dc2 feat(webui_api): update token 2025-01-29 20:40:23 +08:00
Mlikiowa
7f87cee282 release: v4.4.15 2025-01-27 11:53:31 +00:00
手瓜一十雪
c24c704439 fix: clone object 2025-01-27 19:53:03 +08:00
Mlikiowa
232e5d55b8 release: v4.4.14 2025-01-27 11:31:15 +00:00
手瓜一十雪
da24ae7e1c fix: 空json5 2025-01-27 19:30:45 +08:00
Mlikiowa
8fc13f8a8f release: v4.4.13 2025-01-27 11:26:17 +00:00
507 changed files with 14956 additions and 3344 deletions

View File

@@ -12,7 +12,7 @@ insert_final_newline = true
# Set default charset
charset = utf-8
# 2 space indentation
# 4 space indentation
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
indent_style = space
indent_size = 4
@@ -21,4 +21,4 @@ indent_size = 4
charset = latin1
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.

3
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Develop
node_modules/
package-lock.json
pnpm-lock.yaml
out/
dist/
/src/core.lib/common/
@@ -13,4 +14,4 @@ devconfig/*
# Build
*.db
checkVersion.sh
bun.lockb
bun.lockb

115
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,115 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "dev:shell",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev:shell"
]
},
{
"type": "node",
"request": "launch",
"name": "build:shell",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"build:shell"
]
},
{
"type": "node",
"request": "launch",
"name": "build:universal",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"build:universal"
]
},
{
"type": "node",
"request": "launch",
"name": "build:framework",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"build:framework"
]
},
{
"type": "node",
"request": "launch",
"name": "build:webui",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"build:webui"
]
},
{
"type": "node",
"request": "launch",
"name": "dev:universal",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev:universal"
]
},
{
"type": "node",
"request": "launch",
"name": "dev:framework",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev:framework"
]
},
{
"type": "node",
"request": "launch",
"name": "dev:webui",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev:webui"
]
},
{
"type": "node",
"request": "launch",
"name": "lint",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"lint"
]
},
{
"type": "node",
"request": "launch",
"name": "depend",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"depend"
]
},
{
"type": "node",
"request": "launch",
"name": "dev:depend",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"dev:depend"
]
}
]
}

View File

@@ -5,5 +5,8 @@
".env.universal": ".env.*",
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
}
},
"css.customData": [
".vscode/tailwindcss.json"
],
}

55
.vscode/tailwindcss.json vendored Normal file
View File

@@ -0,0 +1,55 @@
{
"version": 1.1,
"atDirectives": [
{
"name": "@tailwind",
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
}
]
},
{
"name": "@apply",
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that youd like to extract to a new component.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
}
]
},
{
"name": "@responsive",
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
}
]
},
{
"name": "@screen",
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
}
]
},
{
"name": "@variants",
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
}
]
}
]
}

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
nanaeonn@outlook.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,67 +1,64 @@
<div align="center">
# NapCat
![NapCatQQ](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Fnewlogo.png&name=1&owner=1&pattern=Diagonal+Stripes&stargazers=1&theme=Auto)
_Modern protocol-side framework implemented based on NTQQ._
> 云起兮风生,心向远方兮路未曾至.
</div>
---
## 欢迎回家
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 特性介绍
- [x] **安装简单**:就算是笨蛋也能使用
- [x] **性能友好**:就算是低内存也能使用
- [x] **接口丰富**:就算是没有也能使用
- [x] **稳定好用**:就算是被捉也能使用
## Welcome
+ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
- NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 使用框架
## Feature
+ **Easy to Use**
- 作为初学者能够轻松使用.
+ **Quick and Efficient**
- 在低内存操作系统长时运行.
+ **Rich API Interface**
- 完整实现了大部分标准接口.
+ **Stable and Reliable**
- 持续稳定的开发与维护.
## Quick Start
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必查看如下文档看使用教程
### 文档地址
## Link
[Cloudflare.Worker](https://doc.napneko.icu/)
| Docs | [![Github.IO](https://img.shields.io/badge/docs%20on-Github.IO-orange)](https://napneko.github.io/) | [![Cloudflare.Worker](https://img.shields.io/badge/docs%20on-Cloudflare.Worker-black)](https://doc.napneko.icu/) | [![Cloudflare.HKServer](https://img.shields.io/badge/docs%20on-Cloudflare.HKServer-informational)](https://napcat.napneko.icu/) |
|:-:|:-:|:-:|:-:|
[Cloudflare.HKServer](https://napcat.napneko.icu/)
| Docs | [![Cloudflare.Pages](https://img.shields.io/badge/docs%20on-Cloudflare.Pages-blue)](https://napneko.pages.dev/) | [![Server.Other](https://img.shields.io/badge/docs%20on-Server.Other-green)](https://napcat.cyou/) | [![NapCat.Wiki](https://img.shields.io/badge/docs%20on-NapCat.Wiki-red)](https://www.napcat.wiki) |
|:-:|:-:|:-:|:-:|
[Github.IO](https://napneko.github.io/)
| Contact | [![QQ Group#1](https://img.shields.io/badge/QQ%20Group%231-Join-blue)](https://qm.qq.com/q/I6LU87a0Yq) | [![QQ Group#2](https://img.shields.io/badge/QQ%20Group%232-Join-blue)](https://qm.qq.com/q/HaRcfrHpUk) | [![Telegram](https://img.shields.io/badge/Telegram-MelodicMoonlight-blue)](https://t.me/MelodicMoonlight) |
|:-:|:-:|:-:|:-:|
[Cloudflare.Pages](https://napneko.pages.dev/)
## Thanks
[Server.Other](https://docs.napcat.cyou/)
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
[NapCat.Wiki](https://www.napcat.wiki)
## 回家旅途
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
[QQ Group#2](https://qm.qq.com/q/HaRcfrHpUk)
[Telegram](https://t.me/MelodicMoonlight)
> QQ Group#2 准许Bot / Telegram与QQ Group#2 为新建Group
## 性能设计/协议标准
NapCat 已实现90+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
由此设计带来一系列好处在开发中获取群员列表通常小于50Ms单条文本消息发送在320Ms以内在1k+的群聊流畅运行同时带来一些副作用消息Id无法持久无法上报撤回消息原始内容。
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
## 感谢他们
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
感谢 React 强力驱动 NapCat.WebUi
不过最最重要的 还是需要感谢屏幕前的你哦~
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 特殊感谢
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
## License
本项目采用 混合协议 开源,因此使用本项目时,你需要注意以下几点:
1. 第三方库代码或修改部分遵循其原始开源许可.
2. 本项目获取部分项目授权而不受部分约束
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
## 开源附加
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。**
## Warnings
[某框架抄袭部分分析](https://napneko.github.io/other/about-copy)

11
SECURITY.md Normal file
View File

@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| > 4.0 | :white_check_mark: |
| < 4.0 | :x: |
## Reporting a Vulnerability
you should open an issue

View File

@@ -1,70 +1,32 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import _import from "eslint-plugin-import";
import { fixupPluginRules } from "@eslint/compat";
import eslint from '@eslint/js';
import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
import tsEslintParser from '@typescript-eslint/parser';
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
const compat = new FlatCompat({
baseDirectory: dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [{
ignores: ["src/core/proto/"],
}, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"), {
plugins: {
"@typescript-eslint": typescriptEslint,
import: fixupPluginRules(_import),
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts"],
},
"import/resolver": {
typescript: {
alwaysTryTypes: true,
const customTsFlatConfig = [
{
name: 'typescript-eslint/base',
languageOptions: {
parser: tsEslintParser,
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
NodeJS: 'readonly', // 添加 NodeJS 全局变量
},
},
},
rules: {
indent: ["error", 4],
semi: ["error", "always"],
"no-unused-vars": "off",
"no-async-promise-executor": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"object-curly-spacing": ["error", "always"],
},
}, {
files: ["**/.eslintrc.{js,cjs}"],
languageOptions: {
globals: {
...globals.node,
files: ['**/*.{ts,tsx}'],
rules: {
...tsEslintPlugin.configs.recommended.rules,
'quotes': ['error', 'single'], // 使用单引号
'semi': ['error', 'always'], // 强制使用分号
'indent': ['error', 4], // 使用 4 空格缩进
},
ecmaVersion: 5,
sourceType: "commonjs",
plugins: {
'@typescript-eslint': tsEslintPlugin,
},
ignores: ['src/webui/**'], // 忽略 src/webui/ 目录所有文件
},
}];
];
export default [eslint.configs.recommended, ...customTsFlatConfig];

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
if not exist "%QQPath%" (
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
if not exist "%QQPath%" (
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -1,10 +1,9 @@
{
"name": "qq-chat",
"version": "9.9.17-30899",
"verHash": "ececf273",
"linuxVersion": "3.2.15-30899",
"linuxVerHash": "63c751e8",
"type": "module",
"version": "9.9.18-32869",
"verHash": "e735296c",
"linuxVersion": "3.2.16-32869",
"linuxVerHash": "4c192ba9",
"private": true,
"description": "QQ",
"productName": "QQ",
@@ -17,8 +16,25 @@
"bin": {
"qd": "externals/devtools/cli/index.js"
},
"appid": {
"win32": "537258389",
"darwin": "537258412",
"linux": "537258424"
},
"main": "./loadNapCat.js",
"buildVersion": "30899",
"peerDependenciesMeta": {
"*": {
"optional": true
}
},
"pnpm": {
"patchedDependencies": {
"@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
"@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
"vuex@4.1.0": "patches/vuex@4.1.0.patch"
}
},
"buildVersion": "32869",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.4.12",
"version": "4.7.34",
"icon": "./logo.png",
"authors": [
{

View File

@@ -4,12 +4,16 @@
"version": "0.0.6",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host=0.0.0.0",
"build": "tsc && vite build",
"lint": "eslint -c eslint.config.mjs ./src/**/**/*.{ts,tsx} --fix",
"preview": "vite preview"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@heroui/accordion": "^2.2.8",
"@heroui/avatar": "2.2.7",
"@heroui/breadcrumbs": "2.2.7",
"@heroui/button": "2.2.10",
@@ -26,80 +30,90 @@
"@heroui/listbox": "2.3.10",
"@heroui/modal": "2.2.8",
"@heroui/navbar": "2.2.9",
"@heroui/pagination": "^2.2.9",
"@heroui/popover": "2.3.10",
"@heroui/select": "2.4.10",
"@heroui/skeleton": "^2.2.6",
"@heroui/slider": "2.4.8",
"@heroui/snippet": "2.2.11",
"@heroui/spinner": "2.2.7",
"@heroui/switch": "2.2.9",
"@heroui/system": "2.4.7",
"@heroui/table": "^2.2.9",
"@heroui/tabs": "2.2.8",
"@heroui/theme": "2.4.6",
"@heroui/tooltip": "2.2.8",
"@monaco-editor/loader": "^1.4.0",
"@monaco-editor/react": "4.7.0-rc.0",
"@react-aria/visually-hidden": "3.8.18",
"@reduxjs/toolkit": "^2.5.0",
"@react-aria/visually-hidden": "^3.8.19",
"@reduxjs/toolkit": "^2.5.1",
"@uidotdev/usehooks": "^2.4.1",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"ahooks": "^3.8.4",
"axios": "^1.7.9",
"clsx": "2.1.1",
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"echarts": "^5.5.1",
"event-source-polyfill": "^1.0.31",
"framer-motion": "^11.15.0",
"framer-motion": "^12.0.6",
"monaco-editor": "^0.52.2",
"motion": "^11.15.0",
"motion": "^12.0.6",
"path-browserify": "^1.0.1",
"qface": "^1.4.1",
"qrcode.react": "^4.2.0",
"quill": "^2.0.3",
"react": "19.0.0",
"react-dom": "19.0.0",
"react": "^19.0.0",
"react-color": "^2.19.3",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
"react-error-boundary": "^5.0.0",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.4.0",
"react-markdown": "^9.0.3",
"react-photo-view": "^1.2.7",
"react-redux": "^9.2.0",
"react-responsive": "^10.0.0",
"react-router-dom": "7.1.0",
"react-router-dom": "^7.1.4",
"react-use-websocket": "^4.11.1",
"react-window": "^1.8.11",
"remark-gfm": "^4.0.0",
"tailwind-variants": "0.3.0",
"tailwindcss": "3.4.17",
"tailwind-variants": "^0.3.0",
"tailwindcss": "^3.4.17",
"zod": "^3.24.1"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@eslint/js": "^9.19.0",
"@react-types/shared": "^3.26.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/crypto-js": "^4.2.2",
"@types/event-source-polyfill": "^1.0.5",
"@types/fabric": "^5.3.9",
"@types/node": "22.10.2",
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"@types/node": "^22.12.0",
"@types/path-browserify": "^1.0.3",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react-window": "^1.8.8",
"@typescript-eslint/eslint-plugin": "8.18.1",
"@typescript-eslint/parser": "8.18.1",
"@typescript-eslint/eslint-plugin": "^8.22.0",
"@typescript-eslint/parser": "^8.22.0",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "10.4.20",
"eslint": "^9.17.0",
"eslint-config-prettier": "9.1.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.19.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-prettier": "5.2.3",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-unused-imports": "4.1.4",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^15.14.0",
"postcss": "8.4.49",
"prettier": "3.4.2",
"typescript": "5.7.2",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"typescript": "^5.7.3",
"vite": "^6.0.5",
"vite-plugin-static-copy": "^2.2.0",
"vite-tsconfig-paths": "^5.1.4"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -16,6 +16,16 @@ import store from '@/store'
const WebLoginPage = lazy(() => import('@/pages/web_login'))
const IndexPage = lazy(() => import('@/pages/index'))
const QQLoginPage = lazy(() => import('@/pages/qq_login'))
const DashboardIndexPage = lazy(() => import('@/pages/dashboard'))
const AboutPage = lazy(() => import('@/pages/dashboard/about'))
const ConfigPage = lazy(() => import('@/pages/dashboard/config'))
const DebugPage = lazy(() => import('@/pages/dashboard/debug'))
const HttpDebug = lazy(() => import('@/pages/dashboard/debug/http'))
const WSDebug = lazy(() => import('@/pages/dashboard/debug/websocket'))
const FileManagerPage = lazy(() => import('@/pages/dashboard/file_manager'))
const LogsPage = lazy(() => import('@/pages/dashboard/logs'))
const NetworkPage = lazy(() => import('@/pages/dashboard/network'))
const TerminalPage = lazy(() => import('@/pages/dashboard/terminal'))
function App() {
return (
@@ -58,9 +68,21 @@ function AuthChecker({ children }: { children: React.ReactNode }) {
function AppRoutes() {
return (
<Routes>
<Route element={<IndexPage />} path="/*" />
<Route element={<QQLoginPage />} path="/qq_login" />
<Route element={<WebLoginPage />} path="/web_login" />
<Route path="/" element={<IndexPage />}>
<Route index element={<DashboardIndexPage />} />
<Route path="network" element={<NetworkPage />} />
<Route path="config" element={<ConfigPage />} />
<Route path="logs" element={<LogsPage />} />
<Route path="debug" element={<DebugPage />}>
<Route path="ws" element={<WSDebug />} />
<Route path="http" element={<HttpDebug />} />
</Route>
<Route path="file_manager" element={<FileManagerPage />} />
<Route path="terminal" element={<TerminalPage />} />
<Route path="about" element={<AboutPage />} />
</Route>
<Route path="/qq_login" element={<QQLoginPage />} />
<Route path="/web_login" element={<WebLoginPage />} />
</Routes>
)
}

View File

@@ -0,0 +1,36 @@
import { Popover, PopoverContent, PopoverTrigger } from '@heroui/popover'
import React from 'react'
import { ColorResult, SketchPicker } from 'react-color'
// 假定 heroui 提供的 Popover组件
interface ColorPickerProps {
color: string
onChange: (color: ColorResult) => void
}
const ColorPicker: React.FC<ColorPickerProps> = ({ color, onChange }) => {
const handleChange = (colorResult: ColorResult) => {
onChange(colorResult)
}
return (
<Popover triggerScaleOnOpen={false}>
<PopoverTrigger>
<div
className="w-36 h-8 rounded-md cursor-pointer border border-content4"
style={{ background: color }}
/>
</PopoverTrigger>
<PopoverContent>
<SketchPicker
color={color}
onChange={handleChange}
className="!bg-transparent !shadow-none"
/>
</PopoverContent>
</Popover>
)
}
export default ColorPicker

View File

@@ -231,7 +231,7 @@ export default function AudioPlayer(props: AudioPlayerProps) {
: 'top-3 -left-8 rounded-l-full bg-opacity-50 backdrop-blur-md'
)}
variant="solid"
color="danger"
color="primary"
size="sm"
onPress={() => setIsCollapsed(!isCollapsed)}
>

View File

@@ -33,7 +33,7 @@ const AddButton: React.FC<AddButtonProps> = (props) => {
>
<DropdownTrigger>
<Button
color="danger"
color="primary"
startContent={<IoAddCircleOutline className="text-2xl" />}
>

View File

@@ -1,21 +1,29 @@
import { Button } from '@heroui/button'
import clsx from 'clsx'
import toast from 'react-hot-toast'
import { IoMdRefresh } from 'react-icons/io'
export interface SaveButtonsProps {
onSubmit: () => void
reset: () => void
refresh: () => void
refresh?: () => void
isSubmitting: boolean
className?: string
}
const SaveButtons: React.FC<SaveButtonsProps> = ({
onSubmit,
reset,
isSubmitting,
refresh
refresh,
className
}) => (
<div className="max-w-full mx-3 w-96 flex flex-col justify-center gap-3">
<div
className={clsx(
'max-w-full mx-3 w-96 flex flex-col justify-center gap-3',
className
)}
>
<div className="flex items-center justify-center gap-2 mt-5">
<Button
color="default"
@@ -33,15 +41,17 @@ const SaveButtons: React.FC<SaveButtonsProps> = ({
>
</Button>
<Button
isIconOnly
color="secondary"
radius="full"
variant="flat"
onPress={() => refresh()}
>
<IoMdRefresh size={24} />
</Button>
{refresh && (
<Button
isIconOnly
color="secondary"
radius="full"
variant="flat"
onPress={() => refresh()}
>
<IoMdRefresh size={24} />
</Button>
)}
</div>
</div>
)

View File

@@ -110,7 +110,7 @@ const AudioInsert = () => {
<Tooltip content="发送音频">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<IoMic className="text-xl" />
</Button>
</PopoverTrigger>
@@ -120,7 +120,7 @@ const AudioInsert = () => {
<Tooltip content="上传音频">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -137,7 +137,7 @@ const AudioInsert = () => {
<PopoverTrigger tooltip="输入音频地址">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -154,7 +154,7 @@ const AudioInsert = () => {
placeholder="请输入音频地址"
/>
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"
@@ -177,7 +177,7 @@ const AudioInsert = () => {
<PopoverTrigger>
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -190,7 +190,7 @@ const AudioInsert = () => {
<PopoverContent className="flex-col gap-2 p-4">
<div className="flex gap-2">
<Button
color={isRecording ? 'danger' : 'danger'}
color={isRecording ? 'primary' : 'primary'}
variant="flat"
onPress={isRecording ? stopRecording : startRecording}
>
@@ -198,7 +198,7 @@ const AudioInsert = () => {
</Button>
{showPreview && audioPreview && (
<Button
color="danger"
color="primary"
variant="flat"
onPress={handleShowPreview}
>
@@ -212,7 +212,7 @@ const AudioInsert = () => {
className={clsx(
'w-4 h-4 rounded-full',
isRecording
? 'animate-pulse bg-danger-400'
? 'animate-pulse bg-primary-400'
: 'bg-success-400'
)}
></span>

View File

@@ -10,7 +10,7 @@ const DiceInsert = () => {
return (
<Tooltip content="发送骰子">
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -55,7 +55,7 @@ const EmojiPicker = ({ onInsertEmoji, onOpenChange }: EmojiPickerProps) => {
<Tooltip content="插入表情">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<MdEmojiEmotions className="text-xl" />
</Button>
</PopoverTrigger>
@@ -65,7 +65,7 @@ const EmojiPicker = ({ onInsertEmoji, onOpenChange }: EmojiPickerProps) => {
{visibleEmojis.map((emoji) => (
<Button
key={emoji.id}
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -35,7 +35,7 @@ const FileInsert = () => {
<Tooltip content="发送文件">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<FaFolder className="text-lg" />
</Button>
</PopoverTrigger>
@@ -45,7 +45,7 @@ const FileInsert = () => {
<Tooltip content="上传文件">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -62,7 +62,7 @@ const FileInsert = () => {
<PopoverTrigger tooltip="输入文件地址">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -79,7 +79,7 @@ const FileInsert = () => {
placeholder="请输入文件地址"
/>
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -23,7 +23,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
<Tooltip content="插入图片">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<MdImage className="text-xl" />
</Button>
</PopoverTrigger>
@@ -33,7 +33,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
<Tooltip content="上传图片">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -50,7 +50,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
<PopoverTrigger tooltip="输入图片地址">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -67,7 +67,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
placeholder="请输入图片地址"
/>
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -80,7 +80,7 @@ const MusicInsert = () => {
<Tooltip content="发送音乐">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<IoMusicalNotes className="text-xl" />
</Button>
</PopoverTrigger>
@@ -132,7 +132,7 @@ const MusicInsert = () => {
<Button
fullWidth
size="lg"
color="danger"
color="primary"
variant="flat"
radius="full"
onPress={() => {
@@ -236,7 +236,7 @@ const MusicInsert = () => {
<Button
fullWidth
size="lg"
color="danger"
color="primary"
variant="flat"
radius="full"
type="submit"

View File

@@ -19,7 +19,7 @@ const ReplyInsert = ({ insertReply }: ReplyInsertProps) => {
<Tooltip content="回复消息">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<BsChatQuoteFill className="text-lg" />
</Button>
</PopoverTrigger>
@@ -38,7 +38,7 @@ const ReplyInsert = ({ insertReply }: ReplyInsertProps) => {
}}
/>
<Button
color="danger"
color="primary"
variant="flat"
radius="full"
isIconOnly

View File

@@ -10,7 +10,7 @@ const RPSInsert = () => {
return (
<Tooltip content="发送猜拳">
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -35,7 +35,7 @@ const VideoInsert = () => {
<Tooltip content="发送视频">
<div className="max-w-fit">
<PopoverTrigger>
<Button color="danger" variant="flat" isIconOnly radius="full">
<Button color="primary" variant="flat" isIconOnly radius="full">
<IoVideocam className="text-xl" />
</Button>
</PopoverTrigger>
@@ -45,7 +45,7 @@ const VideoInsert = () => {
<Tooltip content="上传视频">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -62,7 +62,7 @@ const VideoInsert = () => {
<PopoverTrigger tooltip="输入视频地址">
<Button
className="text-lg"
color="danger"
color="primary"
isIconOnly
variant="flat"
radius="full"
@@ -79,7 +79,7 @@ const VideoInsert = () => {
placeholder="请输入视频地址"
/>
<Button
color="danger"
color="primary"
variant="flat"
isIconOnly
radius="full"

View File

@@ -190,7 +190,7 @@ const ChatInput = () => {
<DiceInsert />
<RPSInsert />
<Button
color="danger"
color="primary"
onPress={() => {
const messages = getChatMessage()
showStructuredMessage(messages)

View File

@@ -15,7 +15,7 @@ export default function ChatInputModal() {
return (
<>
<Button onPress={onOpen} color="danger" radius="full" variant="flat">
<Button onPress={onOpen} color="primary" radius="full" variant="flat">
</Button>
<Modal
@@ -36,7 +36,7 @@ export default function ChatInputModal() {
</div>
</ModalBody>
<ModalFooter>
<Button color="danger" onPress={onClose} variant="flat">
<Button color="primary" onPress={onClose} variant="flat">
</Button>
</ModalFooter>

View File

@@ -78,7 +78,7 @@ const NetworkDisplayCard = <T extends keyof NetworkType>({
{debug ? '关闭调试' : '开启调试'}
</Button>
<Button
color="danger"
color="primary"
startContent={<MdDeleteForever />}
onPress={handleDelete}
>

View File

@@ -19,7 +19,7 @@ const NetworkItemDisplay: React.FC<NetworkItemDisplayProps> = ({
className={clsx(
'bg-opacity-60 shadow-sm md:rounded-3xl',
size === 'md'
? 'col-span-8 md:col-span-2 bg-danger-50 shadow-danger-100'
? 'col-span-8 md:col-span-2 bg-primary-50 shadow-primary-100'
: 'col-span-2 md:col-span-1 bg-warning-100 shadow-warning-200'
)}
shadow="sm"
@@ -27,7 +27,7 @@ const NetworkItemDisplay: React.FC<NetworkItemDisplayProps> = ({
<CardBody className="items-center md:gap-1 p-1 md:p-2">
<div
className={clsx(
'font-outfit flex-1',
'flex-1',
size === 'md' ? 'text-2xl md:text-3xl' : 'text-xl md:text-2xl',
title({
color: size === 'md' ? 'pink' : 'yellow',

View File

@@ -0,0 +1,166 @@
import {
FaFile,
FaFileAudio,
FaFileCode,
FaFileCsv,
FaFileExcel,
FaFileImage,
FaFileLines,
FaFilePdf,
FaFilePowerpoint,
FaFileVideo,
FaFileWord,
FaFileZipper,
FaFolderClosed
} from 'react-icons/fa6'
export interface FileIconProps {
name?: string
isDirectory?: boolean
}
const FileIcon = (props: FileIconProps) => {
const { name, isDirectory = false } = props
if (isDirectory) {
return <FaFolderClosed className="text-yellow-500" />
}
const ext = name?.split('.').pop() || ''
if (ext) {
switch (ext.toLowerCase()) {
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
case 'svg':
case 'bmp':
case 'ico':
case 'webp':
case 'tiff':
case 'tif':
case 'heic':
case 'heif':
case 'avif':
case 'apng':
case 'flif':
case 'ai':
case 'psd':
case 'xcf':
case 'sketch':
case 'fig':
case 'xd':
case 'svgz':
return <FaFileImage className="text-green-500" />
case 'pdf':
return <FaFilePdf className="text-red-500" />
case 'doc':
case 'docx':
return <FaFileWord className="text-blue-500" />
case 'xls':
case 'xlsx':
return <FaFileExcel className="text-green-500" />
case 'csv':
return <FaFileCsv className="text-green-500" />
case 'ppt':
case 'pptx':
return <FaFilePowerpoint className="text-red-500" />
case 'zip':
case 'rar':
case '7z':
case 'tar':
case 'gz':
case 'bz2':
case 'xz':
case 'lz':
case 'lzma':
case 'zst':
case 'zstd':
case 'z':
case 'taz':
case 'tz':
case 'tzo':
return <FaFileZipper className="text-green-500" />
case 'txt':
return <FaFileLines className="text-gray-500" />
case 'mp3':
case 'wav':
case 'flac':
return <FaFileAudio className="text-green-500" />
case 'mp4':
case 'avi':
case 'mov':
case 'wmv':
return <FaFileVideo className="text-red-500" />
case 'html':
case 'css':
case 'js':
case 'ts':
case 'jsx':
case 'tsx':
case 'json':
case 'xml':
case 'yaml':
case 'yml':
case 'md':
case 'sh':
case 'py':
case 'java':
case 'c':
case 'cpp':
case 'cs':
case 'go':
case 'php':
case 'rb':
case 'pl':
case 'swift':
case 'kt':
case 'rs':
case 'sql':
case 'r':
case 'scala':
case 'groovy':
case 'dart':
case 'lua':
case 'perl':
case 'h':
case 'm':
case 'mm':
case 'makefile':
case 'cmake':
case 'dockerfile':
case 'gradle':
case 'properties':
case 'ini':
case 'conf':
case 'env':
case 'bat':
case 'cmd':
case 'ps1':
case 'psm1':
case 'psd1':
case 'ps1xml':
case 'psc1':
case 'pssc':
case 'nuspec':
case 'resx':
case 'resw':
case 'csproj':
case 'vbproj':
case 'vcxproj':
case 'fsproj':
case 'sln':
case 'suo':
case 'user':
case 'userosscache':
case 'sln.docstates':
case 'dll':
return <FaFileCode className="text-blue-500" />
default:
return <FaFile className="text-gray-500" />
}
}
return <FaFile className="text-gray-500" />
}
export default FileIcon

View File

@@ -0,0 +1,64 @@
import { Button, ButtonGroup } from '@heroui/button'
import { Input } from '@heroui/input'
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@heroui/modal'
interface CreateFileModalProps {
isOpen: boolean
fileType: 'file' | 'directory'
newFileName: string
onTypeChange: (type: 'file' | 'directory') => void
onNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onClose: () => void
onCreate: () => void
}
export default function CreateFileModal({
isOpen,
fileType,
newFileName,
onTypeChange,
onNameChange,
onClose,
onCreate
}: CreateFileModalProps) {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContent>
<ModalHeader></ModalHeader>
<ModalBody>
<div className="flex flex-col gap-4">
<ButtonGroup color="primary">
<Button
variant={fileType === 'file' ? 'solid' : 'flat'}
onPress={() => onTypeChange('file')}
>
</Button>
<Button
variant={fileType === 'directory' ? 'solid' : 'flat'}
onPress={() => onTypeChange('directory')}
>
</Button>
</ButtonGroup>
<Input label="名称" value={newFileName} onChange={onNameChange} />
</div>
</ModalBody>
<ModalFooter>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
<Button color="primary" onPress={onCreate}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -0,0 +1,94 @@
import { Button } from '@heroui/button'
import { Code } from '@heroui/code'
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@heroui/modal'
import CodeEditor from '@/components/code_editor'
interface FileEditModalProps {
isOpen: boolean
file: { path: string; content: string } | null
onClose: () => void
onSave: () => void
onContentChange: (newContent?: string) => void
}
export default function FileEditModal({
isOpen,
file,
onClose,
onSave,
onContentChange
}: FileEditModalProps) {
// 根据文件后缀返回对应语言
const getLanguage = (filePath: string) => {
if (filePath.endsWith('.js')) return 'javascript'
if (filePath.endsWith('.ts')) return 'typescript'
if (filePath.endsWith('.tsx')) return 'tsx'
if (filePath.endsWith('.jsx')) return 'jsx'
if (filePath.endsWith('.vue')) return 'vue'
if (filePath.endsWith('.svelte')) return 'svelte'
if (filePath.endsWith('.json')) return 'json'
if (filePath.endsWith('.html')) return 'html'
if (filePath.endsWith('.css')) return 'css'
if (filePath.endsWith('.scss')) return 'scss'
if (filePath.endsWith('.less')) return 'less'
if (filePath.endsWith('.md')) return 'markdown'
if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) return 'yaml'
if (filePath.endsWith('.xml')) return 'xml'
if (filePath.endsWith('.sql')) return 'sql'
if (filePath.endsWith('.sh')) return 'shell'
if (filePath.endsWith('.bat')) return 'bat'
if (filePath.endsWith('.php')) return 'php'
if (filePath.endsWith('.java')) return 'java'
if (filePath.endsWith('.c')) return 'c'
if (filePath.endsWith('.cpp')) return 'cpp'
if (filePath.endsWith('.h')) return 'h'
if (filePath.endsWith('.hpp')) return 'hpp'
if (filePath.endsWith('.go')) return 'go'
if (filePath.endsWith('.py')) return 'python'
if (filePath.endsWith('.rb')) return 'ruby'
if (filePath.endsWith('.cs')) return 'csharp'
if (filePath.endsWith('.swift')) return 'swift'
if (filePath.endsWith('.vb')) return 'vb'
if (filePath.endsWith('.lua')) return 'lua'
if (filePath.endsWith('.pl')) return 'perl'
if (filePath.endsWith('.r')) return 'r'
return 'plaintext'
}
return (
<Modal size="full" isOpen={isOpen} onClose={onClose}>
<ModalContent>
<ModalHeader className="flex items-center gap-2 bg-content2 bg-opacity-50">
<span></span>
<Code className="text-xs">{file?.path}</Code>
</ModalHeader>
<ModalBody className="p-0">
<div className="h-full">
<CodeEditor
height="100%"
value={file?.content || ''}
onChange={onContentChange}
options={{ wordWrap: 'on' }}
language={file?.path ? getLanguage(file.path) : 'plaintext'}
/>
</div>
</ModalBody>
<ModalFooter>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
<Button color="primary" onPress={onSave}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -0,0 +1,92 @@
import { Button } from '@heroui/button'
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@heroui/modal'
import { Spinner } from '@heroui/spinner'
import { useRequest } from 'ahooks'
import path from 'path-browserify'
import { useEffect } from 'react'
import FileManager from '@/controllers/file_manager'
interface FilePreviewModalProps {
isOpen: boolean
filePath: string
onClose: () => void
}
export const videoExts = ['.mp4', '.webm']
export const audioExts = ['.mp3', '.wav']
export const supportedPreviewExts = [...videoExts, ...audioExts]
export default function FilePreviewModal({
isOpen,
filePath,
onClose
}: FilePreviewModalProps) {
const ext = path.extname(filePath).toLowerCase()
const { data, loading, error, run } = useRequest(
async () => FileManager.downloadToURL(filePath),
{
refreshDeps: [filePath],
manual: true,
refreshDepsAction: () => {
const ext = path.extname(filePath).toLowerCase()
if (!filePath || !supportedPreviewExts.includes(ext)) {
return
}
run()
}
}
)
useEffect(() => {
if (filePath) {
run()
}
}, [filePath])
let contentElement = null
if (!supportedPreviewExts.includes(ext)) {
contentElement = <div></div>
} else if (error) {
contentElement = <div></div>
} else if (loading || !data) {
contentElement = (
<div className="flex justify-center items-center h-full">
<Spinner />
</div>
)
} else if (videoExts.includes(ext)) {
contentElement = <video src={data} controls className="max-w-full" />
} else if (audioExts.includes(ext)) {
contentElement = <audio src={data} controls className="w-full" />
} else {
contentElement = (
<div className="flex justify-center items-center h-full">
<Spinner />
</div>
)
}
return (
<Modal isOpen={isOpen} onClose={onClose} scrollBehavior="inside" size="3xl">
<ModalContent>
<ModalHeader></ModalHeader>
<ModalBody className="flex justify-center items-center">
{contentElement}
</ModalBody>
<ModalFooter>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -0,0 +1,245 @@
import { Button, ButtonGroup } from '@heroui/button'
import { Pagination } from '@heroui/pagination'
import { Spinner } from '@heroui/spinner'
import {
type Selection,
type SortDescriptor,
Table,
TableBody,
TableCell,
TableColumn,
TableHeader,
TableRow
} from '@heroui/table'
import path from 'path-browserify'
import { useCallback, useEffect, useState } from 'react'
import { BiRename } from 'react-icons/bi'
import { FiCopy, FiDownload, FiMove, FiTrash2 } from 'react-icons/fi'
import { PhotoSlider } from 'react-photo-view'
import FileIcon from '@/components/file_icon'
import type { FileInfo } from '@/controllers/file_manager'
import { supportedPreviewExts } from './file_preview_modal'
import ImageNameButton, { PreviewImage, imageExts } from './image_name_button'
export interface FileTableProps {
files: FileInfo[]
currentPath: string
loading: boolean
sortDescriptor: SortDescriptor
onSortChange: (descriptor: SortDescriptor) => void
selectedFiles: Selection
onSelectionChange: (selected: Selection) => void
onDirectoryClick: (dirPath: string) => void
onEdit: (filePath: string) => void
onPreview: (filePath: string) => void
onRenameRequest: (name: string) => void
onMoveRequest: (name: string) => void
onCopyPath: (fileName: string) => void
onDelete: (filePath: string) => void
onDownload: (filePath: string) => void
}
const PAGE_SIZE = 20
export default function FileTable({
files,
currentPath,
loading,
sortDescriptor,
onSortChange,
selectedFiles,
onSelectionChange,
onDirectoryClick,
onEdit,
onPreview,
onRenameRequest,
onMoveRequest,
onCopyPath,
onDelete,
onDownload
}: FileTableProps) {
const [page, setPage] = useState(1)
const pages = Math.ceil(files.length / PAGE_SIZE) || 1
const start = (page - 1) * PAGE_SIZE
const end = start + PAGE_SIZE
const displayFiles = files.slice(start, end)
const [showImage, setShowImage] = useState(false)
const [previewIndex, setPreviewIndex] = useState(0)
const [previewImages, setPreviewImages] = useState<PreviewImage[]>([])
const addPreviewImage = useCallback((image: PreviewImage) => {
setPreviewImages((prev) => {
const exists = prev.some((p) => p.key === image.key)
if (exists) return prev
return [...prev, image]
})
}, [])
useEffect(() => {
setPreviewImages([])
setPreviewIndex(0)
setShowImage(false)
}, [currentPath])
const onPreviewImage = (name: string, images: PreviewImage[]) => {
const index = images.findIndex((image) => image.key === name)
if (index === -1) {
return
}
setPreviewIndex(index)
setShowImage(true)
}
return (
<>
<PhotoSlider
images={previewImages}
visible={showImage}
onClose={() => setShowImage(false)}
index={previewIndex}
onIndexChange={setPreviewIndex}
/>
<Table
aria-label="文件列表"
sortDescriptor={sortDescriptor}
onSortChange={onSortChange}
onSelectionChange={onSelectionChange}
defaultSelectedKeys={[]}
selectedKeys={selectedFiles}
selectionMode="multiple"
bottomContent={
<div className="flex w-full justify-center">
<Pagination
isCompact
showControls
showShadow
color="primary"
page={page}
total={pages}
onChange={(page) => setPage(page)}
/>
</div>
}
>
<TableHeader>
<TableColumn key="name" allowsSorting>
</TableColumn>
<TableColumn key="type" allowsSorting>
</TableColumn>
<TableColumn key="size" allowsSorting>
</TableColumn>
<TableColumn key="mtime" allowsSorting>
</TableColumn>
<TableColumn key="actions"></TableColumn>
</TableHeader>
<TableBody
isLoading={loading}
loadingContent={
<div className="flex justify-center items-center h-full">
<Spinner />
</div>
}
>
{displayFiles.map((file: FileInfo) => {
const filePath = path.join(currentPath, file.name)
const ext = path.extname(file.name).toLowerCase()
const previewable = supportedPreviewExts.includes(ext)
const images = previewImages
return (
<TableRow key={file.name}>
<TableCell>
{imageExts.includes(ext) ? (
<ImageNameButton
name={file.name}
filePath={filePath}
onPreview={() => onPreviewImage(file.name, images)}
onAddPreview={addPreviewImage}
/>
) : (
<Button
variant="light"
onPress={() =>
file.isDirectory
? onDirectoryClick(file.name)
: previewable
? onPreview(filePath)
: onEdit(filePath)
}
className="text-left justify-start"
startContent={
<FileIcon
name={file.name}
isDirectory={file.isDirectory}
/>
}
>
{file.name}
</Button>
)}
</TableCell>
<TableCell>{file.isDirectory ? '目录' : '文件'}</TableCell>
<TableCell>
{isNaN(file.size) || file.isDirectory
? '-'
: `${file.size} 字节`}
</TableCell>
<TableCell>{new Date(file.mtime).toLocaleString()}</TableCell>
<TableCell>
<ButtonGroup size="sm">
<Button
isIconOnly
color="primary"
variant="flat"
onPress={() => onRenameRequest(file.name)}
>
<BiRename />
</Button>
<Button
isIconOnly
color="primary"
variant="flat"
onPress={() => onMoveRequest(file.name)}
>
<FiMove />
</Button>
<Button
isIconOnly
color="primary"
variant="flat"
onPress={() => onCopyPath(file.name)}
>
<FiCopy />
</Button>
<Button
isIconOnly
color="primary"
variant="flat"
onPress={() => onDownload(filePath)}
>
<FiDownload />
</Button>
<Button
isIconOnly
color="primary"
variant="flat"
onPress={() => onDelete(filePath)}
>
<FiTrash2 />
</Button>
</ButtonGroup>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</>
)
}

View File

@@ -0,0 +1,88 @@
import { Button } from '@heroui/button'
import { Image } from '@heroui/image'
import { Spinner } from '@heroui/spinner'
import { useRequest } from 'ahooks'
import path from 'path-browserify'
import { useEffect } from 'react'
import FileManager from '@/controllers/file_manager'
import FileIcon from '../file_icon'
export interface PreviewImage {
key: string
src: string
alt: string
}
export const imageExts = ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
export interface ImageNameButtonProps {
name: string
filePath: string
onPreview: () => void
onAddPreview: (image: PreviewImage) => void
}
export default function ImageNameButton({
name,
filePath,
onPreview,
onAddPreview
}: ImageNameButtonProps) {
const { data, loading, error, run } = useRequest(
async () => FileManager.downloadToURL(filePath),
{
refreshDeps: [filePath],
manual: true,
refreshDepsAction: () => {
const ext = path.extname(filePath).toLowerCase()
if (!filePath || !imageExts.includes(ext)) {
return
}
run()
}
}
)
useEffect(() => {
if (data) {
onAddPreview({
key: name,
src: data,
alt: name
})
}
}, [data, name, onAddPreview])
useEffect(() => {
if (filePath) {
run()
}
}, [])
return (
<Button
variant="light"
className="text-left justify-start"
onPress={onPreview}
startContent={
error ? (
<FileIcon name={name} isDirectory={false} />
) : loading || !data ? (
<Spinner size="sm" />
) : (
<Image
src={data}
alt={name}
className="w-8 h-8 flex-shrink-0"
classNames={{
wrapper: 'w-8 h-8 flex-shrink-0'
}}
radius="sm"
/>
)
}
>
{name}
</Button>
)
}

View File

@@ -0,0 +1,168 @@
import { Button } from '@heroui/button'
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@heroui/modal'
import { Spinner } from '@heroui/spinner'
import clsx from 'clsx'
import path from 'path-browserify'
import { useState } from 'react'
import { IoAdd, IoRemove } from 'react-icons/io5'
import FileManager from '@/controllers/file_manager'
interface MoveModalProps {
isOpen: boolean
moveTargetPath: string
selectionInfo: string
onClose: () => void
onMove: () => void
onSelect: (dir: string) => void // 新增回调
}
// 将 DirectoryTree 改为递归组件
// 新增 selectedPath 属性,用于标识当前选中的目录
function DirectoryTree({
basePath,
onSelect,
selectedPath
}: {
basePath: string
onSelect: (dir: string) => void
selectedPath?: string
}) {
const [dirs, setDirs] = useState<string[]>([])
const [expanded, setExpanded] = useState(false)
// 新增loading状态
const [loading, setLoading] = useState(false)
const fetchDirectories = async () => {
try {
// 直接使用 basePath 调用接口,移除 process.platform 判断
const list = await FileManager.listDirectories(basePath)
setDirs(list.map((item) => item.name))
} catch (error) {
// ...error handling...
}
}
const handleToggle = async () => {
if (!expanded) {
setExpanded(true)
setLoading(true)
await fetchDirectories()
setLoading(false)
} else {
setExpanded(false)
}
}
const handleClick = () => {
onSelect(basePath)
handleToggle()
}
// 计算显示的名称
const getDisplayName = () => {
if (basePath === '/') return '/'
if (/^[A-Z]:$/i.test(basePath)) return basePath
return path.basename(basePath)
}
// 更新 Button 的 variant 逻辑
const isSeleted = selectedPath === basePath
const variant = isSeleted
? 'solid'
: selectedPath && path.dirname(selectedPath) === basePath
? 'flat'
: 'light'
return (
<div className="ml-4">
<Button
onPress={handleClick}
className="py-1 px-2 text-left justify-start min-w-0 min-h-0 h-auto text-sm rounded-md"
size="sm"
color="primary"
variant={variant}
startContent={
<div
className={clsx(
'rounded-md',
isSeleted ? 'bg-primary-600' : 'bg-primary-50'
)}
>
{expanded ? <IoRemove /> : <IoAdd />}
</div>
}
>
{getDisplayName()}
</Button>
{expanded && (
<div>
{loading ? (
<div className="flex py-1 px-8">
<Spinner size="sm" color="primary" />
</div>
) : (
dirs.map((dirName) => {
const childPath =
basePath === '/' && /^[A-Z]:$/i.test(dirName)
? dirName
: path.join(basePath, dirName)
return (
<DirectoryTree
key={childPath}
basePath={childPath}
onSelect={onSelect}
selectedPath={selectedPath}
/>
)
})
)}
</div>
)}
</div>
)
}
export default function MoveModal({
isOpen,
moveTargetPath,
selectionInfo,
onClose,
onMove,
onSelect
}: MoveModalProps) {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContent>
<ModalHeader></ModalHeader>
<ModalBody>
<div className="rounded-md p-2 border border-default-300 overflow-auto max-h-60">
<DirectoryTree
basePath="/"
onSelect={onSelect}
selectedPath={moveTargetPath}
/>
</div>
<p className="text-sm text-default-500 mt-2">
{moveTargetPath || '未选择'}
</p>
<p className="text-sm text-default-500">{selectionInfo}</p>
</ModalBody>
<ModalFooter>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
<Button color="primary" onPress={onMove}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -0,0 +1,44 @@
import { Button } from '@heroui/button'
import { Input } from '@heroui/input'
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader
} from '@heroui/modal'
interface RenameModalProps {
isOpen: boolean
newFileName: string
onNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onClose: () => void
onRename: () => void
}
export default function RenameModal({
isOpen,
newFileName,
onNameChange,
onClose,
onRename
}: RenameModalProps) {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalContent>
<ModalHeader></ModalHeader>
<ModalBody>
<Input label="新名称" value={newFileName} onChange={onNameChange} />
</ModalBody>
<ModalFooter>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
<Button color="primary" onPress={onRename}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

View File

@@ -33,10 +33,10 @@ export default function Hitokoto() {
<div className="relative">
{loading && <PageLoading />}
{error ? (
<div className="text-danger-400">{error.message}</div>
<div className="text-primary-400">{error.message}</div>
) : (
<>
<div className="font-noto-serif">{data?.hitokoto}</div>
<div>{data?.hitokoto}</div>
<div className="text-right">
<span className="text-default-400">{data?.from}</span>{' '}
{data?.from_who}
@@ -52,7 +52,7 @@ export default function Hitokoto() {
isLoading={loading}
isIconOnly
radius="full"
color="danger"
color="primary"
variant="flat"
>
<IoRefresh />

View File

@@ -0,0 +1,146 @@
import { motion, useMotionValue, useSpring } from 'motion/react'
import { useRef, useState } from 'react'
const springValues = {
damping: 30,
stiffness: 100,
mass: 2
}
export interface HoverTiltedCardProps {
imageSrc: string
altText?: string
captionText?: string
containerHeight?: string
containerWidth?: string
imageHeight?: string
imageWidth?: string
scaleOnHover?: number
rotateAmplitude?: number
showTooltip?: boolean
overlayContent?: React.ReactNode
displayOverlayContent?: boolean
}
export default function HoverTiltedCard({
imageSrc,
altText = 'NapCat',
captionText = 'NapCat',
containerHeight = '200px',
containerWidth = '100%',
imageHeight = '200px',
imageWidth = '200px',
scaleOnHover = 1.1,
rotateAmplitude = 14,
showTooltip = false,
overlayContent = (
<div className="text-center mt-6 px-4 py-0.5 shadow-lg rounded-full bg-primary-600 text-default-100 bg-opacity-80">
NapCat
</div>
),
displayOverlayContent = true
}: HoverTiltedCardProps) {
const ref = useRef<HTMLDivElement>(null)
const x = useMotionValue(0)
const y = useMotionValue(0)
const rotateX = useSpring(useMotionValue(0), springValues)
const rotateY = useSpring(useMotionValue(0), springValues)
const scale = useSpring(1, springValues)
const opacity = useSpring(0)
const rotateFigcaption = useSpring(0, {
stiffness: 350,
damping: 30,
mass: 1
})
const [lastY, setLastY] = useState(0)
function handleMouse(e: React.MouseEvent) {
if (!ref.current) return
const rect = ref.current.getBoundingClientRect()
const offsetX = e.clientX - rect.left - rect.width / 2
const offsetY = e.clientY - rect.top - rect.height / 2
const rotationX = (offsetY / (rect.height / 2)) * -rotateAmplitude
const rotationY = (offsetX / (rect.width / 2)) * rotateAmplitude
rotateX.set(rotationX)
rotateY.set(rotationY)
x.set(e.clientX - rect.left)
y.set(e.clientY - rect.top)
const velocityY = offsetY - lastY
rotateFigcaption.set(-velocityY * 0.6)
setLastY(offsetY)
}
function handleMouseEnter() {
scale.set(scaleOnHover)
opacity.set(1)
}
function handleMouseLeave() {
opacity.set(0)
scale.set(1)
rotateX.set(0)
rotateY.set(0)
rotateFigcaption.set(0)
}
return (
<figure
ref={ref}
className="relative w-full h-full [perspective:800px] flex flex-col items-center justify-center"
style={{
height: containerHeight,
width: containerWidth
}}
onMouseMove={handleMouse}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<motion.div
className="relative [transform-style:preserve-3d]"
style={{
width: imageWidth,
height: imageHeight,
rotateX,
rotateY,
scale
}}
>
<motion.img
src={imageSrc}
alt={altText}
className="absolute top-0 left-0 object-cover rounded-md will-change-transform [transform:translateZ(0)] pointer-events-none select-none"
style={{
width: imageWidth,
height: imageHeight
}}
/>
{displayOverlayContent && overlayContent && (
<motion.div className="absolute top-0 left-0 right-0 z-10 flex justify-center will-change-transform [transform:translateZ(30px)]">
{overlayContent}
</motion.div>
)}
</motion.div>
{showTooltip && (
<motion.figcaption
className="pointer-events-none absolute left-0 top-0 rounded-md bg-white px-2 py-1 text-sm text-default-900 opacity-0 z-10 hidden sm:block"
style={{
x,
y,
opacity,
rotate: rotateFigcaption
}}
>
{captionText}
</motion.figcaption>
)}
</figure>
)
}

View File

@@ -1152,7 +1152,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
begin="0ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1197,7 +1197,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
begin="800ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1247,7 +1247,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
begin="1600ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1297,7 +1297,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
begin="2400ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1344,7 +1344,7 @@ export const WebUIIcon = (props: IconSvgProps) => (
begin="3200ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1399,7 +1399,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="0ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1446,7 +1446,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="600ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1496,7 +1496,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="1200ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1543,7 +1543,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="1800ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1590,7 +1590,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="2400ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1637,7 +1637,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="3000ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1684,7 +1684,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="3600ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1731,7 +1731,7 @@ export const BietiaopIcon = (props: IconSvgProps) => (
begin="4200ms"
></animate>
<animate
attributeName="fill-opacity"
attributeName="fillOpacity"
to="1"
dur="800ms"
calcMode="linear"
@@ -1744,3 +1744,224 @@ export const BietiaopIcon = (props: IconSvgProps) => (
</svg>
</>
)
export const FileIcon = (props: IconSvgProps) => (
<svg
version="1.1"
id="_x36_"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512"
xmlSpace="preserve"
{...props}
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g
id="SVGRepo_tracerCarrier"
strokeLinecap="round"
strokeLinejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<g>
<path
style={{ fill: '#D4B476' }}
d="M441.853,393.794H70.147C31.566,393.794,0,362.228,0,323.647V106.969 c0-38.581,31.566-70.147,70.147-70.147h371.706c38.581,0,70.147,31.566,70.147,70.147v216.678 C512,362.228,480.434,393.794,441.853,393.794z"
></path>
<path
style={{ fill: '#D4B476' }}
d="M199.884,249.574H70.147C31.566,249.574,0,218.008,0,179.427V70.147C0,31.566,31.566,0,70.147,0 h129.737c38.581,0,70.147,31.566,70.147,70.147v109.28C270.031,218.008,238.465,249.574,199.884,249.574z"
></path>
<polygon
style={{ fill: '#F0EFEF' }}
points="485.439,329.388 87.357,347.774 78.653,130.095 476.734,111.709 "
></polygon>
<defs>
<filter
id="Adobe_OpacityMaskFilter"
filterUnits="userSpaceOnUse"
x="34.381"
y="60.216"
width="416.68"
height="259.557"
>
<feFlood
style={{
floodColor: 'white',
floodOpacity: 1
}}
result="back"
></feFlood>
<feBlend in="SourceGraphic" in2="back" mode="normal"></feBlend>
</filter>
</defs>
<mask
maskUnits="userSpaceOnUse"
x="34.381"
y="60.216"
width="416.68"
height="259.557"
id="SVGID_1_"
>
<g style={{ filter: 'url(#Adobe_OpacityMaskFilter)' }}>
<defs>
<filter
id="Adobe_OpacityMaskFilter_1_"
filterUnits="userSpaceOnUse"
x="34.381"
y="60.216"
width="416.68"
height="259.557"
>
<feFlood
style={{ floodColor: 'white', floodOpacity: 1 }}
result="back"
></feFlood>
<feBlend in="SourceGraphic" in2="back" mode="normal"></feBlend>
</filter>
</defs>
<mask
maskUnits="userSpaceOnUse"
x="34.381"
y="60.216"
width="416.68"
height="259.557"
id="SVGID_1_"
>
<g style={{ filter: 'url(#Adobe_OpacityMaskFilter_1_)' }}> </g>
</mask>
<linearGradient
id="SVGID_2_"
gradientUnits="userSpaceOnUse"
x1="34.3814"
y1="189.9944"
x2="451.061"
y2="189.9944"
>
<stop offset="0.57" style={{ stopColor: '#F6F6F6' }}></stop>
<stop offset="0.6039" style={{ stopColor: '#F6F6F6' }}></stop>
</linearGradient>
<polygon
style={{ mask: 'url(#SVGID_1_)', fill: 'url(#SVGID_2_)' }}
points="451.061,277.073 54.598,319.773 34.381,102.916 430.845,60.216 "
></polygon>
</g>
</mask>
<linearGradient
id="SVGID_3_"
gradientUnits="userSpaceOnUse"
x1="34.3814"
y1="189.9944"
x2="451.061"
y2="189.9944"
>
<stop offset="0.57" style={{ stopColor: '#FFFFFF' }}></stop>
<stop offset="0.6039" style={{ stopColor: '#F0F0F0' }}></stop>
</linearGradient>
<polygon
style={{ fill: 'url(#SVGID_3_)' }}
points="451.061,277.073 54.598,319.773 34.381,102.916 430.845,60.216 "
></polygon>
<path
style={{ fill: '#69A092' }}
d="M441.853,417.32H70.147C31.566,417.32,0,385.754,0,347.173V168.515h512v178.658 C512,385.754,480.434,417.32,441.853,417.32z"
></path>
<path
style={{ fill: '#D4B476' }}
d="M441.853,429.594H70.147C31.566,429.594,0,398.028,0,359.447V189.995h512v169.453 C512,398.028,480.434,429.594,441.853,429.594z"
></path>
<g>
<g>
<path
style={{ fill: '#CBBC89' }}
d="M41.051,330.321h28.918h7.581c0.686,0,1.357,0,2.012,0c0.655,0,1.171,0.126,1.545,0.375 c0.499,0.312,0.795,0.764,0.889,1.357c0.094,0.594,0.141,1.296,0.141,2.106c0,0.312,0.014,0.608,0.047,0.888 c0.031,0.281-0.016,0.547-0.14,0.796c-0.25,0.998-0.796,1.543-1.638,1.638c-0.843,0.094-1.888,0.14-3.136,0.14h-9.733H53.778 c-0.998,0-2.058-0.015-3.182-0.047c-1.122-0.03-1.965,0.173-2.526,0.608c-0.563,0.375-0.858,1.03-0.89,1.965 c-0.032,0.937-0.047,1.873-0.047,2.808v9.265c0,0.5-0.015,1.123-0.046,1.872c-0.033,0.749,0.014,1.373,0.139,1.871v0.748 c0.125,0.375,0.235,0.735,0.328,1.077c0.093,0.344,0.295,0.608,0.608,0.796c0.562,0.374,1.436,0.547,2.621,0.514 c1.184-0.03,2.214-0.047,3.088-0.047h16.845c0.561,0,1.171-0.014,1.825-0.046c0.655-0.031,1.31-0.031,1.965,0 c0.655,0.032,1.248,0.109,1.779,0.234c0.529,0.126,0.889,0.344,1.076,0.655c0.311,0.375,0.467,0.843,0.467,1.404 c0,0.56,0,1.186,0,1.871c0,0.375,0,0.719,0,1.03c0,0.313-0.062,0.594-0.186,0.842c-0.25,0.625-0.595,0.969-1.03,1.03 c-0.25,0.125-0.485,0.186-0.702,0.186c-0.219,0-0.483,0.033-0.796,0.094c-0.25,0.063-0.514,0.079-0.796,0.047 c-0.281-0.03-0.546-0.047-0.794-0.047h-3.277H54.433c-0.999,0-2.168-0.014-3.51-0.047c-1.342-0.03-2.292,0.172-2.853,0.609 c-0.314,0.25-0.547,0.64-0.702,1.169c-0.156,0.531-0.25,1.14-0.281,1.825c-0.033,0.687-0.033,1.389,0,2.106 c0.03,0.717,0.046,1.357,0.046,1.918v16.096c0,1.062,0.032,2.217,0.094,3.463c0.062,1.249-0.156,2.185-0.655,2.807 c-0.186,0.25-0.469,0.391-0.842,0.422c-0.374,0.032-0.749,0.109-1.123,0.234h-1.779c-0.875,0-1.684-0.03-2.433-0.093 c-0.749-0.062-1.279-0.375-1.59-0.937c-0.25-0.374-0.375-0.888-0.375-1.543c0-0.656,0-1.326,0-2.013v-7.488v-39.119v-10.107 c0-0.686-0.016-1.45-0.047-2.292c-0.032-0.842,0.107-1.512,0.422-2.012c0.249-0.374,0.715-0.685,1.404-0.935 c0.124-0.062,0.264-0.077,0.42-0.047C40.785,330.4,40.925,330.383,41.051,330.321z"
></path>
<path
style={{ fill: '#CBBC89' }}
d="M96.895,330.508c0.436,0,0.935-0.014,1.496-0.046c0.563-0.032,1.108-0.032,1.639,0 c0.529,0.032,1.013,0.109,1.45,0.234c0.437,0.125,0.749,0.312,0.937,0.561c0.374,0.438,0.576,1.046,0.608,1.825 c0.03,0.78,0.047,1.576,0.047,2.386v9.079v36.124v10.949c0,0.811,0,1.669,0,2.574c0,0.905-0.156,1.606-0.468,2.105 c-0.25,0.314-0.547,0.5-0.889,0.563c-0.344,0.062-0.765,0.156-1.264,0.28h-1.777c-0.874,0-1.684-0.034-2.433-0.094 c-0.749-0.061-1.28-0.374-1.592-0.935c-0.25-0.374-0.375-0.89-0.375-1.545c0-0.655,0-1.325,0-2.012v-7.487V345.95v-10.107 c0-0.686-0.015-1.451-0.045-2.292c-0.034-0.843,0.108-1.514,0.42-2.013c0.249-0.436,0.748-0.748,1.497-0.936 c0.125-0.061,0.25-0.077,0.375-0.047C96.645,330.587,96.769,330.571,96.895,330.508z"
></path>
<path
style={{ fill: '#CBBC89' }}
d="M126.093,330.321c0.436,0,0.935-0.015,1.498-0.047c0.561-0.03,1.107-0.03,1.638,0 c0.529,0.032,1.012,0.109,1.451,0.234c0.436,0.125,0.748,0.313,0.936,0.562c0.374,0.5,0.56,1.155,0.56,1.965 c0,0.811,0,1.654,0,2.528v9.826v30.042v8.609c0,0.687,0,1.436,0,2.245c0,0.813,0.094,1.468,0.281,1.967 c0.188,0.436,0.529,0.811,1.03,1.122c0.313,0.125,0.717,0.203,1.217,0.235c0.499,0.032,0.998,0.046,1.498,0.046h4.772h16.845 h5.24c0.436,0,0.89-0.014,1.359-0.046c0.467-0.032,0.888,0.016,1.263,0.139c0.81,0.25,1.34,0.594,1.591,1.03 c0.062,0.188,0.124,0.5,0.188,0.937c0.061,0.436,0.107,0.889,0.139,1.357c0.03,0.467,0.015,0.935-0.047,1.402 c-0.062,0.469-0.126,0.829-0.187,1.077c-0.126,0.437-0.406,0.782-0.843,1.029c-0.313,0.25-0.765,0.375-1.357,0.375 c-0.593,0-1.169,0-1.731,0h-6.177H133.58h-6.083c-0.874,0-1.623-0.047-2.246-0.139c-0.624-0.094-1.092-0.39-1.404-0.89 c-0.25-0.374-0.373-0.857-0.373-1.451c0-0.591,0-1.168,0-1.729v-6.74v-25.549v-20.308v-5.802c0-0.561,0.014-1.122,0.045-1.684 c0.032-0.561,0.141-0.998,0.328-1.31c0.249-0.374,0.717-0.685,1.404-0.935c0.124-0.062,0.265-0.077,0.422-0.047 C125.827,330.4,125.968,330.383,126.093,330.321z"
></path>
<path
style={{ fill: '#CBBC89' }}
d="M187.883,330.321h30.602h8.049c0.686,0,1.372-0.015,2.059-0.047 c0.685-0.03,1.248,0.109,1.684,0.422c0.188,0.125,0.343,0.281,0.468,0.467c0.124,0.188,0.249,0.375,0.374,0.563 c0.125,0.436,0.187,1.138,0.187,2.105c0,0.967-0.062,1.701-0.187,2.199c-0.25,0.874-0.78,1.357-1.592,1.451 c-0.811,0.094-1.777,0.14-2.9,0.14h-9.359h-15.722c-0.998,0-2.169-0.015-3.51-0.047c-1.343-0.03-2.325,0.139-2.949,0.514 c-0.624,0.374-0.951,1.03-0.982,1.966c-0.032,0.936-0.048,1.904-0.048,2.901v9.546c0,0.688-0.015,1.498-0.045,2.433 c-0.034,0.937,0.014,1.686,0.139,2.246c0.188,0.749,0.562,1.249,1.123,1.498c0.249,0.125,0.686,0.25,1.31,0.373h0.935 c0.249,0.063,0.515,0.08,0.796,0.047c0.281-0.03,0.577-0.047,0.89-0.047h3.181h18.343c0.561,0,1.184-0.014,1.872-0.046 c0.685-0.031,1.387-0.031,2.104,0c0.717,0.032,1.372,0.093,1.967,0.187c0.591,0.094,1.044,0.233,1.357,0.421 c0.186,0.126,0.341,0.407,0.467,0.842c0.125,0.438,0.218,0.905,0.282,1.404c0.061,0.5,0.077,1.016,0.045,1.545 c-0.031,0.531-0.109,0.983-0.233,1.357c-0.126,0.561-0.406,0.967-0.842,1.217c-0.127,0.126-0.297,0.203-0.515,0.235 c-0.22,0.032-0.422,0.079-0.608,0.139h-0.563c-0.312,0.063-0.623,0.079-0.935,0.047c-0.313-0.03-0.624-0.047-0.935-0.047h-3.463 h-19.747c-0.747,0-1.482-0.014-2.199-0.047c-0.719-0.03-1.39,0-2.012,0.094c-0.624,0.094-1.171,0.267-1.639,0.515 c-0.467,0.25-0.794,0.687-0.982,1.31c-0.126,0.375-0.173,0.811-0.139,1.31c0.03,0.5,0.045,0.999,0.045,1.498v5.239v8.892 c0,0.873,0.032,1.669,0.094,2.386c0.062,0.718,0.312,1.233,0.749,1.544c0.435,0.313,0.935,0.483,1.497,0.515 c0.561,0.032,1.185,0.046,1.871,0.046h5.99h17.314h5.334c0.499,0,0.998-0.014,1.497-0.046c0.498-0.032,0.936,0.016,1.31,0.139 c0.749,0.25,1.248,0.594,1.498,1.03c0.123,0.188,0.217,0.5,0.281,0.937c0.061,0.436,0.108,0.889,0.14,1.357 c0.03,0.467,0.015,0.935-0.047,1.402c-0.062,0.469-0.126,0.829-0.187,1.077c-0.25,0.5-0.531,0.842-0.842,1.029 c-0.375,0.25-0.858,0.375-1.45,0.375c-0.594,0-1.171,0-1.731,0h-6.552h-24.894h-6.551c-0.874,0-1.623-0.047-2.245-0.139 c-0.624-0.094-1.093-0.39-1.404-0.89c-0.25-0.374-0.374-0.857-0.374-1.451c0-0.591,0-1.168,0-1.729v-6.551v-25.082v-20.964 v-5.802c0-0.561,0-1.122,0-1.684c0-0.561,0.124-0.998,0.374-1.31c0.249-0.435,0.716-0.749,1.404-0.935 c0.125-0.062,0.248-0.077,0.375-0.047C187.632,330.4,187.756,330.383,187.883,330.321z"
></path>
</g>
<g>
<path
style={{ fill: '#98806E' }}
d="M41.051,330.321h28.918h7.581c0.686,0,1.357,0,2.012,0c0.655,0,1.171,0.126,1.545,0.375 c0.499,0.312,0.795,0.764,0.889,1.357c0.094,0.594,0.141,1.296,0.141,2.106c0,0.312,0.014,0.608,0.047,0.888 c0.031,0.281-0.016,0.547-0.14,0.796c-0.25,0.998-0.796,1.543-1.638,1.638c-0.843,0.094-1.888,0.14-3.136,0.14h-9.733H53.778 c-0.998,0-2.058-0.015-3.182-0.047c-1.122-0.03-1.965,0.173-2.526,0.608c-0.563,0.375-0.858,1.03-0.89,1.965 c-0.032,0.937-0.047,1.873-0.047,2.808v9.265c0,0.5-0.015,1.123-0.046,1.872c-0.033,0.749,0.014,1.373,0.139,1.871v0.748 c0.125,0.375,0.235,0.735,0.328,1.077c0.093,0.344,0.295,0.608,0.608,0.796c0.562,0.374,1.436,0.547,2.621,0.514 c1.184-0.03,2.214-0.047,3.088-0.047h16.845c0.561,0,1.171-0.014,1.825-0.046c0.655-0.031,1.31-0.031,1.965,0 c0.655,0.032,1.248,0.109,1.779,0.234c0.529,0.126,0.889,0.344,1.076,0.655c0.311,0.375,0.467,0.843,0.467,1.404 c0,0.56,0,1.186,0,1.871c0,0.375,0,0.719,0,1.03c0,0.313-0.062,0.594-0.186,0.842c-0.25,0.625-0.595,0.969-1.03,1.03 c-0.25,0.125-0.485,0.186-0.702,0.186c-0.219,0-0.483,0.033-0.796,0.094c-0.25,0.063-0.514,0.079-0.796,0.047 c-0.281-0.03-0.546-0.047-0.794-0.047h-3.277H54.433c-0.999,0-2.168-0.014-3.51-0.047c-1.342-0.03-2.292,0.172-2.853,0.609 c-0.314,0.25-0.547,0.64-0.702,1.169c-0.156,0.531-0.25,1.14-0.281,1.825c-0.033,0.687-0.033,1.389,0,2.106 c0.03,0.717,0.046,1.357,0.046,1.918v16.096c0,1.062,0.032,2.217,0.094,3.463c0.062,1.249-0.156,2.185-0.655,2.807 c-0.186,0.25-0.469,0.391-0.842,0.422c-0.374,0.032-0.749,0.109-1.123,0.234h-1.779c-0.875,0-1.684-0.03-2.433-0.093 c-0.749-0.062-1.279-0.375-1.59-0.937c-0.25-0.374-0.375-0.888-0.375-1.543c0-0.656,0-1.326,0-2.013v-7.488v-39.119v-10.107 c0-0.686-0.016-1.45-0.047-2.292c-0.032-0.842,0.107-1.512,0.422-2.012c0.249-0.374,0.715-0.685,1.404-0.935 c0.124-0.062,0.264-0.077,0.42-0.047C40.785,330.4,40.925,330.383,41.051,330.321z"
></path>
<path
style={{ fill: '#98806E' }}
d="M96.895,330.508c0.436,0,0.935-0.014,1.496-0.046c0.563-0.032,1.108-0.032,1.639,0 c0.529,0.032,1.013,0.109,1.45,0.234c0.437,0.125,0.749,0.312,0.937,0.561c0.374,0.438,0.576,1.046,0.608,1.825 c0.03,0.78,0.047,1.576,0.047,2.386v9.079v36.124v10.949c0,0.811,0,1.669,0,2.574c0,0.905-0.156,1.606-0.468,2.105 c-0.25,0.314-0.547,0.5-0.889,0.563c-0.344,0.062-0.765,0.156-1.264,0.28h-1.777c-0.874,0-1.684-0.034-2.433-0.094 c-0.749-0.061-1.28-0.374-1.592-0.935c-0.25-0.374-0.375-0.89-0.375-1.545c0-0.655,0-1.325,0-2.012v-7.487V345.95v-10.107 c0-0.686-0.015-1.451-0.045-2.292c-0.034-0.843,0.108-1.514,0.42-2.013c0.249-0.436,0.748-0.748,1.497-0.936 c0.125-0.061,0.25-0.077,0.375-0.047C96.645,330.587,96.769,330.571,96.895,330.508z"
></path>
<path
style={{ fill: '#98806E' }}
d="M126.093,330.321c0.436,0,0.935-0.015,1.498-0.047c0.561-0.03,1.107-0.03,1.638,0 c0.529,0.032,1.012,0.109,1.451,0.234c0.436,0.125,0.748,0.313,0.936,0.562c0.374,0.5,0.56,1.155,0.56,1.965 c0,0.811,0,1.654,0,2.528v9.826v30.042v8.609c0,0.687,0,1.436,0,2.245c0,0.813,0.094,1.468,0.281,1.967 c0.188,0.436,0.529,0.811,1.03,1.122c0.313,0.125,0.717,0.203,1.217,0.235c0.499,0.032,0.998,0.046,1.498,0.046h4.772h16.845 h5.24c0.436,0,0.89-0.014,1.359-0.046c0.467-0.032,0.888,0.016,1.263,0.139c0.81,0.25,1.34,0.594,1.591,1.03 c0.062,0.188,0.124,0.5,0.188,0.937c0.061,0.436,0.107,0.889,0.139,1.357c0.03,0.467,0.015,0.935-0.047,1.402 c-0.062,0.469-0.126,0.829-0.187,1.077c-0.126,0.437-0.406,0.782-0.843,1.029c-0.313,0.25-0.765,0.375-1.357,0.375 c-0.593,0-1.169,0-1.731,0h-6.177H133.58h-6.083c-0.874,0-1.623-0.047-2.246-0.139c-0.624-0.094-1.092-0.39-1.404-0.89 c-0.25-0.374-0.373-0.857-0.373-1.451c0-0.591,0-1.168,0-1.729v-6.74v-25.549v-20.308v-5.802c0-0.561,0.014-1.122,0.045-1.684 c0.032-0.561,0.141-0.998,0.328-1.31c0.249-0.374,0.717-0.685,1.404-0.935c0.124-0.062,0.265-0.077,0.422-0.047 C125.827,330.4,125.968,330.383,126.093,330.321z"
></path>
<path
style={{ fill: '#98806E' }}
d="M187.883,330.321h30.602h8.049c0.686,0,1.372-0.015,2.059-0.047 c0.685-0.03,1.248,0.109,1.684,0.422c0.188,0.125,0.343,0.281,0.468,0.467c0.124,0.188,0.249,0.375,0.374,0.563 c0.125,0.436,0.187,1.138,0.187,2.105c0,0.967-0.062,1.701-0.187,2.199c-0.25,0.874-0.78,1.357-1.592,1.451 c-0.811,0.094-1.777,0.14-2.9,0.14h-9.359h-15.722c-0.998,0-2.169-0.015-3.51-0.047c-1.343-0.03-2.325,0.139-2.949,0.514 c-0.624,0.374-0.951,1.03-0.982,1.966c-0.032,0.936-0.048,1.904-0.048,2.901v9.546c0,0.688-0.015,1.498-0.045,2.433 c-0.034,0.937,0.014,1.686,0.139,2.246c0.188,0.749,0.562,1.249,1.123,1.498c0.249,0.125,0.686,0.25,1.31,0.373h0.935 c0.249,0.063,0.515,0.08,0.796,0.047c0.281-0.03,0.577-0.047,0.89-0.047h3.181h18.343c0.561,0,1.184-0.014,1.872-0.046 c0.685-0.031,1.387-0.031,2.104,0c0.717,0.032,1.372,0.093,1.967,0.187c0.591,0.094,1.044,0.233,1.357,0.421 c0.186,0.126,0.341,0.407,0.467,0.842c0.125,0.438,0.218,0.905,0.282,1.404c0.061,0.5,0.077,1.016,0.045,1.545 c-0.031,0.531-0.109,0.983-0.233,1.357c-0.126,0.561-0.406,0.967-0.842,1.217c-0.127,0.126-0.297,0.203-0.515,0.235 c-0.22,0.032-0.422,0.079-0.608,0.139h-0.563c-0.312,0.063-0.623,0.079-0.935,0.047c-0.313-0.03-0.624-0.047-0.935-0.047h-3.463 h-19.747c-0.747,0-1.482-0.014-2.199-0.047c-0.719-0.03-1.39,0-2.012,0.094c-0.624,0.094-1.171,0.267-1.639,0.515 c-0.467,0.25-0.794,0.687-0.982,1.31c-0.126,0.375-0.173,0.811-0.139,1.31c0.03,0.5,0.045,0.999,0.045,1.498v5.239v8.892 c0,0.873,0.032,1.669,0.094,2.386c0.062,0.718,0.312,1.233,0.749,1.544c0.435,0.313,0.935,0.483,1.497,0.515 c0.561,0.032,1.185,0.046,1.871,0.046h5.99h17.314h5.334c0.499,0,0.998-0.014,1.497-0.046c0.498-0.032,0.936,0.016,1.31,0.139 c0.749,0.25,1.248,0.594,1.498,1.03c0.123,0.188,0.217,0.5,0.281,0.937c0.061,0.436,0.108,0.889,0.14,1.357 c0.03,0.467,0.015,0.935-0.047,1.402c-0.062,0.469-0.126,0.829-0.187,1.077c-0.25,0.5-0.531,0.842-0.842,1.029 c-0.375,0.25-0.858,0.375-1.45,0.375c-0.594,0-1.171,0-1.731,0h-6.552h-24.894h-6.551c-0.874,0-1.623-0.047-2.245-0.139 c-0.624-0.094-1.093-0.39-1.404-0.89c-0.25-0.374-0.374-0.857-0.374-1.451c0-0.591,0-1.168,0-1.729v-6.551v-25.082v-20.964 v-5.802c0-0.561,0-1.122,0-1.684c0-0.561,0.124-0.998,0.374-1.31c0.249-0.435,0.716-0.749,1.404-0.935 c0.125-0.062,0.248-0.077,0.375-0.047C187.632,330.4,187.756,330.383,187.883,330.321z"
></path>
</g>
</g>
<polygon
style={{ fill: '#BBAF98' }}
points="276.167,208.741 0,302.069 0,186.053 512,186.053 512,302.069 "
></polygon>
</g>
</g>
</svg>
)
export const LogIcon = (props: IconSvgProps) => (
<svg
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g
id="SVGRepo_tracerCarrier"
strokeLinecap="round"
strokeLinejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<rect width="48" height="48" fill="white" fillOpacity="0.01"></rect>
<rect
x="13"
y="10"
width="28"
height="34"
fill="#2F88FF"
stroke="#000000"
strokeWidth="4"
strokeLinejoin="round"
></rect>
<path
d="M35 10V4H8C7.44772 4 7 4.44772 7 5V38H13"
stroke="#000000"
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M21 22H33"
stroke="white"
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M21 30H33"
stroke="white"
strokeWidth="4"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</g>
</svg>
)

View File

@@ -0,0 +1,69 @@
import { Button } from '@heroui/button'
import { Input } from '@heroui/input'
import { useRef, useState } from 'react'
export interface FileInputProps {
onChange: (file: File) => Promise<void> | void
onDelete?: () => Promise<void> | void
label?: string
accept?: string
}
const FileInput: React.FC<FileInputProps> = ({
onChange,
onDelete,
label,
accept
}) => {
const inputRef = useRef<HTMLInputElement>(null)
const [isLoading, setIsLoading] = useState(false)
return (
<div className="flex items-end gap-2">
<div className="flex-grow">
<Input
isDisabled={isLoading}
ref={inputRef}
label={label}
type="file"
placeholder="选择文件"
accept={accept}
onChange={async (e) => {
try {
setIsLoading(true)
const file = e.target.files?.[0]
if (file) {
await onChange(file)
}
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
if (inputRef.current) inputRef.current.value = ''
}
}}
/>
</div>
<Button
isDisabled={isLoading}
onPress={async () => {
try {
setIsLoading(true)
if (onDelete) await onDelete()
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
if (inputRef.current) inputRef.current.value = ''
}
}}
color="primary"
variant="flat"
size="sm"
>
</Button>
</div>
)
}
export default FileInput

View File

@@ -43,7 +43,7 @@ const ImageInput: React.FC<ImageInputProps> = ({ onChange, value, label }) => {
onChange('')
if (inputRef.current) inputRef.current.value = ''
}}
color="danger"
color="primary"
variant="flat"
size="sm"
>

View File

@@ -16,13 +16,13 @@ const logLevelColor: {
| 'secondary'
| 'success'
| 'warning'
| 'danger'
| 'primary'
} = {
[LogLevel.DEBUG]: 'default',
[LogLevel.INFO]: 'primary',
[LogLevel.WARN]: 'warning',
[LogLevel.ERROR]: 'danger',
[LogLevel.FATAL]: 'danger'
[LogLevel.ERROR]: 'primary',
[LogLevel.FATAL]: 'primary'
}
const LogLevelSelect = (props: LogLevelSelectProps) => {
const { selectedKeys, onSelectionChange } = props

View File

@@ -65,7 +65,7 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
<ModalFooter>
{showCancel && (
<Button
color="danger"
color="primary"
variant="light"
onPress={() => {
onCancel?.()
@@ -76,7 +76,7 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
</Button>
)}
<Button
color="danger"
color="primary"
onPress={() => {
onConfirm?.()
nativeClose()

View File

@@ -28,7 +28,7 @@ import type {
function displayData(data: number, loading: boolean, error?: Error) {
if (error) {
return <MdError className="text-danger-400" />
return <MdError className="text-primary-400" />
}
if (loading) {
@@ -175,7 +175,7 @@ export default function NapCatRepoInfo() {
className="group h-auto py-3"
endContent={
releaseError ? (
<MdError className="text-danger-400" />
<MdError className="text-primary-400" />
) : releaseLoading ? (
<Spinner size="sm" />
) : (
@@ -229,7 +229,7 @@ export default function NapCatRepoInfo() {
</span>
}
startContent={
<IconWrapper className="bg-danger/10 text-danger dark:text-danger-500">
<IconWrapper className="bg-primary/10 text-primary dark:text-primary-500">
<BookIcon />
</IconWrapper>
}

View File

@@ -150,7 +150,7 @@ const GenericForm = <T extends keyof NetworkConfigType>({
</ModalBody>
<ModalFooter>
<Button
color="danger"
color="primary"
isDisabled={formState.isSubmitting}
variant="light"
onPress={onClose}

View File

@@ -20,7 +20,7 @@ const WebsocketServerForm: React.FC<WebsocketServerFormProps> = ({
enable: false,
name: '',
host: '0.0.0.0',
port: 3000,
port: 3001,
reportSelfMessage: false,
enableForcePushEvent: true,
messagePostFormat: 'array',

View File

@@ -91,7 +91,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
return (
<section className="p-4 pt-14 rounded-lg shadow-md">
<h1 className="text-2xl font-bold mb-4 flex items-center gap-1 text-danger-400">
<h1 className="text-2xl font-bold mb-4 flex items-center gap-1 text-primary-400">
<PiCatDuotone />
{data.description}
</h1>
@@ -125,7 +125,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
/>
<Button
onPress={sendRequest}
color="danger"
color="primary"
size="lg"
radius="full"
isIconOnly
@@ -136,9 +136,9 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
</div>
<Card
shadow="sm"
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible z-20"
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible"
>
<CardHeader className="font-noto-serif font-bold text-lg gap-1 pb-0">
<CardHeader className="font-bold text-lg gap-1 pb-0">
<span className="mr-2"></span>
<Button
color="warning"
@@ -186,7 +186,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
className="my-4 relative bg-opacity-50 backdrop-blur-md"
>
<PageLoading loading={isFetching} />
<CardHeader className="font-noto-serif font-bold text-lg gap-1 pb-0">
<CardHeader className="font-bold text-lg gap-1 pb-0">
<span className="mr-2"></span>
<Button
color="warning"

View File

@@ -27,7 +27,7 @@ const SchemaType = ({
name = '固定值'
break
}
let chipColor: 'primary' | 'success' | 'danger' | 'warning' | 'secondary' =
let chipColor: 'primary' | 'success' | 'primary' | 'warning' | 'secondary' =
'primary'
switch (type) {
case 'enum':
@@ -37,7 +37,7 @@ const SchemaType = ({
chipColor = 'secondary'
break
case 'array':
chipColor = 'danger'
chipColor = 'primary'
break
case 'object':
chipColor = 'success'

View File

@@ -33,11 +33,11 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
>
<div className="w-64 h-full overflow-y-auto px-2 pt-2 pb-10 md:pb-0">
<Input
className="sticky top-0 z-10 text-danger-600"
className="sticky top-0 z-10 text-primary-600"
classNames={{
inputWrapper:
'bg-opacity-30 bg-danger-50 backdrop-blur-sm border border-danger-300 mb-2',
input: 'bg-transparent !text-danger-400 !placeholder-danger-400'
'bg-opacity-30 bg-primary-50 backdrop-blur-sm border border-primary-300 mb-2',
input: 'bg-transparent !text-primary-400 !placeholder-primary-400'
}}
radius="full"
placeholder="搜索 API"
@@ -51,7 +51,7 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
key={apiName}
shadow="none"
className={clsx(
'w-full border border-danger-100 rounded-lg mb-1 bg-opacity-30 backdrop-blur-sm text-danger-400',
'w-full border border-primary-100 rounded-lg mb-1 bg-opacity-30 backdrop-blur-sm text-primary-400',
{
hidden: !(
apiName.includes(searchValue) ||
@@ -59,7 +59,7 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
)
},
{
'!bg-opacity-40 border border-danger-400 bg-danger-50 text-danger-600':
'!bg-opacity-40 border border-primary-400 bg-primary-50 text-primary-600':
apiName === selectedApi
}
)}
@@ -67,10 +67,10 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
onPress={() => onSelect(apiName as OneBotHttpApiPath)}
>
<CardBody>
<h2 className="font-ubuntu font-bold">{api.description}</h2>
<h2 className="font-bold">{api.description}</h2>
<div
className={clsx('text-sm text-danger-200', {
'!text-danger-400': apiName === selectedApi
className={clsx('text-sm text-primary-200', {
'!text-primary-400': apiName === selectedApi
})}
>
{apiName}

View File

@@ -109,7 +109,7 @@ const OneBotItemRender = ({ data, index, style }: OneBotItemRenderProps) => {
<PopoverTrigger>
<Button
size="sm"
color="danger"
color="primary"
variant="flat"
radius="full"
isIconOnly

View File

@@ -30,7 +30,7 @@ const OneBotDisplayResponse: React.FC<OneBotDisplayResponseProps> = ({
<PopoverTrigger>
<Button
size="sm"
color="danger"
color="primary"
variant="flat"
radius="full"
className="text-medium"

View File

@@ -43,7 +43,7 @@ const OneBotSendModal: React.FC<OneBotSendModalProps> = (props) => {
return (
<>
<Button onPress={onOpen} color="danger" radius="full" variant="flat">
<Button onPress={onOpen} color="primary" radius="full" variant="flat">
</Button>
<Modal
@@ -75,11 +75,11 @@ const OneBotSendModal: React.FC<OneBotSendModalProps> = (props) => {
<ModalFooter>
<ChatInputModal />
<Button color="danger" variant="flat" onPress={onClose}>
<Button color="primary" variant="flat" onPress={onClose}>
</Button>
<Button
color="danger"
color="primary"
onPress={() => handleSendMessage(onClose)}
>

View File

@@ -10,7 +10,7 @@ function StatusTag({
color
}: {
title: string
color: 'success' | 'danger' | 'warning'
color: 'success' | 'primary' | 'warning'
}) {
const textClassName = `text-${color} text-sm`
const bgClassName = `bg-${color}`
@@ -27,7 +27,7 @@ export default function WSStatus({ state }: WSStatusProps) {
return <StatusTag title="已连接" color="success" />
}
if (state === ReadyState.CLOSED) {
return <StatusTag title="已关闭" color="danger" />
return <StatusTag title="已关闭" color="primary" />
}
if (state === ReadyState.CONNECTING) {
return <StatusTag title="连接中" color="warning" />

View File

@@ -16,23 +16,21 @@ export interface QQInfoCardProps {
const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
return (
<Card
className="relative bg-danger-100 bg-opacity-60 overflow-hidden flex-shrink-0 shadow-md shadow-danger-300 dark:shadow-danger-50"
className="relative bg-primary-100 bg-opacity-60 overflow-hidden flex-shrink-0 shadow-md shadow-primary-300 dark:shadow-primary-50"
shadow="none"
radius="lg"
>
<PageLoading loading={loading} />
{error ? (
<CardBody className="items-center gap-1 justify-center">
<div className="font-outfit flex-1 text-content1-foreground">
Error
</div>
<div className="flex-1 text-content1-foreground">Error</div>
<div className="whitespace-nowrap text-nowrap flex-shrink-0">
{error.message}
</div>
</CardBody>
) : (
<CardBody className="flex-row items-center gap-2 overflow-hidden relative">
<div className="absolute right-0 bottom-0 text-5xl text-danger-400">
<div className="absolute right-0 bottom-0 text-5xl text-primary-400">
<BsTencentQq />
</div>
<div className="relative flex-shrink-0 z-10">
@@ -45,16 +43,14 @@ const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
/>
<div
className={clsx(
'w-4 h-4 rounded-full absolute right-0.5 bottom-0 border-2 border-danger-100 z-10',
'w-4 h-4 rounded-full absolute right-0.5 bottom-0 border-2 border-primary-100 z-10',
data?.online ? 'bg-green-500' : 'bg-gray-500'
)}
></div>
</div>
<div className="flex-col justify-center">
<div className="font-outfit text-lg truncate">{data?.nick}</div>
<div className="font-ubuntu text-danger-500 text-sm">
{data?.uin}
</div>
<div className="text-lg truncate">{data?.nick}</div>
<div className="text-primary-500 text-sm">{data?.uin}</div>
</div>
</CardBody>
)}

View File

@@ -11,7 +11,7 @@ const QrCodeLogin: React.FC<QrCodeLoginProps> = ({ qrcode }) => {
<div className="bg-white p-2 rounded-md w-fit mx-auto relative overflow-hidden">
{!qrcode && (
<div className="absolute left-2 top-2 right-2 bottom-2 bg-white bg-opacity-50 backdrop-blur flex items-center justify-center">
<Spinner color="danger" />
<Spinner color="primary" />
</div>
)}
<QRCodeSVG size={180} value={qrcode} />

View File

@@ -0,0 +1,265 @@
import {
AnimatePresence,
HTMLMotionProps,
TargetAndTransition,
Transition,
motion
} from 'motion/react'
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useState
} from 'react'
function cn(...classes: (string | undefined | null | boolean)[]): string {
return classes.filter(Boolean).join(' ')
}
export interface RotatingTextRef {
next: () => void
previous: () => void
jumpTo: (index: number) => void
reset: () => void
}
export interface RotatingTextProps
extends Omit<
HTMLMotionProps<'span'>,
'children' | 'transition' | 'initial' | 'animate' | 'exit'
> {
texts: string[]
transition?: Transition
initial?: TargetAndTransition
animate?: TargetAndTransition
exit?: TargetAndTransition
animatePresenceMode?: 'sync' | 'wait'
animatePresenceInitial?: boolean
rotationInterval?: number
staggerDuration?: number
staggerFrom?: 'first' | 'last' | 'center' | 'random' | number
loop?: boolean
auto?: boolean
splitBy?: string
onNext?: (index: number) => void
mainClassName?: string
splitLevelClassName?: string
elementLevelClassName?: string
}
const RotatingText = forwardRef<RotatingTextRef, RotatingTextProps>(
(
{
texts,
transition = { type: 'spring', damping: 25, stiffness: 300 },
initial = { y: '100%', opacity: 0 },
animate = { y: 0, opacity: 1 },
exit = { y: '-120%', opacity: 0 },
animatePresenceMode = 'wait',
animatePresenceInitial = false,
rotationInterval = 2000,
staggerDuration = 0,
staggerFrom = 'first',
loop = true,
auto = true,
splitBy = 'characters',
onNext,
mainClassName,
splitLevelClassName,
elementLevelClassName,
...rest
},
ref
) => {
const [currentTextIndex, setCurrentTextIndex] = useState<number>(0)
const splitIntoCharacters = (text: string): string[] => {
return Array.from(text)
}
const elements = useMemo(() => {
const currentText: string = texts[currentTextIndex]
if (splitBy === 'characters') {
const words = currentText.split(' ')
return words.map((word, i) => ({
characters: splitIntoCharacters(word),
needsSpace: i !== words.length - 1
}))
}
if (splitBy === 'words') {
return currentText.split(' ').map((word, i, arr) => ({
characters: [word],
needsSpace: i !== arr.length - 1
}))
}
if (splitBy === 'lines') {
return currentText.split('\n').map((line, i, arr) => ({
characters: [line],
needsSpace: i !== arr.length - 1
}))
}
return currentText.split(splitBy).map((part, i, arr) => ({
characters: [part],
needsSpace: i !== arr.length - 1
}))
}, [texts, currentTextIndex, splitBy])
const getStaggerDelay = useCallback(
(index: number, totalChars: number): number => {
const total = totalChars
if (staggerFrom === 'first') return index * staggerDuration
if (staggerFrom === 'last') return (total - 1 - index) * staggerDuration
if (staggerFrom === 'center') {
const center = Math.floor(total / 2)
return Math.abs(center - index) * staggerDuration
}
if (staggerFrom === 'random') {
const randomIndex = Math.floor(Math.random() * total)
return Math.abs(randomIndex - index) * staggerDuration
}
return Math.abs((staggerFrom as number) - index) * staggerDuration
},
[staggerFrom, staggerDuration]
)
const handleIndexChange = useCallback(
(newIndex: number) => {
setCurrentTextIndex(newIndex)
if (onNext) onNext(newIndex)
},
[onNext]
)
const next = useCallback(() => {
const nextIndex =
currentTextIndex === texts.length - 1
? loop
? 0
: currentTextIndex
: currentTextIndex + 1
if (nextIndex !== currentTextIndex) {
handleIndexChange(nextIndex)
}
}, [currentTextIndex, texts.length, loop, handleIndexChange])
const previous = useCallback(() => {
const prevIndex =
currentTextIndex === 0
? loop
? texts.length - 1
: currentTextIndex
: currentTextIndex - 1
if (prevIndex !== currentTextIndex) {
handleIndexChange(prevIndex)
}
}, [currentTextIndex, texts.length, loop, handleIndexChange])
const jumpTo = useCallback(
(index: number) => {
const validIndex = Math.max(0, Math.min(index, texts.length - 1))
if (validIndex !== currentTextIndex) {
handleIndexChange(validIndex)
}
},
[texts.length, currentTextIndex, handleIndexChange]
)
const reset = useCallback(() => {
if (currentTextIndex !== 0) {
handleIndexChange(0)
}
}, [currentTextIndex, handleIndexChange])
useImperativeHandle(
ref,
() => ({
next,
previous,
jumpTo,
reset
}),
[next, previous, jumpTo, reset]
)
useEffect(() => {
if (!auto) return
const intervalId = setInterval(next, rotationInterval)
return () => clearInterval(intervalId)
}, [next, rotationInterval, auto])
return (
<motion.span
className={cn(
'flex flex-wrap whitespace-pre-wrap relative',
mainClassName
)}
{...rest}
layout
transition={transition}
>
<span className="sr-only">{texts[currentTextIndex]}</span>
<AnimatePresence
mode={animatePresenceMode}
initial={animatePresenceInitial}
>
<motion.div
key={currentTextIndex}
className={cn(
splitBy === 'lines'
? 'flex flex-col w-full'
: 'flex flex-wrap whitespace-pre-wrap relative'
)}
layout
aria-hidden="true"
initial={initial as HTMLMotionProps<'div'>['initial']}
animate={animate as HTMLMotionProps<'div'>['animate']}
exit={exit as HTMLMotionProps<'div'>['exit']}
>
{elements.map((wordObj, wordIndex, array) => {
const previousCharsCount = array
.slice(0, wordIndex)
.reduce((sum, word) => sum + word.characters.length, 0)
return (
<span
key={wordIndex}
className={cn('inline-flex', splitLevelClassName)}
>
{wordObj.characters.map((char, charIndex) => (
<motion.span
key={charIndex}
initial={initial as HTMLMotionProps<'span'>['initial']}
animate={animate as HTMLMotionProps<'span'>['animate']}
exit={exit as HTMLMotionProps<'span'>['exit']}
transition={{
...transition,
delay: getStaggerDelay(
previousCharsCount + charIndex,
array.reduce(
(sum, word) => sum + word.characters.length,
0
)
)
}}
className={cn('inline-block', elementLevelClassName)}
>
{char}
</motion.span>
))}
{wordObj.needsSpace && (
<span className="whitespace-pre"> </span>
)}
</span>
)
})}
</motion.div>
</AnimatePresence>
</motion.span>
)
}
)
RotatingText.displayName = 'RotatingText'
export default RotatingText

View File

@@ -13,7 +13,6 @@ import { useTheme } from '@/hooks/use-theme'
import logo from '@/assets/images/logo.png'
import type { MenuItem } from '@/config/site'
import { title } from '../primitives'
import Menus from './menus'
interface SideBarProps {
@@ -48,19 +47,15 @@ const SideBar: React.FC<SideBarProps> = (props) => {
style={{ overflow: 'hidden' }}
>
<motion.div className="w-64 flex flex-col items-stretch h-full transition-transform duration-300 ease-in-out z-30 relative float-right">
<div className="flex justify-center items-center mt-2 gap-2">
<Image height={40} src={logo} className="mb-2" />
<div className="flex justify-center items-center my-2 gap-2">
<Image radius="none" height={40} src={logo} className="mb-2" />
<div
className={clsx(
'flex items-center hm-medium',
title({
shadow: true,
color: isDark ? 'violet' : 'pink'
}),
'!text-2xl'
'flex items-center font-bold',
'!text-2xl shiny-text'
)}
>
WebUI
NapCat
</div>
</div>
<div className="overflow-y-auto flex flex-col flex-1 px-4">
@@ -68,7 +63,7 @@ const SideBar: React.FC<SideBarProps> = (props) => {
<div className="mt-auto mb-10 md:mb-0">
<Button
className="w-full"
color="danger"
color="primary"
radius="full"
variant="light"
onPress={toggleTheme}
@@ -80,7 +75,7 @@ const SideBar: React.FC<SideBarProps> = (props) => {
</Button>
<Button
className="w-full mb-2"
color="danger"
color="primary"
radius="full"
variant="light"
onPress={onRevokeAuth}

View File

@@ -55,15 +55,16 @@ const renderItems = (items: MenuItem[], children = false) => {
isActive && 'bg-opacity-60',
b64img && 'backdrop-blur-md text-white'
)}
color="danger"
color="primary"
endContent={
canOpen ? (
// div实现箭头V效果
<div
className={clsx(
'ml-auto relative w-3 h-3 transition-transform',
open && 'transform rotate-180',
isActive ? 'text-danger-500' : 'text-red-300 dark:text-white',
isActive
? 'text-primary-500'
: 'text-primary-200 dark:text-white',
'before:rounded-full',
'before:content-[""]',
'before:block',
@@ -95,8 +96,8 @@ const renderItems = (items: MenuItem[], children = false) => {
className={clsx(
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
isActive
? 'bg-danger-500 animate-spinner-ease-spin'
: 'bg-red-300 dark:bg-white'
? 'bg-primary-500 animate-spinner-ease-spin'
: 'bg-primary-200 dark:bg-white'
)}
/>
)

View File

@@ -4,6 +4,8 @@ import { Chip } from '@heroui/chip'
import { Spinner } from '@heroui/spinner'
import { Tooltip } from '@heroui/tooltip'
import { useRequest } from 'ahooks'
import { useEffect } from 'react'
import { BsStars } from 'react-icons/bs'
import { FaCircleInfo, FaInfo, FaQq } from 'react-icons/fa6'
import { IoLogoChrome, IoLogoOctocat } from 'react-icons/io'
import { RiMacFill } from 'react-icons/ri'
@@ -16,7 +18,6 @@ import { compareVersion } from '@/utils/version'
import WebUIManager from '@/controllers/webui_manager'
import { GithubRelease } from '@/types/github'
import packageJson from '../../package.json'
import TailwindMarkdown from './tailwind_markdown'
export interface SystemInfoItemProps {
@@ -33,10 +34,10 @@ const SystemInfoItem: React.FC<SystemInfoItemProps> = ({
endContent
}) => {
return (
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-danger-50 dark:shadow-danger-100 rounded text-danger-400">
<div className="flex text-sm gap-1 p-2 items-center shadow-sm shadow-primary-100 dark:shadow-primary-100 rounded text-primary-400">
{icon}
<div className="w-24">{title}</div>
<div className="text-danger-200">{value}</div>
<div className="text-primary-200">{value}</div>
<div className="ml-auto">{endContent}</div>
</div>
)
@@ -61,7 +62,7 @@ const NewVersionTip = (props: NewVersionTipProps) => {
<Button
isIconOnly
radius="full"
color="danger"
color="primary"
variant="shadow"
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
onPress={() => {
@@ -98,12 +99,48 @@ const NewVersionTip = (props: NewVersionTipProps) => {
}
}
const AISummaryComponent = () => {
const {
data: aiSummaryData,
loading: aiSummaryLoading,
error: aiSummaryError,
run: runAiSummary
} = useRequest(
(version) =>
request.get<ServerResponse<string | null>>(
`https://release.nc.152710.xyz/?version=${version}`,
{
timeout: 30000
}
),
{
manual: true
}
)
useEffect(() => {
runAiSummary(currentVersion)
}, [currentVersion, runAiSummary])
if (aiSummaryLoading) {
return (
<div className="flex justify-center py-1">
<Spinner size="sm" />
</div>
)
}
if (aiSummaryError) {
return <div className="text-center text-primary-500">AI </div>
}
return <span className="text-default-700">{aiSummaryData?.data.data}</span>
}
return (
<Tooltip content="有新版本可用">
<Button
isIconOnly
radius="full"
color="danger"
color="primary"
variant="shadow"
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
onPress={() => {
@@ -121,6 +158,13 @@ const NewVersionTip = (props: NewVersionTipProps) => {
<span></span>
<Chip color="primary">{latestVersion}</Chip>
</div>
<div className="p-2 rounded-md bg-content2 text-sm">
<div className="text-primary-400 font-bold flex items-center gap-1 mb-1">
<BsStars />
<span>AI总结</span>
</div>
{<AISummaryComponent />}
</div>
<div className="text-sm space-y-2 !mt-4">
{middleVersions.map((versionInfo) => (
<div
@@ -190,19 +234,14 @@ const SystemInfo: React.FC<SystemInfoProps> = (props) => {
error: qqVersionError
} = useRequest(WebUIManager.getQQVersion)
return (
<Card className="bg-opacity-60 shadow-sm shadow-danger-50 dark:shadow-danger-100 overflow-visible flex-1">
<CardHeader className="pb-0 items-center gap-1 text-danger-500 font-extrabold">
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 dark:shadow-primary-100 overflow-visible flex-1">
<CardHeader className="pb-0 items-center gap-1 text-primary-500 font-extrabold">
<FaCircleInfo className="text-lg" />
<span></span>
</CardHeader>
<CardBody className="flex-1">
<div className="flex flex-col justify-between h-full">
<NapCatVersion />
<SystemInfoItem
title="WebUI 版本"
icon={<IoLogoChrome className="text-xl" />}
value={packageJson.version}
/>
<SystemInfoItem
title="QQ 版本"
icon={<FaQq className="text-lg" />}
@@ -216,6 +255,11 @@ const SystemInfo: React.FC<SystemInfoProps> = (props) => {
)
}
/>
<SystemInfoItem
title="WebUI 版本"
icon={<IoLogoChrome className="text-xl" />}
value="Next"
/>
<SystemInfoItem
title="系统版本"
icon={<RiMacFill className="text-xl" />}

View File

@@ -24,7 +24,7 @@ const SystemStatusItem: React.FC<SystemStatusItemProps> = ({
return (
<div
className={clsx(
'shadow-sm p-2 rounded-md text-sm bg-content1 bg-opacity-30',
'shadow-sm shadow-primary-100 p-2 rounded-md text-sm bg-content1 bg-opacity-30',
size === 'lg' ? 'col-span-2' : 'col-span-1 flex justify-between'
)}
>
@@ -55,7 +55,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
}
return (
<Card className="bg-opacity-60 shadow-sm shadow-danger-50 dark:shadow-danger-100 col-span-1 lg:col-span-2 relative overflow-hidden">
<Card className="bg-opacity-60 shadow-sm shadow-primary-100 col-span-1 lg:col-span-2 relative overflow-hidden">
<div className="absolute h-full right-0 top-0">
<Image
src={bkg}
@@ -69,7 +69,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
</div>
<CardBody className="overflow-visible md:flex-row gap-4 items-center justify-stretch z-10">
<div className="flex-1 w-full md:max-w-96">
<h2 className="text-lg font-semibold flex items-center gap-1 text-danger-400">
<h2 className="text-lg font-semibold flex items-center gap-1 text-primary-400">
<GiCpu className="text-xl" />
<span>CPU</span>
</h2>
@@ -88,7 +88,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
unit="%"
/>
</div>
<h2 className="text-lg font-semibold flex items-center gap-1 text-danger-400 mt-2">
<h2 className="text-lg font-semibold flex items-center gap-1 text-primary-400 mt-2">
<BiSolidMemoryCard className="text-xl" />
<span></span>
</h2>

View File

@@ -0,0 +1,89 @@
import clsx from 'clsx'
import { type ReactNode, createContext, forwardRef, useContext } from 'react'
export interface TabsContextValue {
activeKey: string
onChange: (key: string) => void
}
const TabsContext = createContext<TabsContextValue>({
activeKey: '',
onChange: () => {}
})
export interface TabsProps {
activeKey: string
onChange: (key: string) => void
children: ReactNode
className?: string
}
export function Tabs({ activeKey, onChange, children, className }: TabsProps) {
return (
<TabsContext.Provider value={{ activeKey, onChange }}>
<div className={clsx('flex flex-col gap-2', className)}>{children}</div>
</TabsContext.Provider>
)
}
export interface TabListProps {
children: ReactNode
className?: string
}
export function TabList({ children, className }: TabListProps) {
return (
<div className={clsx('flex items-center gap-1', className)}>{children}</div>
)
}
export interface TabProps extends React.ButtonHTMLAttributes<HTMLDivElement> {
value: string
className?: string
children: ReactNode
isSelected?: boolean
}
export const Tab = forwardRef<HTMLDivElement, TabProps>(
({ className, isSelected, value, ...props }, ref) => {
const { onChange } = useContext(TabsContext)
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
onChange(value)
props.onClick?.(e)
}
return (
<div
ref={ref}
role="tab"
aria-selected={isSelected}
onClick={handleClick}
className={clsx(
'px-2 py-1 flex items-center gap-1 text-sm font-medium border-b-2 transition-colors',
isSelected
? 'border-primary text-primary'
: 'border-transparent hover:border-default',
className
)}
{...props}
/>
)
}
)
Tab.displayName = 'Tab'
export interface TabPanelProps {
value: string
children: ReactNode
className?: string
}
export function TabPanel({ value, children, className }: TabPanelProps) {
const { activeKey } = useContext(TabsContext)
if (value !== activeKey) return null
return <div className={clsx('flex-1', className)}>{children}</div>
}

View File

@@ -0,0 +1,38 @@
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { Tab } from '@/components/tabs'
import type { TabProps } from '@/components/tabs'
interface SortableTabProps extends TabProps {
id: string
}
export function SortableTab({ id, ...props }: SortableTabProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging
} = useSortable({ id })
const style = {
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 1 : 0,
position: 'relative' as const,
touchAction: 'none'
}
return (
<Tab
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
{...props}
/>
)
}

View File

@@ -19,7 +19,7 @@ const TailwindMarkdown: React.FC<{ content: string }> = ({ content }) => {
p: ({ node, ...props }) => <p className="m-0" {...props} />,
a: ({ node, ...props }) => (
<a
className="text-blue-500 hover:underline"
className="text-primary-500 inline-block hover:underline"
target="_blank"
{...props}
/>
@@ -32,12 +32,12 @@ const TailwindMarkdown: React.FC<{ content: string }> = ({ content }) => {
),
blockquote: ({ node, ...props }) => (
<blockquote
className="border-l-4 border-gray-300 pl-4 italic"
className="border-l-4 border-default-300 pl-4 italic"
{...props}
/>
),
code: ({ node, ...props }) => (
<code className="bg-gray-100 p-1 rounded" {...props} />
<code className="bg-default-100 p-1 rounded text-xs" {...props} />
)
}}
>

View File

@@ -0,0 +1,56 @@
import { useEffect, useRef } from 'react'
import TerminalManager from '@/controllers/terminal_manager'
import XTerm, { XTermRef } from '../xterm'
interface TerminalInstanceProps {
id: string
}
export function TerminalInstance({ id }: TerminalInstanceProps) {
const termRef = useRef<XTermRef>(null)
const connected = useRef(false)
const handleData = (data: string) => {
try {
const parsed = JSON.parse(data)
if (parsed.data) {
termRef.current?.write(parsed.data)
}
} catch (e) {
termRef.current?.write(data)
}
}
useEffect(() => {
return () => {
if (connected.current) {
TerminalManager.disconnectTerminal(id, handleData)
}
}
}, [id])
const handleInput = (data: string) => {
TerminalManager.sendInput(id, data)
}
const handleResize = (cols: number, rows: number) => {
if (!connected.current) {
connected.current = true
console.log('instance', rows, cols)
TerminalManager.connectTerminal(id, handleData, { rows, cols })
} else {
TerminalManager.sendResize(id, cols, rows)
}
}
return (
<XTerm
ref={termRef}
onInput={handleInput}
onResize={handleResize} // 使用 fitAddon 改变后触发的 resize 回调
className="w-full h-full"
/>
)
}

View File

@@ -0,0 +1,12 @@
export default function UnderConstruction() {
return (
<div className="flex flex-col items-center justify-center h-full pt-4">
<div className="flex flex-col items-center justify-center space-y-4">
<div className="text-6xl font-bold text-gray-500">🚧</div>
<div className="text-2xl font-bold text-gray-500">
Under Construction
</div>
</div>
</div>
)
}

View File

@@ -1,6 +1,7 @@
import { CanvasAddon } from '@xterm/addon-canvas'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
import { WebglAddon } from '@xterm/addon-webgl'
// import { WebglAddon } from '@xterm/addon-webgl'
import { Terminal } from '@xterm/xterm'
import '@xterm/xterm/css/xterm.css'
import clsx from 'clsx'
@@ -8,8 +9,6 @@ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
import { useTheme } from '@/hooks/use-theme'
import { gradientText } from '@/utils/terminal'
export type XTermRef = {
write: (
...args: Parameters<Terminal['write']>
@@ -20,134 +19,174 @@ export type XTermRef = {
) => ReturnType<Terminal['writeln']>
writelnAsync: (data: Parameters<Terminal['writeln']>[0]) => Promise<void>
clear: () => void
terminalRef: React.RefObject<Terminal | null>
}
const XTerm = forwardRef<XTermRef, React.HTMLAttributes<HTMLDivElement>>(
(props, ref) => {
const domRef = useRef<HTMLDivElement>(null)
const terminalRef = useRef<Terminal | null>(null)
const { className, ...rest } = props
const { theme } = useTheme()
useEffect(() => {
if (!domRef.current) {
return
export interface XTermProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onInput' | 'onResize'> {
onInput?: (data: string) => void
onKey?: (key: string, event: KeyboardEvent) => void
onResize?: (cols: number, rows: number) => void // 新增属性
}
const XTerm = forwardRef<XTermRef, XTermProps>((props, ref) => {
const domRef = useRef<HTMLDivElement>(null)
const terminalRef = useRef<Terminal | null>(null)
const { className, onInput, onKey, onResize, ...rest } = props
const { theme } = useTheme()
useEffect(() => {
const terminal = new Terminal({
allowTransparency: true,
fontFamily:
'"JetBrains Mono", "Aa偷吃可爱长大的", "Noto Serif SC", monospace',
cursorInactiveStyle: 'outline',
drawBoldTextInBrightColors: false,
fontSize: 14,
lineHeight: 1.2
})
terminalRef.current = terminal
const fitAddon = new FitAddon()
terminal.loadAddon(
new WebLinksAddon((event, uri) => {
if (event.ctrlKey || event.metaKey) {
window.open(uri, '_blank')
}
})
)
terminal.loadAddon(fitAddon)
terminal.open(domRef.current!)
terminal.loadAddon(new CanvasAddon())
terminal.onData((data) => {
if (onInput) {
onInput(data)
}
const terminal = new Terminal({
allowTransparency: true,
fontFamily: '"Fira Code", "Harmony", "Noto Serif SC", monospace',
cursorInactiveStyle: 'outline',
drawBoldTextInBrightColors: false,
letterSpacing: 0,
lineHeight: 1.0
})
terminalRef.current = terminal
const fitAddon = new FitAddon()
terminal.loadAddon(
new WebLinksAddon((event, uri) => {
if (event.ctrlKey) {
window.open(uri, '_blank')
}
})
)
terminal.loadAddon(fitAddon)
terminal.loadAddon(new WebglAddon())
terminal.open(domRef.current)
})
terminal.writeln(
gradientText(
'Welcome to NapCat WebUI',
[255, 0, 0],
[0, 255, 0],
true,
true,
true
)
)
const resizeObserver = new ResizeObserver(() => {
fitAddon.fit()
})
// 字体加载完成后重新调整终端大小
document.fonts.ready.then(() => {
fitAddon.fit()
resizeObserver.observe(domRef.current!)
})
return () => {
resizeObserver.disconnect()
setTimeout(() => {
terminal.dispose()
}, 0)
terminal.onKey((event) => {
if (onKey) {
onKey(event.key, event.domEvent)
}
}, [])
})
useEffect(() => {
if (terminalRef.current) {
const resizeObserver = new ResizeObserver(() => {
fitAddon.fit()
// 获取当前终端尺寸
const cols = terminal.cols
const rows = terminal.rows
if (onResize) {
onResize(cols, rows)
}
})
// 字体加载完成后重新调整终端大小
document.fonts.ready.then(() => {
fitAddon.fit()
resizeObserver.observe(domRef.current!)
})
return () => {
resizeObserver.disconnect()
setTimeout(() => {
terminal.dispose()
}, 0)
}
}, [])
useEffect(() => {
if (terminalRef.current) {
if (theme === 'dark') {
terminalRef.current.options.theme = {
background: theme === 'dark' ? '#00000000' : '#ffffff00',
foreground: theme === 'dark' ? '#fff' : '#000',
selectionBackground:
theme === 'dark'
? 'rgba(179, 0, 0, 0.3)'
: 'rgba(255, 167, 167, 0.3)',
cursor: theme === 'dark' ? '#fff' : '#000',
cursorAccent: theme === 'dark' ? '#000' : '#fff',
black: theme === 'dark' ? '#fff' : '#000'
background: '#00000000',
black: '#ffffff',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
cyan: '#11a8cd',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightCyan: '#29b8db',
brightWhite: '#e5e5e5',
foreground: '#cccccc',
selectionBackground: '#3a3d41',
cursor: '#ffffff'
}
} else {
terminalRef.current.options.theme = {
background: '#ffffff00',
black: '#000000',
red: '#aa3731',
green: '#448c27',
yellow: '#cb9000',
blue: '#325cc0',
cyan: '#0083b2',
white: '#7f7f7f',
brightBlack: '#777777',
brightRed: '#f05050',
brightGreen: '#60cb00',
brightYellow: '#ffbc5d',
brightBlue: '#007acc',
brightCyan: '#00aacb',
brightWhite: '#b0b0b0',
foreground: '#000000',
selectionBackground: '#bfdbfe',
cursor: '#007acc'
}
terminalRef.current.options.fontWeight =
theme === 'dark' ? 'normal' : '600'
terminalRef.current.options.fontWeightBold =
theme === 'dark' ? 'bold' : '900'
}
}, [theme])
}
}, [theme])
useImperativeHandle(
ref,
() => ({
write: (...args) => {
return terminalRef.current?.write(...args)
},
writeAsync: async (data) => {
return new Promise((resolve) => {
terminalRef.current?.write(data, resolve)
})
},
writeln: (...args) => {
return terminalRef.current?.writeln(...args)
},
writelnAsync: async (data) => {
return new Promise((resolve) => {
terminalRef.current?.writeln(data, resolve)
})
},
clear: () => {
terminalRef.current?.clear()
}
}),
[]
)
useImperativeHandle(
ref,
() => ({
write: (...args) => {
return terminalRef.current?.write(...args)
},
writeAsync: async (data) => {
return new Promise((resolve) => {
terminalRef.current?.write(data, resolve)
})
},
writeln: (...args) => {
return terminalRef.current?.writeln(...args)
},
writelnAsync: async (data) => {
return new Promise((resolve) => {
terminalRef.current?.writeln(data, resolve)
})
},
clear: () => {
terminalRef.current?.clear()
},
terminalRef: terminalRef
}),
[]
)
return (
return (
<div
className={clsx(
'p-2 rounded-md shadow-sm border border-default-200 w-full h-full overflow-hidden bg-opacity-50 backdrop-blur-sm',
theme === 'dark' ? 'bg-black' : 'bg-white',
className
)}
{...rest}
>
<div
className={clsx(
'p-2 rounded-md shadow-sm border border-default-200 w-full h-full overflow-hidden bg-opacity-50 backdrop-blur-sm',
theme === 'dark' ? 'bg-black' : 'bg-white',
className
)}
{...rest}
>
<div
style={{
width: '100%',
height: '100%'
}}
ref={domRef}
></div>
</div>
)
}
)
style={{
width: '100%',
height: '100%'
}}
ref={domRef}
></div>
</div>
)
})
export default XTerm

View File

@@ -1,6 +1,8 @@
import {
BugIcon2,
FileIcon,
InfoIcon,
LogIcon,
RouteIcon,
SettingsIcon,
SignalTowerIcon,
@@ -49,10 +51,10 @@ export const siteConfig = {
href: '/config'
},
{
label: '系统日志',
label: '猫猫日志',
icon: (
<div className="w-5 h-5">
<TerminalIcon />
<LogIcon />
</div>
),
href: '/logs'
@@ -75,6 +77,24 @@ export const siteConfig = {
}
]
},
{
label: '文件管理',
icon: (
<div className="w-5 h-5">
<FileIcon />
</div>
),
href: '/file_manager'
},
{
label: '系统终端',
icon: (
<div className="w-5 h-5">
<TerminalIcon />
</div>
),
href: '/terminal'
},
{
label: '关于我们',
icon: (

View File

@@ -0,0 +1,6 @@
import heroui from './themes/heroui'
import nc_pink from './themes/nc_pink'
const themes: ThemeInfo[] = [nc_pink, heroui]
export default themes

View File

@@ -0,0 +1,256 @@
const theme: ThemeConfig = {
dark: {
'--heroui-background': '0 0% 0%',
'--heroui-foreground-50': '240 5.88% 10%',
'--heroui-foreground-100': '240 3.7% 15.88%',
'--heroui-foreground-200': '240 5.26% 26.08%',
'--heroui-foreground-300': '240 5.2% 33.92%',
'--heroui-foreground-400': '240 3.83% 46.08%',
'--heroui-foreground-500': '240 5.03% 64.9%',
'--heroui-foreground-600': '240 4.88% 83.92%',
'--heroui-foreground-700': '240 5.88% 90%',
'--heroui-foreground-800': '240 4.76% 95.88%',
'--heroui-foreground-900': '0 0% 98.04%',
'--heroui-foreground': '210 5.56% 92.94%',
'--heroui-focus': '212.01999999999998 100% 46.67%',
'--heroui-overlay': '0 0% 0%',
'--heroui-divider': '0 0% 100%',
'--heroui-divider-opacity': '0.15',
'--heroui-content1': '240 5.88% 10%',
'--heroui-content1-foreground': '0 0% 98.04%',
'--heroui-content2': '240 3.7% 15.88%',
'--heroui-content2-foreground': '240 4.76% 95.88%',
'--heroui-content3': '240 5.26% 26.08%',
'--heroui-content3-foreground': '240 5.88% 90%',
'--heroui-content4': '240 5.2% 33.92%',
'--heroui-content4-foreground': '240 4.88% 83.92%',
'--heroui-default-50': '240 5.88% 10%',
'--heroui-default-100': '240 3.7% 15.88%',
'--heroui-default-200': '240 5.26% 26.08%',
'--heroui-default-300': '240 5.2% 33.92%',
'--heroui-default-400': '240 3.83% 46.08%',
'--heroui-default-500': '240 5.03% 64.9%',
'--heroui-default-600': '240 4.88% 83.92%',
'--heroui-default-700': '240 5.88% 90%',
'--heroui-default-800': '240 4.76% 95.88%',
'--heroui-default-900': '0 0% 98.04%',
'--heroui-default-foreground': '0 0% 100%',
'--heroui-default': '240 5.26% 26.08%',
'--heroui-danger-50': '340 84.91% 10.39%',
'--heroui-danger-100': '339.33 86.54% 20.39%',
'--heroui-danger-200': '339.11 85.99% 30.78%',
'--heroui-danger-300': '339 86.54% 40.78%',
'--heroui-danger-400': '339.2 90.36% 51.18%',
'--heroui-danger-500': '339 90% 60.78%',
'--heroui-danger-600': '339.11 90.6% 70.78%',
'--heroui-danger-700': '339.33 90% 80.39%',
'--heroui-danger-800': '340 91.84% 90.39%',
'--heroui-danger-900': '339.13 92% 95.1%',
'--heroui-danger-foreground': '0 0% 100%',
'--heroui-danger': '339.2 90.36% 51.18%',
'--heroui-primary-50': '211.84 100% 9.61%',
'--heroui-primary-100': '211.84 100% 19.22%',
'--heroui-primary-200': '212.24 100% 28.82%',
'--heroui-primary-300': '212.14 100% 38.43%',
'--heroui-primary-400': '212.02 100% 46.67%',
'--heroui-primary-500': '212.14 92.45% 58.43%',
'--heroui-primary-600': '212.24 92.45% 68.82%',
'--heroui-primary-700': '211.84 92.45% 79.22%',
'--heroui-primary-800': '211.84 92.45% 89.61%',
'--heroui-primary-900': '212.5 92.31% 94.9%',
'--heroui-primary-foreground': '0 0% 100%',
'--heroui-primary': '212.02 100% 46.67%',
'--heroui-secondary-50': '270 66.67% 9.41%',
'--heroui-secondary-100': '270 66.67% 18.82%',
'--heroui-secondary-200': '270 66.67% 28.24%',
'--heroui-secondary-300': '270 66.67% 37.65%',
'--heroui-secondary-400': '270 66.67% 47.06%',
'--heroui-secondary-500': '270 59.26% 57.65%',
'--heroui-secondary-600': '270 59.26% 68.24%',
'--heroui-secondary-700': '270 59.26% 78.82%',
'--heroui-secondary-800': '270 59.26% 89.41%',
'--heroui-secondary-900': '270 61.54% 94.9%',
'--heroui-secondary-foreground': '0 0% 100%',
'--heroui-secondary': '270 59.26% 57.65%',
'--heroui-success-50': '145.71 77.78% 8.82%',
'--heroui-success-100': '146.2 79.78% 17.45%',
'--heroui-success-200': '145.79 79.26% 26.47%',
'--heroui-success-300': '146.01 79.89% 35.1%',
'--heroui-success-400': '145.96 79.46% 43.92%',
'--heroui-success-500': '146.01 62.45% 55.1%',
'--heroui-success-600': '145.79 62.57% 66.47%',
'--heroui-success-700': '146.2 61.74% 77.45%',
'--heroui-success-800': '145.71 61.4% 88.82%',
'--heroui-success-900': '146.67 64.29% 94.51%',
'--heroui-success-foreground': '0 0% 0%',
'--heroui-success': '145.96 79.46% 43.92%',
'--heroui-warning-50': '37.14 75% 10.98%',
'--heroui-warning-100': '37.14 75% 21.96%',
'--heroui-warning-200': '36.96 73.96% 33.14%',
'--heroui-warning-300': '37.01 74.22% 44.12%',
'--heroui-warning-400': '37.03 91.27% 55.1%',
'--heroui-warning-500': '37.01 91.26% 64.12%',
'--heroui-warning-600': '36.96 91.24% 73.14%',
'--heroui-warning-700': '37.14 91.3% 81.96%',
'--heroui-warning-800': '37.14 91.3% 90.98%',
'--heroui-warning-900': '54.55 91.67% 95.29%',
'--heroui-warning-foreground': '0 0% 0%',
'--heroui-warning': '37.03 91.27% 55.1%',
'--heroui-code-background': '240 5.56% 7.06%',
'--heroui-strong': '190.14 94.67% 44.12%',
'--heroui-code-mdx': '190.14 94.67% 44.12%',
'--heroui-divider-weight': '1px',
'--heroui-disabled-opacity': '.5',
'--heroui-font-size-tiny': '0.75rem',
'--heroui-font-size-small': '0.875rem',
'--heroui-font-size-medium': '1rem',
'--heroui-font-size-large': '1.125rem',
'--heroui-line-height-tiny': '1rem',
'--heroui-line-height-small': '1.25rem',
'--heroui-line-height-medium': '1.5rem',
'--heroui-line-height-large': '1.75rem',
'--heroui-radius-small': '8px',
'--heroui-radius-medium': '12px',
'--heroui-radius-large': '14px',
'--heroui-border-width-small': '1px',
'--heroui-border-width-medium': '2px',
'--heroui-border-width-large': '3px',
'--heroui-box-shadow-small':
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-box-shadow-medium':
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-box-shadow-large':
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-hover-opacity': '.9'
},
light: {
'--heroui-background': '0 0% 100%',
'--heroui-foreground-50': '240 5.88% 95%',
'--heroui-foreground-100': '240 3.7% 90%',
'--heroui-foreground-200': '240 5.26% 80%',
'--heroui-foreground-300': '240 5.2% 70%',
'--heroui-foreground-400': '240 3.83% 60%',
'--heroui-foreground-500': '240 5.03% 50%',
'--heroui-foreground-600': '240 4.88% 40%',
'--heroui-foreground-700': '240 5.88% 30%',
'--heroui-foreground-800': '240 4.76% 20%',
'--heroui-foreground-900': '0 0% 10%',
'--heroui-foreground': '210 5.56% 7.06%',
'--heroui-focus': '212.01999999999998 100% 53.33%',
'--heroui-overlay': '0 0% 100%',
'--heroui-divider': '0 0% 0%',
'--heroui-divider-opacity': '0.85',
'--heroui-content1': '240 5.88% 95%',
'--heroui-content1-foreground': '0 0% 10%',
'--heroui-content2': '240 3.7% 90%',
'--heroui-content2-foreground': '240 4.76% 20%',
'--heroui-content3': '240 5.26% 80%',
'--heroui-content3-foreground': '240 5.88% 30%',
'--heroui-content4': '240 5.2% 70%',
'--heroui-content4-foreground': '240 4.88% 40%',
'--heroui-default-50': '240 5.88% 95%',
'--heroui-default-100': '240 3.7% 90%',
'--heroui-default-200': '240 5.26% 80%',
'--heroui-default-300': '240 5.2% 70%',
'--heroui-default-400': '240 3.83% 60%',
'--heroui-default-500': '240 5.03% 50%',
'--heroui-default-600': '240 4.88% 40%',
'--heroui-default-700': '240 5.88% 30%',
'--heroui-default-800': '240 4.76% 20%',
'--heroui-default-900': '0 0% 10%',
'--heroui-default-foreground': '0 0% 0%',
'--heroui-default': '240 5.26% 80%',
'--heroui-danger-50': '339.13 92% 95.1%',
'--heroui-danger-100': '340 91.84% 90.39%',
'--heroui-danger-200': '339.33 90% 80.39%',
'--heroui-danger-300': '339.11 90.6% 70.78%',
'--heroui-danger-400': '339 90% 60.78%',
'--heroui-danger-500': '339.2 90.36% 51.18%',
'--heroui-danger-600': '339 86.54% 40.78%',
'--heroui-danger-700': '339.11 85.99% 30.78%',
'--heroui-danger-800': '339.33 86.54% 20.39%',
'--heroui-danger-900': '340 84.91% 10.39%',
'--heroui-danger-foreground': '0 0% 100%',
'--heroui-danger': '339.2 90.36% 51.18%',
'--heroui-primary-50': '212.5 92.31% 94.9%',
'--heroui-primary-100': '211.84 92.45% 89.61%',
'--heroui-primary-200': '211.84 92.45% 79.22%',
'--heroui-primary-300': '212.24 92.45% 68.82%',
'--heroui-primary-400': '212.14 92.45% 58.43%',
'--heroui-primary-500': '212.02 100% 46.67%',
'--heroui-primary-600': '212.14 100% 38.43%',
'--heroui-primary-700': '212.24 100% 28.82%',
'--heroui-primary-800': '211.84 100% 19.22%',
'--heroui-primary-900': '211.84 100% 9.61%',
'--heroui-primary-foreground': '0 0% 100%',
'--heroui-primary': '212.02 100% 46.67%',
'--heroui-secondary-50': '270 61.54% 94.9%',
'--heroui-secondary-100': '270 59.26% 89.41%',
'--heroui-secondary-200': '270 59.26% 78.82%',
'--heroui-secondary-300': '270 59.26% 68.24%',
'--heroui-secondary-400': '270 59.26% 57.65%',
'--heroui-secondary-500': '270 66.67% 47.06%',
'--heroui-secondary-600': '270 66.67% 37.65%',
'--heroui-secondary-700': '270 66.67% 28.24%',
'--heroui-secondary-800': '270 66.67% 18.82%',
'--heroui-secondary-900': '270 66.67% 9.41%',
'--heroui-secondary-foreground': '0 0% 100%',
'--heroui-secondary': '270 66.67% 47.06%',
'--heroui-success-50': '146.67 64.29% 94.51%',
'--heroui-success-100': '145.71 61.4% 88.82%',
'--heroui-success-200': '146.2 61.74% 77.45%',
'--heroui-success-300': '145.79 62.57% 66.47%',
'--heroui-success-400': '146.01 62.45% 55.1%',
'--heroui-success-500': '145.96 79.46% 43.92%',
'--heroui-success-600': '146.01 79.89% 35.1%',
'--heroui-success-700': '145.79 79.26% 26.47%',
'--heroui-success-800': '146.2 79.78% 17.45%',
'--heroui-success-900': '145.71 77.78% 8.82%',
'--heroui-success-foreground': '0 0% 0%',
'--heroui-success': '145.96 79.46% 43.92%',
'--heroui-warning-50': '54.55 91.67% 95.29%',
'--heroui-warning-100': '37.14 91.3% 90.98%',
'--heroui-warning-200': '37.14 91.3% 81.96%',
'--heroui-warning-300': '36.96 91.24% 73.14%',
'--heroui-warning-400': '37.01 91.26% 64.12%',
'--heroui-warning-500': '37.03 91.27% 55.1%',
'--heroui-warning-600': '37.01 74.22% 44.12%',
'--heroui-warning-700': '36.96 73.96% 33.14%',
'--heroui-warning-800': '37.14 75% 21.96%',
'--heroui-warning-900': '37.14 75% 10.98%',
'--heroui-warning-foreground': '0 0% 0%',
'--heroui-warning': '37.03 91.27% 55.1%',
'--heroui-code-background': '221.25 17.39% 18.04%',
'--heroui-strong': '316.95 100% 65.29%',
'--heroui-code-mdx': '316.95 100% 65.29%',
'--heroui-divider-weight': '1px',
'--heroui-disabled-opacity': '.5',
'--heroui-font-size-tiny': '0.75rem',
'--heroui-font-size-small': '0.875rem',
'--heroui-font-size-medium': '1rem',
'--heroui-font-size-large': '1.125rem',
'--heroui-line-height-tiny': '1rem',
'--heroui-line-height-small': '1.25rem',
'--heroui-line-height-medium': '1.5rem',
'--heroui-line-height-large': '1.75rem',
'--heroui-radius-small': '8px',
'--heroui-radius-medium': '12px',
'--heroui-radius-large': '14px',
'--heroui-border-width-small': '1px',
'--heroui-border-width-medium': '2px',
'--heroui-border-width-large': '3px',
'--heroui-box-shadow-small':
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-box-shadow-medium':
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-box-shadow-large':
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-hover-opacity': '.8'
}
}
export default {
theme,
author: 'HeroUI',
name: 'heroui',
description: 'HeroUI Default Theme'
} satisfies ThemeInfo

View File

@@ -0,0 +1,256 @@
const theme: ThemeConfig = {
dark: {
'--heroui-background': '0 0% 0%',
'--heroui-foreground-50': '240 5.88% 10%',
'--heroui-foreground-100': '240 3.7% 15.88%',
'--heroui-foreground-200': '240 5.26% 26.08%',
'--heroui-foreground-300': '240 5.2% 33.92%',
'--heroui-foreground-400': '240 3.83% 46.08%',
'--heroui-foreground-500': '240 5.03% 64.9%',
'--heroui-foreground-600': '240 4.88% 83.92%',
'--heroui-foreground-700': '240 5.88% 90%',
'--heroui-foreground-800': '240 4.76% 95.88%',
'--heroui-foreground-900': '0 0% 98.04%',
'--heroui-foreground': '210 5.56% 92.94%',
'--heroui-focus': '212.01999999999998 100% 46.67%',
'--heroui-overlay': '0 0% 0%',
'--heroui-divider': '0 0% 100%',
'--heroui-divider-opacity': '0.15',
'--heroui-content1': '240 5.88% 10%',
'--heroui-content1-foreground': '0 0% 98.04%',
'--heroui-content2': '240 3.7% 15.88%',
'--heroui-content2-foreground': '240 4.76% 95.88%',
'--heroui-content3': '240 5.26% 26.08%',
'--heroui-content3-foreground': '240 5.88% 90%',
'--heroui-content4': '240 5.2% 33.92%',
'--heroui-content4-foreground': '240 4.88% 83.92%',
'--heroui-default-50': '240 5.88% 10%',
'--heroui-default-100': '240 3.7% 15.88%',
'--heroui-default-200': '240 5.26% 26.08%',
'--heroui-default-300': '240 5.2% 33.92%',
'--heroui-default-400': '240 3.83% 46.08%',
'--heroui-default-500': '240 5.03% 64.9%',
'--heroui-default-600': '240 4.88% 83.92%',
'--heroui-default-700': '240 5.88% 90%',
'--heroui-default-800': '240 4.76% 95.88%',
'--heroui-default-900': '0 0% 98.04%',
'--heroui-default-foreground': '0 0% 100%',
'--heroui-default': '240 5.26% 26.08%',
'--heroui-danger-50': '301.89 82.61% 22.55%',
'--heroui-danger-100': '308.18 76.39% 28.24%',
'--heroui-danger-200': '313.85 70.65% 36.08%',
'--heroui-danger-300': '319.73 65.64% 44.51%',
'--heroui-danger-400': '325.82 69.62% 53.53%',
'--heroui-danger-500': '331.82 75% 65.49%',
'--heroui-danger-600': '337.84 83.46% 73.92%',
'--heroui-danger-700': '343.42 90.48% 83.53%',
'--heroui-danger-800': '350.53 90.48% 91.76%',
'--heroui-danger-900': '324 90.91% 95.69%',
'--heroui-danger-foreground': '0 0% 100%',
'--heroui-danger': '325.82 69.62% 53.53%',
'--heroui-primary-50': '340 84.91% 10.39%',
'--heroui-primary-100': '339.33 86.54% 20.39%',
'--heroui-primary-200': '339.11 85.99% 30.78%',
'--heroui-primary-300': '339 86.54% 40.78%',
'--heroui-primary-400': '339.2 90.36% 51.18%',
'--heroui-primary-500': '339 90% 60.78%',
'--heroui-primary-600': '339.11 90.6% 70.78%',
'--heroui-primary-700': '339.33 90% 80.39%',
'--heroui-primary-800': '340 91.84% 90.39%',
'--heroui-primary-900': '339.13 92% 95.1%',
'--heroui-primary-foreground': '0 0% 100%',
'--heroui-primary': '339.2 90.36% 51.18%',
'--heroui-secondary-50': '270 66.67% 9.41%',
'--heroui-secondary-100': '270 66.67% 18.82%',
'--heroui-secondary-200': '270 66.67% 28.24%',
'--heroui-secondary-300': '270 66.67% 37.65%',
'--heroui-secondary-400': '270 66.67% 47.06%',
'--heroui-secondary-500': '270 59.26% 57.65%',
'--heroui-secondary-600': '270 59.26% 68.24%',
'--heroui-secondary-700': '270 59.26% 78.82%',
'--heroui-secondary-800': '270 59.26% 89.41%',
'--heroui-secondary-900': '270 61.54% 94.9%',
'--heroui-secondary-foreground': '0 0% 100%',
'--heroui-secondary': '270 59.26% 57.65%',
'--heroui-success-50': '145.71 77.78% 8.82%',
'--heroui-success-100': '146.2 79.78% 17.45%',
'--heroui-success-200': '145.79 79.26% 26.47%',
'--heroui-success-300': '146.01 79.89% 35.1%',
'--heroui-success-400': '145.96 79.46% 43.92%',
'--heroui-success-500': '146.01 62.45% 55.1%',
'--heroui-success-600': '145.79 62.57% 66.47%',
'--heroui-success-700': '146.2 61.74% 77.45%',
'--heroui-success-800': '145.71 61.4% 88.82%',
'--heroui-success-900': '146.67 64.29% 94.51%',
'--heroui-success-foreground': '0 0% 0%',
'--heroui-success': '145.96 79.46% 43.92%',
'--heroui-warning-50': '37.14 75% 10.98%',
'--heroui-warning-100': '37.14 75% 21.96%',
'--heroui-warning-200': '36.96 73.96% 33.14%',
'--heroui-warning-300': '37.01 74.22% 44.12%',
'--heroui-warning-400': '37.03 91.27% 55.1%',
'--heroui-warning-500': '37.01 91.26% 64.12%',
'--heroui-warning-600': '36.96 91.24% 73.14%',
'--heroui-warning-700': '37.14 91.3% 81.96%',
'--heroui-warning-800': '37.14 91.3% 90.98%',
'--heroui-warning-900': '54.55 91.67% 95.29%',
'--heroui-warning-foreground': '0 0% 0%',
'--heroui-warning': '37.03 91.27% 55.1%',
'--heroui-code-background': '240 5.56% 7.06%',
'--heroui-strong': '190.14 94.67% 44.12%',
'--heroui-code-mdx': '190.14 94.67% 44.12%',
'--heroui-divider-weight': '1px',
'--heroui-disabled-opacity': '.5',
'--heroui-font-size-tiny': '0.75rem',
'--heroui-font-size-small': '0.875rem',
'--heroui-font-size-medium': '1rem',
'--heroui-font-size-large': '1.125rem',
'--heroui-line-height-tiny': '1rem',
'--heroui-line-height-small': '1.25rem',
'--heroui-line-height-medium': '1.5rem',
'--heroui-line-height-large': '1.75rem',
'--heroui-radius-small': '8px',
'--heroui-radius-medium': '12px',
'--heroui-radius-large': '14px',
'--heroui-border-width-small': '1px',
'--heroui-border-width-medium': '2px',
'--heroui-border-width-large': '3px',
'--heroui-box-shadow-small':
'0px 0px 5px 0px rgba(0, 0, 0, .05), 0px 2px 10px 0px rgba(0, 0, 0, .2), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-box-shadow-medium':
'0px 0px 15px 0px rgba(0, 0, 0, .06), 0px 2px 30px 0px rgba(0, 0, 0, .22), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-box-shadow-large':
'0px 0px 30px 0px rgba(0, 0, 0, .07), 0px 30px 60px 0px rgba(0, 0, 0, .26), inset 0px 0px 1px 0px hsla(0, 0%, 100%, .15)',
'--heroui-hover-opacity': '.9'
},
light: {
'--heroui-background': '0 0% 100%',
'--heroui-foreground-50': '240 5.88% 95%',
'--heroui-foreground-100': '240 3.7% 90%',
'--heroui-foreground-200': '240 5.26% 80%',
'--heroui-foreground-300': '240 5.2% 70%',
'--heroui-foreground-400': '240 3.83% 60%',
'--heroui-foreground-500': '240 5.03% 50%',
'--heroui-foreground-600': '240 4.88% 40%',
'--heroui-foreground-700': '240 5.88% 30%',
'--heroui-foreground-800': '240 4.76% 20%',
'--heroui-foreground-900': '0 0% 10%',
'--heroui-foreground': '210 5.56% 7.06%',
'--heroui-focus': '212.01999999999998 100% 53.33%',
'--heroui-overlay': '0 0% 100%',
'--heroui-divider': '0 0% 0%',
'--heroui-divider-opacity': '0.85',
'--heroui-content1': '240 5.88% 95%',
'--heroui-content1-foreground': '0 0% 10%',
'--heroui-content2': '240 3.7% 90%',
'--heroui-content2-foreground': '240 4.76% 20%',
'--heroui-content3': '240 5.26% 80%',
'--heroui-content3-foreground': '240 5.88% 30%',
'--heroui-content4': '240 5.2% 70%',
'--heroui-content4-foreground': '240 4.88% 40%',
'--heroui-default-50': '240 5.88% 95%',
'--heroui-default-100': '240 3.7% 90%',
'--heroui-default-200': '240 5.26% 80%',
'--heroui-default-300': '240 5.2% 70%',
'--heroui-default-400': '240 3.83% 60%',
'--heroui-default-500': '240 5.03% 50%',
'--heroui-default-600': '240 4.88% 40%',
'--heroui-default-700': '240 5.88% 30%',
'--heroui-default-800': '240 4.76% 20%',
'--heroui-default-900': '0 0% 10%',
'--heroui-default-foreground': '0 0% 0%',
'--heroui-default': '240 5.26% 80%',
'--heroui-danger-50': '324 90.91% 95.69%',
'--heroui-danger-100': '350.53 90.48% 91.76%',
'--heroui-danger-200': '343.42 90.48% 83.53%',
'--heroui-danger-300': '337.84 83.46% 73.92%',
'--heroui-danger-400': '331.82 75% 65.49%',
'--heroui-danger-500': '325.82 69.62% 53.53%',
'--heroui-danger-600': '319.73 65.64% 44.51%',
'--heroui-danger-700': '313.85 70.65% 36.08%',
'--heroui-danger-800': '308.18 76.39% 28.24%',
'--heroui-danger-900': '301.89 82.61% 22.55%',
'--heroui-danger-foreground': '0 0% 100%',
'--heroui-danger': '325.82 69.62% 53.53%',
'--heroui-primary-50': '339.13 92% 95.1%',
'--heroui-primary-100': '340 91.84% 90.39%',
'--heroui-primary-200': '339.33 90% 80.39%',
'--heroui-primary-300': '339.11 90.6% 70.78%',
'--heroui-primary-400': '339 90% 60.78%',
'--heroui-primary-500': '339.2 90.36% 51.18%',
'--heroui-primary-600': '339 86.54% 40.78%',
'--heroui-primary-700': '339.11 85.99% 30.78%',
'--heroui-primary-800': '339.33 86.54% 20.39%',
'--heroui-primary-900': '340 84.91% 10.39%',
'--heroui-primary-foreground': '0 0% 100%',
'--heroui-primary': '339.2 90.36% 51.18%',
'--heroui-secondary-50': '270 61.54% 94.9%',
'--heroui-secondary-100': '270 59.26% 89.41%',
'--heroui-secondary-200': '270 59.26% 78.82%',
'--heroui-secondary-300': '270 59.26% 68.24%',
'--heroui-secondary-400': '270 59.26% 57.65%',
'--heroui-secondary-500': '270 66.67% 47.06%',
'--heroui-secondary-600': '270 66.67% 37.65%',
'--heroui-secondary-700': '270 66.67% 28.24%',
'--heroui-secondary-800': '270 66.67% 18.82%',
'--heroui-secondary-900': '270 66.67% 9.41%',
'--heroui-secondary-foreground': '0 0% 100%',
'--heroui-secondary': '270 66.67% 47.06%',
'--heroui-success-50': '146.67 64.29% 94.51%',
'--heroui-success-100': '145.71 61.4% 88.82%',
'--heroui-success-200': '146.2 61.74% 77.45%',
'--heroui-success-300': '145.79 62.57% 66.47%',
'--heroui-success-400': '146.01 62.45% 55.1%',
'--heroui-success-500': '145.96 79.46% 43.92%',
'--heroui-success-600': '146.01 79.89% 35.1%',
'--heroui-success-700': '145.79 79.26% 26.47%',
'--heroui-success-800': '146.2 79.78% 17.45%',
'--heroui-success-900': '145.71 77.78% 8.82%',
'--heroui-success-foreground': '0 0% 0%',
'--heroui-success': '145.96 79.46% 43.92%',
'--heroui-warning-50': '54.55 91.67% 95.29%',
'--heroui-warning-100': '37.14 91.3% 90.98%',
'--heroui-warning-200': '37.14 91.3% 81.96%',
'--heroui-warning-300': '36.96 91.24% 73.14%',
'--heroui-warning-400': '37.01 91.26% 64.12%',
'--heroui-warning-500': '37.03 91.27% 55.1%',
'--heroui-warning-600': '37.01 74.22% 44.12%',
'--heroui-warning-700': '36.96 73.96% 33.14%',
'--heroui-warning-800': '37.14 75% 21.96%',
'--heroui-warning-900': '37.14 75% 10.98%',
'--heroui-warning-foreground': '0 0% 0%',
'--heroui-warning': '37.03 91.27% 55.1%',
'--heroui-code-background': '221.25 17.39% 18.04%',
'--heroui-strong': '316.95 100% 65.29%',
'--heroui-code-mdx': '316.95 100% 65.29%',
'--heroui-divider-weight': '1px',
'--heroui-disabled-opacity': '.5',
'--heroui-font-size-tiny': '0.75rem',
'--heroui-font-size-small': '0.875rem',
'--heroui-font-size-medium': '1rem',
'--heroui-font-size-large': '1.125rem',
'--heroui-line-height-tiny': '1rem',
'--heroui-line-height-small': '1.25rem',
'--heroui-line-height-medium': '1.5rem',
'--heroui-line-height-large': '1.75rem',
'--heroui-radius-small': '8px',
'--heroui-radius-medium': '12px',
'--heroui-radius-large': '14px',
'--heroui-border-width-small': '1px',
'--heroui-border-width-medium': '2px',
'--heroui-border-width-large': '3px',
'--heroui-box-shadow-small':
'0px 0px 5px 0px rgba(0, 0, 0, .02), 0px 2px 10px 0px rgba(0, 0, 0, .06), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-box-shadow-medium':
'0px 0px 15px 0px rgba(0, 0, 0, .03), 0px 2px 30px 0px rgba(0, 0, 0, .08), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-box-shadow-large':
'0px 0px 30px 0px rgba(0, 0, 0, .04), 0px 30px 60px 0px rgba(0, 0, 0, .12), 0px 0px 1px 0px rgba(0, 0, 0, .3)',
'--heroui-hover-opacity': '.8'
}
}
export default {
theme,
author: 'NapCat',
name: 'nc_pink',
description: 'NapCat Pink Theme'
} satisfies ThemeInfo

View File

@@ -35,6 +35,7 @@ const AudioProvider: React.FC<MusicProviderProps> = ({ children }) => {
const [musicId, setMusicId] = useState<number>(0)
const [playMode, setPlayMode] = useState<PlayMode>(PlayMode.Loop)
const music = musicList.find((music) => music.id === musicId)
const [token] = useLocalStorage(key.token, '')
const onNext = () => {
const nextID = getNextMusic(musicList, musicId, playMode)
setMusicId(nextID)
@@ -60,8 +61,8 @@ const AudioProvider: React.FC<MusicProviderProps> = ({ children }) => {
setMusicId(res[0].id)
}
useEffect(() => {
fetchMusicList(listId)
}, [listId])
if (listId && token) fetchMusicList(listId)
}, [listId, token])
return (
<AudioContext.Provider
value={{

View File

@@ -0,0 +1,221 @@
import toast from 'react-hot-toast'
import { serverRequest } from '@/utils/request'
export interface FileInfo {
name: string
isDirectory: boolean
size: number
mtime: Date
}
export default class FileManager {
public static async listFiles(path: string = '/') {
const { data } = await serverRequest.get<ServerResponse<FileInfo[]>>(
`/File/list?path=${encodeURIComponent(path)}`
)
return data.data
}
// 新增:按目录获取
public static async listDirectories(path: string = '/') {
const { data } = await serverRequest.get<ServerResponse<FileInfo[]>>(
`/File/list?path=${encodeURIComponent(path)}&onlyDirectory=true`
)
return data.data
}
public static async createDirectory(path: string): Promise<boolean> {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/mkdir',
{ path }
)
return data.data
}
public static async delete(path: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/delete',
{ path }
)
return data.data
}
public static async readFile(path: string) {
const { data } = await serverRequest.get<ServerResponse<string>>(
`/File/read?path=${encodeURIComponent(path)}`
)
return data.data
}
public static async writeFile(path: string, content: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/write',
{ path, content }
)
return data.data
}
public static async createFile(path: string): Promise<boolean> {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/create',
{ path }
)
return data.data
}
public static async batchDelete(paths: string[]) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/batchDelete',
{ paths }
)
return data.data
}
public static async rename(oldPath: string, newPath: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/rename',
{ oldPath, newPath }
)
return data.data
}
public static async move(sourcePath: string, targetPath: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/move',
{ sourcePath, targetPath }
)
return data.data
}
public static async batchMove(
items: { sourcePath: string; targetPath: string }[]
) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/batchMove',
{ items }
)
return data.data
}
public static download(path: string) {
const downloadUrl = `/File/download?path=${encodeURIComponent(path)}`
toast
.promise(
serverRequest
.post(downloadUrl, void 0, {
responseType: 'blob'
})
.catch((e) => {
console.error(e)
throw new Error('下载失败')
}),
{
loading: '正在下载文件...',
success: '下载成功',
error: '下载失败'
}
)
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
let fileName = path.split('/').pop() || ''
if (path.split('.').length === 1) {
fileName += '.zip'
}
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch((e) => {
console.error(e)
})
}
public static async batchDownload(paths: string[]) {
const downloadUrl = `/File/batchDownload`
toast
.promise(
serverRequest
.post(
downloadUrl,
{ paths },
{
responseType: 'blob'
}
)
.catch((e) => {
console.error(e)
throw new Error('下载失败')
}),
{
loading: '正在下载文件...',
success: '下载成功',
error: '下载失败'
}
)
.then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
const fileName = 'files.zip'
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
.catch((e) => {
console.error(e)
})
}
public static async downloadToURL(path: string) {
const downloadUrl = `/File/download?path=${encodeURIComponent(path)}`
const response = await serverRequest.post(downloadUrl, void 0, {
responseType: 'blob'
})
return window.URL.createObjectURL(new Blob([response.data]))
}
public static async upload(path: string, files: File[]) {
const formData = new FormData()
files.forEach((file) => {
formData.append('files', file)
})
const { data } = await serverRequest.post<ServerResponse<boolean>>(
`/File/upload?path=${encodeURIComponent(path)}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
return data.data
}
public static async uploadWebUIFont(file: File) {
const formData = new FormData()
formData.append('file', file)
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/font/upload/webui',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
)
return data.data
}
public static async deleteWebUIFont() {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/File/font/delete/webui'
)
return data.data
}
}

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