mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
380 Commits
v4.5.2
...
protobuf-d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c50359c504 | ||
![]() |
364500e16e | ||
![]() |
22144010ec | ||
![]() |
6421bb4f5c | ||
![]() |
3919743885 | ||
![]() |
a5a57b9e20 | ||
![]() |
e31d2810ad | ||
![]() |
140e62fdcd | ||
![]() |
014b4deb87 | ||
![]() |
956b6cd172 | ||
![]() |
bbaca3f044 | ||
![]() |
bb8a44b918 | ||
![]() |
b5574d5999 | ||
![]() |
06dde072da | ||
![]() |
8e92a81bb9 | ||
![]() |
2c7345ae88 | ||
![]() |
33d4696155 | ||
![]() |
7d2dcc10e5 | ||
![]() |
e82687454c | ||
![]() |
84382caebc | ||
![]() |
662530e507 | ||
![]() |
edf81d0a2e | ||
![]() |
7cbae86941 | ||
![]() |
8ff7420a5e | ||
![]() |
7ae59b1419 | ||
![]() |
41036f8ee8 | ||
![]() |
380777ca04 | ||
![]() |
c658cd1096 | ||
![]() |
c7b9946d2f | ||
![]() |
0caca473d6 | ||
![]() |
3e5d35957d | ||
![]() |
6b8b14aba2 | ||
![]() |
5db7a90a24 | ||
![]() |
88b86611a3 | ||
![]() |
886fe2052e | ||
![]() |
e4dd194d4a | ||
![]() |
a47af60f58 | ||
![]() |
35f24eb806 | ||
![]() |
36e3119d34 | ||
![]() |
8ff3ad824e | ||
![]() |
556000c002 | ||
![]() |
fda050d3fe | ||
![]() |
b1047309c9 | ||
![]() |
d766c4945e | ||
![]() |
43c98c45b9 | ||
![]() |
f7556b5af3 | ||
![]() |
cd781c4cf6 | ||
![]() |
cd8698b157 | ||
![]() |
d921dcddf1 | ||
![]() |
9f318ddaef | ||
![]() |
5c35ea11c3 | ||
![]() |
3b16effff0 | ||
![]() |
d3a27ad701 | ||
![]() |
2a4589e268 | ||
![]() |
80a34c82b9 | ||
![]() |
fca7a65ee0 | ||
![]() |
30a75bc581 | ||
![]() |
7b365367f7 | ||
![]() |
3ed5f543e2 | ||
![]() |
ceea50b116 | ||
![]() |
a5455e27d1 | ||
![]() |
6f83d01321 | ||
![]() |
c453b82e9f | ||
![]() |
b7da316447 | ||
![]() |
fb20b2e16c | ||
![]() |
9df7c341a9 | ||
![]() |
7c113d6e04 | ||
![]() |
a6f22167ff | ||
![]() |
d49e69735a | ||
![]() |
eca73eae18 | ||
![]() |
d3a34dfdf9 | ||
![]() |
623188d884 | ||
![]() |
f093f52792 | ||
![]() |
d53607a118 | ||
![]() |
5f637e064a | ||
![]() |
e4b21e94f5 | ||
![]() |
fc37288827 | ||
![]() |
dad7245a3a | ||
![]() |
4190831081 | ||
![]() |
c509a01d7d | ||
![]() |
6d259593fd | ||
![]() |
bd3e06520f | ||
![]() |
41dccd98a9 | ||
![]() |
54e6d5c3f2 | ||
![]() |
3f6249f39c | ||
![]() |
a888714629 | ||
![]() |
17ef3231df | ||
![]() |
cc30b51d58 | ||
![]() |
9d40eacc15 | ||
![]() |
a0415c5f4e | ||
![]() |
941978b578 | ||
![]() |
06538b9122 | ||
![]() |
dd895d7c17 | ||
![]() |
9257a6cfde | ||
![]() |
f8260067ab | ||
![]() |
40b06daf1e | ||
![]() |
e30915a06b | ||
![]() |
2ab3898d28 | ||
![]() |
6e38e748b8 | ||
![]() |
7ecdd63bef | ||
![]() |
8056962203 | ||
![]() |
7a42f8c26f | ||
![]() |
6c510e42e8 | ||
![]() |
45d6ebf084 | ||
![]() |
2147c4ffee | ||
![]() |
d4ab191f34 | ||
![]() |
a5e53a713b | ||
![]() |
e3f965a9d6 | ||
![]() |
dd00d4c8a5 | ||
![]() |
4d292a75fa | ||
![]() |
50b6733f57 | ||
![]() |
19479b4b3c | ||
![]() |
540f58d8ec | ||
![]() |
749f1dfcf9 | ||
![]() |
176691bb96 | ||
![]() |
b9ec8ac9b1 | ||
![]() |
28d973b9cb | ||
![]() |
a6be54937c | ||
![]() |
0664b9af84 | ||
![]() |
407d8d1fd2 | ||
![]() |
108897f6ad | ||
![]() |
3d2decb0ec | ||
![]() |
386b884f1b | ||
![]() |
ace4da2297 | ||
![]() |
a8fb48fb50 | ||
![]() |
61f065c0c6 | ||
![]() |
d6cf6d120a | ||
![]() |
c20c19d8e0 | ||
![]() |
bd8bbf76ab | ||
![]() |
faccff1834 | ||
![]() |
99d3c5a117 | ||
![]() |
31eb09edef | ||
![]() |
4180c2d754 | ||
![]() |
68f5deedff | ||
![]() |
9f72196414 | ||
![]() |
b32efa9131 | ||
![]() |
3cf502fea3 | ||
![]() |
863a953ae1 | ||
![]() |
a44104d8f7 | ||
![]() |
f602bbb0cf | ||
![]() |
2807ff5927 | ||
![]() |
4fb8e6a4da | ||
![]() |
7ec61f089d | ||
![]() |
f7a500a8cf | ||
![]() |
2b319bd694 | ||
![]() |
24cf7c01f8 | ||
![]() |
aa50e73909 | ||
![]() |
d9b33b5439 | ||
![]() |
da58c6bec0 | ||
![]() |
c4cbac4331 | ||
![]() |
ac26a99143 | ||
![]() |
640252d391 | ||
![]() |
258a1dda5e | ||
![]() |
53a7ce2e46 | ||
![]() |
291e2fd8fd | ||
![]() |
f180c7698f | ||
![]() |
183d6f3011 | ||
![]() |
ba71d7ad03 | ||
![]() |
26cfaac3bd | ||
![]() |
556c8b24c0 | ||
![]() |
31f0f527b7 | ||
![]() |
b2e0cab702 | ||
![]() |
3e3609e0f2 | ||
![]() |
83e73d9842 | ||
![]() |
673a175ddf | ||
![]() |
8403b1c9c1 | ||
![]() |
26593d593c | ||
![]() |
4d26ec737b | ||
![]() |
31fdffde36 | ||
![]() |
bccf5894cd | ||
![]() |
2754165ae5 | ||
![]() |
b9a9438bf0 | ||
![]() |
d8b2ebc01e | ||
![]() |
a979a07d3d | ||
![]() |
12eacd3530 | ||
![]() |
030f0551fd | ||
![]() |
47f5947410 | ||
![]() |
aaefa2e83c | ||
![]() |
f8e92f7c8d | ||
![]() |
b6430e6eb6 | ||
![]() |
e60605c7bb | ||
![]() |
d4d94e43d8 | ||
![]() |
58d2bd3c81 | ||
![]() |
6534d05b76 | ||
![]() |
2d7de174c5 | ||
![]() |
79aa1dc67f | ||
![]() |
7792ad9ea0 | ||
![]() |
be6671923b | ||
![]() |
0fa1b3f044 | ||
![]() |
3deb7788ae | ||
![]() |
4ab751696b | ||
![]() |
dce4eedf7d | ||
![]() |
129b67b751 | ||
![]() |
9ab776d53a | ||
![]() |
a790303ebe | ||
![]() |
99bfe69752 | ||
![]() |
2759a34d96 | ||
![]() |
1c8cf9538d | ||
![]() |
2f9f42750e | ||
![]() |
30abd1f904 | ||
![]() |
008075466e | ||
![]() |
5b4035c320 | ||
![]() |
e3feb6a73c | ||
![]() |
40fe73317d | ||
![]() |
073745030c | ||
![]() |
c523437506 | ||
![]() |
9eef570d37 | ||
![]() |
be37b8cbbd | ||
![]() |
c635496677 | ||
![]() |
8753ecfd92 | ||
![]() |
5eda1f2870 | ||
![]() |
d5a60074f7 | ||
![]() |
91df57d932 | ||
![]() |
e27d4c4302 | ||
![]() |
55847f6e10 | ||
![]() |
b39d8bae27 | ||
![]() |
b0cf23f775 | ||
![]() |
c641246056 | ||
![]() |
1e5bc9bbea | ||
![]() |
99b504b5f6 | ||
![]() |
1146454fec | ||
![]() |
805e014a75 | ||
![]() |
d3acd1efc1 | ||
![]() |
9fcd218a5a | ||
![]() |
d6a0830cfe | ||
![]() |
40a63b9c66 | ||
![]() |
eeb19a04cc | ||
![]() |
91e457eb03 | ||
![]() |
78d1919d7f | ||
![]() |
8393acf173 | ||
![]() |
bca152a047 | ||
![]() |
6a15908a93 | ||
![]() |
c626bbab74 | ||
![]() |
c5c7dcc6f2 | ||
![]() |
03dafe727e | ||
![]() |
744921c45e | ||
![]() |
abc4a4dcba | ||
![]() |
7e0da2f929 | ||
![]() |
a3b70d0f1f | ||
![]() |
d291724f06 | ||
![]() |
122a9ca2cc | ||
![]() |
48aaddd32b | ||
![]() |
47401af856 | ||
![]() |
709adfd812 | ||
![]() |
038d0c5412 | ||
![]() |
6bb4362ed4 | ||
![]() |
e617f9452d | ||
![]() |
6d8bb49a37 | ||
![]() |
4f6073ee86 | ||
![]() |
2e7176304b | ||
![]() |
e36cf11004 | ||
![]() |
0e49e17f68 | ||
![]() |
524de45f6b | ||
![]() |
85741a4b60 | ||
![]() |
f9ccb8c978 | ||
![]() |
ea3d069e49 | ||
![]() |
3e6024f183 | ||
![]() |
337871693a | ||
![]() |
2d921c4577 | ||
![]() |
9accff7323 | ||
![]() |
88b1ee8c31 | ||
![]() |
3ac618bb4e | ||
![]() |
0051df3741 | ||
![]() |
7eb4e010b0 | ||
![]() |
33cc23ada3 | ||
![]() |
e5aee372e3 | ||
![]() |
6b6ce4a761 | ||
![]() |
8c4ea7f8f2 | ||
![]() |
c8b268b806 | ||
![]() |
faf390bb18 | ||
![]() |
be2e2e86f0 | ||
![]() |
cf5e0e0f14 | ||
![]() |
7b79f9cc17 | ||
![]() |
708d599966 | ||
![]() |
1ecd5b78e6 | ||
![]() |
fca2e3c51a | ||
![]() |
95ea761b2d | ||
![]() |
6b3bfa1ee9 | ||
![]() |
df3e302a9d | ||
![]() |
c88a68c9a8 | ||
![]() |
92d01b9cdd | ||
![]() |
fe04fa5986 | ||
![]() |
c382f541b4 | ||
![]() |
f420527207 | ||
![]() |
e0c83ebf79 | ||
![]() |
c7fb18fc08 | ||
![]() |
2db8ab937d | ||
![]() |
819f5dd8e5 | ||
![]() |
d4a8ed735e | ||
![]() |
f07e3bb4d5 | ||
![]() |
fa5ef0c221 | ||
![]() |
da7499ec0b | ||
![]() |
d2f4327e44 | ||
![]() |
2eba640180 | ||
![]() |
29ae55f340 | ||
![]() |
3d2bca3f9f | ||
![]() |
7fd8c0c822 | ||
![]() |
a9e9c81505 | ||
![]() |
e8cc68bdea | ||
![]() |
9e51a661a4 | ||
![]() |
a167aaf55f | ||
![]() |
a54ecbcaa0 | ||
![]() |
788462cdfa | ||
![]() |
45c5965b99 | ||
![]() |
ce7614de46 | ||
![]() |
9f78e1ce1e | ||
![]() |
2c7b0625e8 | ||
![]() |
c3a5da9be1 | ||
![]() |
ca796e1920 | ||
![]() |
7ce04cf781 | ||
![]() |
024a3eb760 | ||
![]() |
1702f429b4 | ||
![]() |
96d79cf495 | ||
![]() |
a6a11a7026 | ||
![]() |
970a49e2a5 | ||
![]() |
2e013ed4f5 | ||
![]() |
f8c396b1fe | ||
![]() |
b54870cb60 | ||
![]() |
84318acb18 | ||
![]() |
a11a042b93 | ||
![]() |
8a8aa8f62c | ||
![]() |
93f78f4db5 | ||
![]() |
404bfdd5e6 | ||
![]() |
e4577dc2f1 | ||
![]() |
5c932e5a27 | ||
![]() |
4bd63c6267 | ||
![]() |
aabe24f903 | ||
![]() |
69cebd7fbc | ||
![]() |
8da371176a | ||
![]() |
dd08adf1d1 | ||
![]() |
2f67bef139 | ||
![]() |
8968c51cdc | ||
![]() |
f2fdcc9289 | ||
![]() |
aa3a575cbe | ||
![]() |
11816d038d | ||
![]() |
6a990edb38 | ||
![]() |
fa12865924 | ||
![]() |
ecdd717742 | ||
![]() |
6851334af9 | ||
![]() |
9051b29565 | ||
![]() |
95c7d3dfbd | ||
![]() |
bc1148c00a | ||
![]() |
d4556d9299 | ||
![]() |
5d389a2359 | ||
![]() |
305116874b | ||
![]() |
b08a29897f | ||
![]() |
b59c1d9122 | ||
![]() |
adb9cea701 | ||
![]() |
5e148d2e82 | ||
![]() |
a0d780558e | ||
![]() |
ad56065a4e | ||
![]() |
f5dee80b6e | ||
![]() |
9cc75881b8 | ||
![]() |
593fb13b61 | ||
![]() |
fca90592d6 | ||
![]() |
d6848e2855 | ||
![]() |
7539a4129f | ||
![]() |
5402574266 | ||
![]() |
853175aa1a | ||
![]() |
feb84809ec | ||
![]() |
a812c568e4 | ||
![]() |
11db25e355 | ||
![]() |
ecd2fba629 | ||
![]() |
a6763cf5a1 | ||
![]() |
c9e91a9b94 | ||
![]() |
43fb62c5bd | ||
![]() |
cb8727d487 | ||
![]() |
a94e03e2fd | ||
![]() |
425c3c6432 | ||
![]() |
89b9610016 | ||
![]() |
62fe88f868 | ||
![]() |
11a7f5fade | ||
![]() |
fbde997f7c | ||
![]() |
26734a35ef | ||
![]() |
715c4ac534 | ||
![]() |
941b30847b | ||
![]() |
4c5a26698e | ||
![]() |
d14a1dd948 | ||
![]() |
1c0b434f47 | ||
![]() |
573451bade |
115
.vscode/launch.json
vendored
Normal file
115
.vscode/launch.json
vendored
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -6,5 +6,7 @@
|
|||||||
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
|
"tsconfig.json": "tsconfig.*.json, env.d.ts, vite.config.ts",
|
||||||
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
|
||||||
},
|
},
|
||||||
"css.customData": [".vscode/tailwindcss.json"],
|
"css.customData": [
|
||||||
|
".vscode/tailwindcss.json"
|
||||||
|
],
|
||||||
}
|
}
|
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal 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.
|
86
README.md
86
README.md
@@ -1,67 +1,67 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
# NapCat
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
_Modern protocol-side framework implemented based on NTQQ._
|
||||||
|
|
||||||
|
> 云起兮风生,心向远方兮路未曾至.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
## 欢迎回家
|
|
||||||
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
|
||||||
|
|
||||||
## 特性介绍
|
## Welcome
|
||||||
- [x] **安装简单**:就算是笨蛋也能使用
|
+ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
|
||||||
- [x] **性能友好**:就算是低内存也能使用
|
- NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
|
||||||
- [x] **接口丰富**:就算是没有也能使用
|
|
||||||
- [x] **稳定好用**:就算是被捉也能使用
|
|
||||||
|
|
||||||
## 使用框架
|
## Feature
|
||||||
|
|
||||||
|
+ **Easy to Use**
|
||||||
|
- 作为初学者能够轻松使用.
|
||||||
|
+ **Quick and Efficient**
|
||||||
|
- 在低内存操作系统长时运行.
|
||||||
|
+ **Rich API Interface**
|
||||||
|
- 完整实现了大部分标准接口.
|
||||||
|
+ **Stable and Reliable**
|
||||||
|
- 持续稳定的开发与维护.
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
|
||||||
|
|
||||||
**首次使用**请务必查看如下文档看使用教程
|
**首次使用**请务必查看如下文档看使用教程
|
||||||
|
|
||||||
### 文档地址
|
## Link
|
||||||
|
|
||||||
[Cloudflare.Worker](https://doc.napneko.icu/)
|
| Docs | [](https://napneko.github.io/) | [](https://doc.napneko.icu/) | [](https://napcat.napneko.icu/) |
|
||||||
|
|:-:|:-:|:-:|:-:|
|
||||||
|
|
||||||
[Cloudflare.HKServer](https://napcat.napneko.icu/)
|
| Docs | [](https://napneko.pages.dev/) | [](https://napcat.cyou/) | [](https://www.napcat.wiki) |
|
||||||
|
|:-:|:-:|:-:|:-:|
|
||||||
|
|
||||||
[Github.IO](https://napneko.github.io/)
|
| QQ Group | [](https://qm.qq.com/q/CMmPbGw0jA) | [](https://qm.qq.com/q/8zJMLjqy2Y) | [](https://qm.qq.com/q/HaRcfrHpUk) | [](https://qm.qq.com/q/I6LU87a0Yq) |
|
||||||
|
|:-:|:-:|:-:|:-:|:-:|
|
||||||
|
|
||||||
[Cloudflare.Pages](https://napneko.pages.dev/)
|
| Telegram | [](https://t.me/MelodicMoonlight) |
|
||||||
|
|:-:|:-:|
|
||||||
|
|
||||||
[Server.Other](https://docs.napcat.cyou/)
|
## Thanks
|
||||||
|
|
||||||
[NapCat.Wiki](https://www.napcat.wiki)
|
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
|
||||||
|
|
||||||
## 回家旅途
|
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
|
||||||
[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
|
|
||||||
|
|
||||||
不过最最重要的 还是需要感谢屏幕前的你哦~
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 特殊感谢
|
## License
|
||||||
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
|
本项目采用 混合协议 开源,因此使用本项目时,你需要注意以下几点:
|
||||||
|
1. 第三方库代码或修改部分遵循其原始开源许可.
|
||||||
|
2. 本项目获取部分项目授权而不受部分约束
|
||||||
|
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
|
||||||
|
|
||||||
## 开源附加
|
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
||||||
|
|
||||||
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高IM易用性,实现类似Hook推送,此外,禁止任何项目未经仓库主作者授权二次分发或基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
|
## Warnings
|
||||||
|
|
||||||
|
[某框架抄袭部分分析](https://napneko.github.io/other/about-copy)
|
||||||
|
11
SECURITY.md
Normal file
11
SECURITY.md
Normal 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
|
BIN
external/LiteLoaderWrapper.zip
vendored
BIN
external/LiteLoaderWrapper.zip
vendored
Binary file not shown.
BIN
external/fonts/AaCute.ttf
vendored
Normal file
BIN
external/fonts/AaCute.ttf
vendored
Normal file
Binary file not shown.
BIN
external/fonts/JetBrainsMono.ttf
vendored
Normal file
BIN
external/fonts/JetBrainsMono.ttf
vendored
Normal file
Binary file not shown.
BIN
external/fonts/post.jpg
vendored
Normal file
BIN
external/fonts/post.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 695 KiB |
Binary file not shown.
Binary file not shown.
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
|
|||||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
if not exist "%QQpath%" (
|
if not exist "%QQpath%" (
|
||||||
echo provided QQ path is invalid: %QQpath%
|
echo provided QQ path is invalid
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
|
@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
|
|||||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
if not exist "%QQpath%" (
|
if not exist "%QQpath%" (
|
||||||
echo provided QQ path is invalid: %QQpath%
|
echo provided QQ path is invalid
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
|
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
|
|||||||
|
|
||||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
if not exist "%QQpath%" (
|
if not exist "%QQPath%" (
|
||||||
echo provided QQ path is invalid: %QQpath%
|
echo provided QQ path is invalid
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
|
@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
|
|||||||
|
|
||||||
SET QQPath=%pathWithoutUninstall%QQ.exe
|
SET QQPath=%pathWithoutUninstall%QQ.exe
|
||||||
|
|
||||||
if not exist "%QQpath%" (
|
if not exist "%QQPath%" (
|
||||||
echo provided QQ path is invalid: %QQpath%
|
echo provided QQ path is invalid
|
||||||
pause
|
pause
|
||||||
exit /b
|
exit /b
|
||||||
)
|
)
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "qq-chat",
|
"name": "qq-chat",
|
||||||
"version": "9.9.17-30899",
|
"version": "9.9.18-32869",
|
||||||
"verHash": "ececf273",
|
"verHash": "e735296c",
|
||||||
"linuxVersion": "3.2.15-30899",
|
"linuxVersion": "3.2.16-32869",
|
||||||
"linuxVerHash": "63c751e8",
|
"linuxVerHash": "4c192ba9",
|
||||||
"type": "module",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "QQ",
|
"description": "QQ",
|
||||||
"productName": "QQ",
|
"productName": "QQ",
|
||||||
@@ -17,8 +16,25 @@
|
|||||||
"bin": {
|
"bin": {
|
||||||
"qd": "externals/devtools/cli/index.js"
|
"qd": "externals/devtools/cli/index.js"
|
||||||
},
|
},
|
||||||
|
"appid": {
|
||||||
|
"win32": "537258389",
|
||||||
|
"darwin": "537258412",
|
||||||
|
"linux": "537258424"
|
||||||
|
},
|
||||||
"main": "./loadNapCat.js",
|
"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,
|
"isPureShell": true,
|
||||||
"isByteCodeShell": true,
|
"isByteCodeShell": true,
|
||||||
"platform": "win32",
|
"platform": "win32",
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.5.1",
|
"version": "4.7.45",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@heroui/accordion": "^2.2.8",
|
||||||
"@heroui/avatar": "2.2.7",
|
"@heroui/avatar": "2.2.7",
|
||||||
"@heroui/breadcrumbs": "2.2.7",
|
"@heroui/breadcrumbs": "2.2.7",
|
||||||
"@heroui/button": "2.2.10",
|
"@heroui/button": "2.2.10",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"@heroui/pagination": "^2.2.9",
|
"@heroui/pagination": "^2.2.9",
|
||||||
"@heroui/popover": "2.3.10",
|
"@heroui/popover": "2.3.10",
|
||||||
"@heroui/select": "2.4.10",
|
"@heroui/select": "2.4.10",
|
||||||
|
"@heroui/skeleton": "^2.2.6",
|
||||||
"@heroui/slider": "2.4.8",
|
"@heroui/slider": "2.4.8",
|
||||||
"@heroui/snippet": "2.2.11",
|
"@heroui/snippet": "2.2.11",
|
||||||
"@heroui/spinner": "2.2.7",
|
"@heroui/spinner": "2.2.7",
|
||||||
@@ -53,6 +55,7 @@
|
|||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
"framer-motion": "^12.0.6",
|
"framer-motion": "^12.0.6",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-dropzone": "^14.3.5",
|
"react-dropzone": "^14.3.5",
|
||||||
"react-error-boundary": "^5.0.0",
|
"react-error-boundary": "^5.0.0",
|
||||||
@@ -85,6 +89,7 @@
|
|||||||
"@eslint/js": "^9.19.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@react-types/shared": "^3.26.0",
|
"@react-types/shared": "^3.26.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/event-source-polyfill": "^1.0.5",
|
"@types/event-source-polyfill": "^1.0.5",
|
||||||
"@types/fabric": "^5.3.9",
|
"@types/fabric": "^5.3.9",
|
||||||
"@types/node": "^22.12.0",
|
"@types/node": "^22.12.0",
|
||||||
|
@@ -16,6 +16,16 @@ import store from '@/store'
|
|||||||
const WebLoginPage = lazy(() => import('@/pages/web_login'))
|
const WebLoginPage = lazy(() => import('@/pages/web_login'))
|
||||||
const IndexPage = lazy(() => import('@/pages/index'))
|
const IndexPage = lazy(() => import('@/pages/index'))
|
||||||
const QQLoginPage = lazy(() => import('@/pages/qq_login'))
|
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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -58,9 +68,21 @@ function AuthChecker({ children }: { children: React.ReactNode }) {
|
|||||||
function AppRoutes() {
|
function AppRoutes() {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<IndexPage />} path="/*" />
|
<Route path="/" element={<IndexPage />}>
|
||||||
<Route element={<QQLoginPage />} path="/qq_login" />
|
<Route index element={<DashboardIndexPage />} />
|
||||||
<Route element={<WebLoginPage />} path="/web_login" />
|
<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>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
36
napcat.webui/src/components/ColorPicker.tsx
Normal file
36
napcat.webui/src/components/ColorPicker.tsx
Normal 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
|
@@ -231,7 +231,7 @@ export default function AudioPlayer(props: AudioPlayerProps) {
|
|||||||
: 'top-3 -left-8 rounded-l-full bg-opacity-50 backdrop-blur-md'
|
: 'top-3 -left-8 rounded-l-full bg-opacity-50 backdrop-blur-md'
|
||||||
)}
|
)}
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onPress={() => setIsCollapsed(!isCollapsed)}
|
onPress={() => setIsCollapsed(!isCollapsed)}
|
||||||
>
|
>
|
||||||
|
@@ -33,7 +33,7 @@ const AddButton: React.FC<AddButtonProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
startContent={<IoAddCircleOutline className="text-2xl" />}
|
startContent={<IoAddCircleOutline className="text-2xl" />}
|
||||||
>
|
>
|
||||||
新建
|
新建
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from '@heroui/button'
|
import { Button } from '@heroui/button'
|
||||||
|
import clsx from 'clsx'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
|
||||||
@@ -7,15 +8,22 @@ export interface SaveButtonsProps {
|
|||||||
reset: () => void
|
reset: () => void
|
||||||
refresh?: () => void
|
refresh?: () => void
|
||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
reset,
|
reset,
|
||||||
isSubmitting,
|
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">
|
<div className="flex items-center justify-center gap-2 mt-5">
|
||||||
<Button
|
<Button
|
||||||
color="default"
|
color="default"
|
||||||
@@ -27,7 +35,7 @@ const SaveButtons: React.FC<SaveButtonsProps> = ({
|
|||||||
取消更改
|
取消更改
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
onPress={() => onSubmit()}
|
onPress={() => onSubmit()}
|
||||||
>
|
>
|
||||||
|
@@ -110,7 +110,7 @@ const AudioInsert = () => {
|
|||||||
<Tooltip content="发送音频">
|
<Tooltip content="发送音频">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<IoMic className="text-xl" />
|
<IoMic className="text-xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -120,7 +120,7 @@ const AudioInsert = () => {
|
|||||||
<Tooltip content="上传音频">
|
<Tooltip content="上传音频">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -137,7 +137,7 @@ const AudioInsert = () => {
|
|||||||
<PopoverTrigger tooltip="输入音频地址">
|
<PopoverTrigger tooltip="输入音频地址">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -154,7 +154,7 @@ const AudioInsert = () => {
|
|||||||
placeholder="请输入音频地址"
|
placeholder="请输入音频地址"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -177,7 +177,7 @@ const AudioInsert = () => {
|
|||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -190,7 +190,7 @@ const AudioInsert = () => {
|
|||||||
<PopoverContent className="flex-col gap-2 p-4">
|
<PopoverContent className="flex-col gap-2 p-4">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
color={isRecording ? 'danger' : 'danger'}
|
color={isRecording ? 'primary' : 'primary'}
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={isRecording ? stopRecording : startRecording}
|
onPress={isRecording ? stopRecording : startRecording}
|
||||||
>
|
>
|
||||||
@@ -198,7 +198,7 @@ const AudioInsert = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
{showPreview && audioPreview && (
|
{showPreview && audioPreview && (
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={handleShowPreview}
|
onPress={handleShowPreview}
|
||||||
>
|
>
|
||||||
@@ -212,7 +212,7 @@ const AudioInsert = () => {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'w-4 h-4 rounded-full',
|
'w-4 h-4 rounded-full',
|
||||||
isRecording
|
isRecording
|
||||||
? 'animate-pulse bg-danger-400'
|
? 'animate-pulse bg-primary-400'
|
||||||
: 'bg-success-400'
|
: 'bg-success-400'
|
||||||
)}
|
)}
|
||||||
></span>
|
></span>
|
||||||
|
@@ -10,7 +10,7 @@ const DiceInsert = () => {
|
|||||||
return (
|
return (
|
||||||
<Tooltip content="发送骰子">
|
<Tooltip content="发送骰子">
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -55,7 +55,7 @@ const EmojiPicker = ({ onInsertEmoji, onOpenChange }: EmojiPickerProps) => {
|
|||||||
<Tooltip content="插入表情">
|
<Tooltip content="插入表情">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<MdEmojiEmotions className="text-xl" />
|
<MdEmojiEmotions className="text-xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -65,7 +65,7 @@ const EmojiPicker = ({ onInsertEmoji, onOpenChange }: EmojiPickerProps) => {
|
|||||||
{visibleEmojis.map((emoji) => (
|
{visibleEmojis.map((emoji) => (
|
||||||
<Button
|
<Button
|
||||||
key={emoji.id}
|
key={emoji.id}
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -35,7 +35,7 @@ const FileInsert = () => {
|
|||||||
<Tooltip content="发送文件">
|
<Tooltip content="发送文件">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<FaFolder className="text-lg" />
|
<FaFolder className="text-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -45,7 +45,7 @@ const FileInsert = () => {
|
|||||||
<Tooltip content="上传文件">
|
<Tooltip content="上传文件">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -62,7 +62,7 @@ const FileInsert = () => {
|
|||||||
<PopoverTrigger tooltip="输入文件地址">
|
<PopoverTrigger tooltip="输入文件地址">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -79,7 +79,7 @@ const FileInsert = () => {
|
|||||||
placeholder="请输入文件地址"
|
placeholder="请输入文件地址"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -23,7 +23,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
|
|||||||
<Tooltip content="插入图片">
|
<Tooltip content="插入图片">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<MdImage className="text-xl" />
|
<MdImage className="text-xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -33,7 +33,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
|
|||||||
<Tooltip content="上传图片">
|
<Tooltip content="上传图片">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -50,7 +50,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
|
|||||||
<PopoverTrigger tooltip="输入图片地址">
|
<PopoverTrigger tooltip="输入图片地址">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -67,7 +67,7 @@ const ImageInsert = ({ insertImage, onOpenChange }: ImageInsertProps) => {
|
|||||||
placeholder="请输入图片地址"
|
placeholder="请输入图片地址"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -80,7 +80,7 @@ const MusicInsert = () => {
|
|||||||
<Tooltip content="发送音乐">
|
<Tooltip content="发送音乐">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<IoMusicalNotes className="text-xl" />
|
<IoMusicalNotes className="text-xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -132,7 +132,7 @@ const MusicInsert = () => {
|
|||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
size="lg"
|
size="lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -236,7 +236,7 @@ const MusicInsert = () => {
|
|||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
size="lg"
|
size="lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
@@ -19,7 +19,7 @@ const ReplyInsert = ({ insertReply }: ReplyInsertProps) => {
|
|||||||
<Tooltip content="回复消息">
|
<Tooltip content="回复消息">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<BsChatQuoteFill className="text-lg" />
|
<BsChatQuoteFill className="text-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -38,7 +38,7 @@ const ReplyInsert = ({ insertReply }: ReplyInsertProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
|
@@ -10,7 +10,7 @@ const RPSInsert = () => {
|
|||||||
return (
|
return (
|
||||||
<Tooltip content="发送猜拳">
|
<Tooltip content="发送猜拳">
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -35,7 +35,7 @@ const VideoInsert = () => {
|
|||||||
<Tooltip content="发送视频">
|
<Tooltip content="发送视频">
|
||||||
<div className="max-w-fit">
|
<div className="max-w-fit">
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button color="danger" variant="flat" isIconOnly radius="full">
|
<Button color="primary" variant="flat" isIconOnly radius="full">
|
||||||
<IoVideocam className="text-xl" />
|
<IoVideocam className="text-xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -45,7 +45,7 @@ const VideoInsert = () => {
|
|||||||
<Tooltip content="上传视频">
|
<Tooltip content="上传视频">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -62,7 +62,7 @@ const VideoInsert = () => {
|
|||||||
<PopoverTrigger tooltip="输入视频地址">
|
<PopoverTrigger tooltip="输入视频地址">
|
||||||
<Button
|
<Button
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
color="danger"
|
color="primary"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
@@ -79,7 +79,7 @@ const VideoInsert = () => {
|
|||||||
placeholder="请输入视频地址"
|
placeholder="请输入视频地址"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -190,7 +190,7 @@ const ChatInput = () => {
|
|||||||
<DiceInsert />
|
<DiceInsert />
|
||||||
<RPSInsert />
|
<RPSInsert />
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const messages = getChatMessage()
|
const messages = getChatMessage()
|
||||||
showStructuredMessage(messages)
|
showStructuredMessage(messages)
|
||||||
|
@@ -15,7 +15,7 @@ export default function ChatInputModal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onPress={onOpen} color="danger" radius="full" variant="flat">
|
<Button onPress={onOpen} color="primary" radius="full" variant="flat">
|
||||||
构造聊天消息
|
构造聊天消息
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -36,7 +36,7 @@ export default function ChatInputModal() {
|
|||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" onPress={onClose} variant="flat">
|
<Button color="primary" onPress={onClose} variant="flat">
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -78,7 +78,7 @@ const NetworkDisplayCard = <T extends keyof NetworkType>({
|
|||||||
{debug ? '关闭调试' : '开启调试'}
|
{debug ? '关闭调试' : '开启调试'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
startContent={<MdDeleteForever />}
|
startContent={<MdDeleteForever />}
|
||||||
onPress={handleDelete}
|
onPress={handleDelete}
|
||||||
>
|
>
|
||||||
|
@@ -19,7 +19,7 @@ const NetworkItemDisplay: React.FC<NetworkItemDisplayProps> = ({
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'bg-opacity-60 shadow-sm md:rounded-3xl',
|
'bg-opacity-60 shadow-sm md:rounded-3xl',
|
||||||
size === 'md'
|
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'
|
: 'col-span-2 md:col-span-1 bg-warning-100 shadow-warning-200'
|
||||||
)}
|
)}
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
|
@@ -33,7 +33,7 @@ export default function CreateFileModal({
|
|||||||
<ModalHeader>新建</ModalHeader>
|
<ModalHeader>新建</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<ButtonGroup color="danger">
|
<ButtonGroup color="primary">
|
||||||
<Button
|
<Button
|
||||||
variant={fileType === 'file' ? 'solid' : 'flat'}
|
variant={fileType === 'file' ? 'solid' : 'flat'}
|
||||||
onPress={() => onTypeChange('file')}
|
onPress={() => onTypeChange('file')}
|
||||||
@@ -51,10 +51,10 @@ export default function CreateFileModal({
|
|||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="danger" onPress={onCreate}>
|
<Button color="primary" onPress={onCreate}>
|
||||||
创建
|
创建
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -81,10 +81,10 @@ export default function FileEditModal({
|
|||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="danger" onPress={onSave}>
|
<Button color="primary" onPress={onSave}>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
import { Spinner } from '@heroui/spinner'
|
import { Spinner } from '@heroui/spinner'
|
||||||
import { useRequest } from 'ahooks'
|
import { useRequest } from 'ahooks'
|
||||||
import path from 'path-browserify'
|
import path from 'path-browserify'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import FileManager from '@/controllers/file_manager'
|
import FileManager from '@/controllers/file_manager'
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ export default function FilePreviewModal({
|
|||||||
async () => FileManager.downloadToURL(filePath),
|
async () => FileManager.downloadToURL(filePath),
|
||||||
{
|
{
|
||||||
refreshDeps: [filePath],
|
refreshDeps: [filePath],
|
||||||
|
manual: true,
|
||||||
refreshDepsAction: () => {
|
refreshDepsAction: () => {
|
||||||
const ext = path.extname(filePath).toLowerCase()
|
const ext = path.extname(filePath).toLowerCase()
|
||||||
if (!filePath || !supportedPreviewExts.includes(ext)) {
|
if (!filePath || !supportedPreviewExts.includes(ext)) {
|
||||||
@@ -43,6 +45,12 @@ export default function FilePreviewModal({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (filePath) {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
}, [filePath])
|
||||||
|
|
||||||
let contentElement = null
|
let contentElement = null
|
||||||
if (!supportedPreviewExts.includes(ext)) {
|
if (!supportedPreviewExts.includes(ext)) {
|
||||||
contentElement = <div>暂不支持预览此文件类型</div>
|
contentElement = <div>暂不支持预览此文件类型</div>
|
||||||
@@ -74,7 +82,7 @@ export default function FilePreviewModal({
|
|||||||
{contentElement}
|
{contentElement}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -82,7 +82,7 @@ export default function FileTable({
|
|||||||
setPreviewImages([])
|
setPreviewImages([])
|
||||||
setPreviewIndex(0)
|
setPreviewIndex(0)
|
||||||
setShowImage(false)
|
setShowImage(false)
|
||||||
}, [files])
|
}, [currentPath])
|
||||||
|
|
||||||
const onPreviewImage = (name: string, images: PreviewImage[]) => {
|
const onPreviewImage = (name: string, images: PreviewImage[]) => {
|
||||||
const index = images.findIndex((image) => image.key === name)
|
const index = images.findIndex((image) => image.key === name)
|
||||||
@@ -116,7 +116,7 @@ export default function FileTable({
|
|||||||
isCompact
|
isCompact
|
||||||
showControls
|
showControls
|
||||||
showShadow
|
showShadow
|
||||||
color="danger"
|
color="primary"
|
||||||
page={page}
|
page={page}
|
||||||
total={pages}
|
total={pages}
|
||||||
onChange={(page) => setPage(page)}
|
onChange={(page) => setPage(page)}
|
||||||
@@ -195,7 +195,7 @@ export default function FileTable({
|
|||||||
<ButtonGroup size="sm">
|
<ButtonGroup size="sm">
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => onRenameRequest(file.name)}
|
onPress={() => onRenameRequest(file.name)}
|
||||||
>
|
>
|
||||||
@@ -203,7 +203,7 @@ export default function FileTable({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => onMoveRequest(file.name)}
|
onPress={() => onMoveRequest(file.name)}
|
||||||
>
|
>
|
||||||
@@ -211,7 +211,7 @@ export default function FileTable({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => onCopyPath(file.name)}
|
onPress={() => onCopyPath(file.name)}
|
||||||
>
|
>
|
||||||
@@ -219,7 +219,7 @@ export default function FileTable({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => onDownload(filePath)}
|
onPress={() => onDownload(filePath)}
|
||||||
>
|
>
|
||||||
@@ -227,7 +227,7 @@ export default function FileTable({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => onDelete(filePath)}
|
onPress={() => onDelete(filePath)}
|
||||||
>
|
>
|
||||||
|
@@ -33,6 +33,7 @@ export default function ImageNameButton({
|
|||||||
async () => FileManager.downloadToURL(filePath),
|
async () => FileManager.downloadToURL(filePath),
|
||||||
{
|
{
|
||||||
refreshDeps: [filePath],
|
refreshDeps: [filePath],
|
||||||
|
manual: true,
|
||||||
refreshDepsAction: () => {
|
refreshDepsAction: () => {
|
||||||
const ext = path.extname(filePath).toLowerCase()
|
const ext = path.extname(filePath).toLowerCase()
|
||||||
if (!filePath || !imageExts.includes(ext)) {
|
if (!filePath || !imageExts.includes(ext)) {
|
||||||
@@ -52,6 +53,12 @@ export default function ImageNameButton({
|
|||||||
}
|
}
|
||||||
}, [data, name, onAddPreview])
|
}, [data, name, onAddPreview])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (filePath) {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -63,7 +70,15 @@ export default function ImageNameButton({
|
|||||||
) : loading || !data ? (
|
) : loading || !data ? (
|
||||||
<Spinner size="sm" />
|
<Spinner size="sm" />
|
||||||
) : (
|
) : (
|
||||||
<Image src={data} alt={name} className="w-8 h-8" radius="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"
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@@ -86,13 +86,13 @@ function DirectoryTree({
|
|||||||
onPress={handleClick}
|
onPress={handleClick}
|
||||||
className="py-1 px-2 text-left justify-start min-w-0 min-h-0 h-auto text-sm rounded-md"
|
className="py-1 px-2 text-left justify-start min-w-0 min-h-0 h-auto text-sm rounded-md"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
startContent={
|
startContent={
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'rounded-md',
|
'rounded-md',
|
||||||
isSeleted ? 'bg-danger-600' : 'bg-danger-50'
|
isSeleted ? 'bg-primary-600' : 'bg-primary-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{expanded ? <IoRemove /> : <IoAdd />}
|
{expanded ? <IoRemove /> : <IoAdd />}
|
||||||
@@ -105,7 +105,7 @@ function DirectoryTree({
|
|||||||
<div>
|
<div>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex py-1 px-8">
|
<div className="flex py-1 px-8">
|
||||||
<Spinner size="sm" color="danger" />
|
<Spinner size="sm" color="primary" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
dirs.map((dirName) => {
|
dirs.map((dirName) => {
|
||||||
@@ -155,10 +155,10 @@ export default function MoveModal({
|
|||||||
<p className="text-sm text-default-500">移动项:{selectionInfo}</p>
|
<p className="text-sm text-default-500">移动项:{selectionInfo}</p>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="danger" onPress={onMove}>
|
<Button color="primary" onPress={onMove}>
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -31,10 +31,10 @@ export default function RenameModal({
|
|||||||
<Input label="新名称" value={newFileName} onChange={onNameChange} />
|
<Input label="新名称" value={newFileName} onChange={onNameChange} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="danger" onPress={onRename}>
|
<Button color="primary" onPress={onRename}>
|
||||||
确定
|
确定
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
@@ -33,7 +33,7 @@ export default function Hitokoto() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
{loading && <PageLoading />}
|
{loading && <PageLoading />}
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="text-danger-400">一言加载失败:{error.message}</div>
|
<div className="text-primary-400">一言加载失败:{error.message}</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div>{data?.hitokoto}</div>
|
<div>{data?.hitokoto}</div>
|
||||||
@@ -52,7 +52,7 @@ export default function Hitokoto() {
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
>
|
>
|
||||||
<IoRefresh />
|
<IoRefresh />
|
||||||
|
@@ -34,7 +34,7 @@ export default function HoverTiltedCard({
|
|||||||
rotateAmplitude = 14,
|
rotateAmplitude = 14,
|
||||||
showTooltip = false,
|
showTooltip = false,
|
||||||
overlayContent = (
|
overlayContent = (
|
||||||
<div className="text-center mt-6 px-4 py-0.5 shadow-lg rounded-full bg-danger-600 text-default-100 bg-opacity-80">
|
<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
|
NapCat
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
69
napcat.webui/src/components/input/file_input.tsx
Normal file
69
napcat.webui/src/components/input/file_input.tsx
Normal 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
|
@@ -43,7 +43,7 @@ const ImageInput: React.FC<ImageInputProps> = ({ onChange, value, label }) => {
|
|||||||
onChange('')
|
onChange('')
|
||||||
if (inputRef.current) inputRef.current.value = ''
|
if (inputRef.current) inputRef.current.value = ''
|
||||||
}}
|
}}
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
|
@@ -16,13 +16,13 @@ const logLevelColor: {
|
|||||||
| 'secondary'
|
| 'secondary'
|
||||||
| 'success'
|
| 'success'
|
||||||
| 'warning'
|
| 'warning'
|
||||||
| 'danger'
|
| 'primary'
|
||||||
} = {
|
} = {
|
||||||
[LogLevel.DEBUG]: 'default',
|
[LogLevel.DEBUG]: 'default',
|
||||||
[LogLevel.INFO]: 'primary',
|
[LogLevel.INFO]: 'primary',
|
||||||
[LogLevel.WARN]: 'warning',
|
[LogLevel.WARN]: 'warning',
|
||||||
[LogLevel.ERROR]: 'danger',
|
[LogLevel.ERROR]: 'primary',
|
||||||
[LogLevel.FATAL]: 'danger'
|
[LogLevel.FATAL]: 'primary'
|
||||||
}
|
}
|
||||||
const LogLevelSelect = (props: LogLevelSelectProps) => {
|
const LogLevelSelect = (props: LogLevelSelectProps) => {
|
||||||
const { selectedKeys, onSelectionChange } = props
|
const { selectedKeys, onSelectionChange } = props
|
||||||
|
@@ -65,7 +65,7 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
{showCancel && (
|
{showCancel && (
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onCancel?.()
|
onCancel?.()
|
||||||
@@ -76,7 +76,7 @@ const Modal: React.FC<ModalProps> = React.memo((props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onConfirm?.()
|
onConfirm?.()
|
||||||
nativeClose()
|
nativeClose()
|
||||||
|
@@ -28,7 +28,7 @@ import type {
|
|||||||
|
|
||||||
function displayData(data: number, loading: boolean, error?: Error) {
|
function displayData(data: number, loading: boolean, error?: Error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
return <MdError className="text-danger-400" />
|
return <MdError className="text-primary-400" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -175,7 +175,7 @@ export default function NapCatRepoInfo() {
|
|||||||
className="group h-auto py-3"
|
className="group h-auto py-3"
|
||||||
endContent={
|
endContent={
|
||||||
releaseError ? (
|
releaseError ? (
|
||||||
<MdError className="text-danger-400" />
|
<MdError className="text-primary-400" />
|
||||||
) : releaseLoading ? (
|
) : releaseLoading ? (
|
||||||
<Spinner size="sm" />
|
<Spinner size="sm" />
|
||||||
) : (
|
) : (
|
||||||
@@ -229,7 +229,7 @@ export default function NapCatRepoInfo() {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
startContent={
|
startContent={
|
||||||
<IconWrapper className="bg-danger/10 text-danger dark:text-danger-500">
|
<IconWrapper className="bg-primary/10 text-primary dark:text-primary-500">
|
||||||
<BookIcon />
|
<BookIcon />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
}
|
}
|
||||||
|
@@ -150,7 +150,7 @@ const GenericForm = <T extends keyof NetworkConfigType>({
|
|||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
isDisabled={formState.isSubmitting}
|
isDisabled={formState.isSubmitting}
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={onClose}
|
onPress={onClose}
|
||||||
|
@@ -20,7 +20,7 @@ const WebsocketServerForm: React.FC<WebsocketServerFormProps> = ({
|
|||||||
enable: false,
|
enable: false,
|
||||||
name: '',
|
name: '',
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 3000,
|
port: 3001,
|
||||||
reportSelfMessage: false,
|
reportSelfMessage: false,
|
||||||
enableForcePushEvent: true,
|
enableForcePushEvent: true,
|
||||||
messagePostFormat: 'array',
|
messagePostFormat: 'array',
|
||||||
|
@@ -91,7 +91,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="p-4 pt-14 rounded-lg shadow-md">
|
<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 />
|
<PiCatDuotone />
|
||||||
{data.description}
|
{data.description}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -125,7 +125,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onPress={sendRequest}
|
onPress={sendRequest}
|
||||||
color="danger"
|
color="primary"
|
||||||
size="lg"
|
size="lg"
|
||||||
radius="full"
|
radius="full"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
@@ -136,7 +136,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<Card
|
<Card
|
||||||
shadow="sm"
|
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-bold text-lg gap-1 pb-0">
|
<CardHeader className="font-bold text-lg gap-1 pb-0">
|
||||||
<span className="mr-2">请求体</span>
|
<span className="mr-2">请求体</span>
|
||||||
|
@@ -27,7 +27,7 @@ const SchemaType = ({
|
|||||||
name = '固定值'
|
name = '固定值'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let chipColor: 'primary' | 'success' | 'danger' | 'warning' | 'secondary' =
|
let chipColor: 'primary' | 'success' | 'primary' | 'warning' | 'secondary' =
|
||||||
'primary'
|
'primary'
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'enum':
|
case 'enum':
|
||||||
@@ -37,7 +37,7 @@ const SchemaType = ({
|
|||||||
chipColor = 'secondary'
|
chipColor = 'secondary'
|
||||||
break
|
break
|
||||||
case 'array':
|
case 'array':
|
||||||
chipColor = 'danger'
|
chipColor = 'primary'
|
||||||
break
|
break
|
||||||
case 'object':
|
case 'object':
|
||||||
chipColor = 'success'
|
chipColor = 'success'
|
||||||
|
@@ -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">
|
<div className="w-64 h-full overflow-y-auto px-2 pt-2 pb-10 md:pb-0">
|
||||||
<Input
|
<Input
|
||||||
className="sticky top-0 z-10 text-danger-600"
|
className="sticky top-0 z-10 text-primary-600"
|
||||||
classNames={{
|
classNames={{
|
||||||
inputWrapper:
|
inputWrapper:
|
||||||
'bg-opacity-30 bg-danger-50 backdrop-blur-sm border border-danger-300 mb-2',
|
'bg-opacity-30 bg-primary-50 backdrop-blur-sm border border-primary-300 mb-2',
|
||||||
input: 'bg-transparent !text-danger-400 !placeholder-danger-400'
|
input: 'bg-transparent !text-primary-400 !placeholder-primary-400'
|
||||||
}}
|
}}
|
||||||
radius="full"
|
radius="full"
|
||||||
placeholder="搜索 API"
|
placeholder="搜索 API"
|
||||||
@@ -51,7 +51,7 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
|
|||||||
key={apiName}
|
key={apiName}
|
||||||
shadow="none"
|
shadow="none"
|
||||||
className={clsx(
|
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: !(
|
hidden: !(
|
||||||
apiName.includes(searchValue) ||
|
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
|
apiName === selectedApi
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
@@ -69,8 +69,8 @@ const OneBotApiNavList: React.FC<OneBotApiNavListProps> = (props) => {
|
|||||||
<CardBody>
|
<CardBody>
|
||||||
<h2 className="font-bold">{api.description}</h2>
|
<h2 className="font-bold">{api.description}</h2>
|
||||||
<div
|
<div
|
||||||
className={clsx('text-sm text-danger-200', {
|
className={clsx('text-sm text-primary-200', {
|
||||||
'!text-danger-400': apiName === selectedApi
|
'!text-primary-400': apiName === selectedApi
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{apiName}
|
{apiName}
|
||||||
|
@@ -109,7 +109,7 @@ const OneBotItemRender = ({ data, index, style }: OneBotItemRenderProps) => {
|
|||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
|
@@ -30,7 +30,7 @@ const OneBotDisplayResponse: React.FC<OneBotDisplayResponseProps> = ({
|
|||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
radius="full"
|
radius="full"
|
||||||
className="text-medium"
|
className="text-medium"
|
||||||
|
@@ -43,7 +43,7 @@ const OneBotSendModal: React.FC<OneBotSendModalProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onPress={onOpen} color="danger" radius="full" variant="flat">
|
<Button onPress={onOpen} color="primary" radius="full" variant="flat">
|
||||||
构造请求
|
构造请求
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -75,11 +75,11 @@ const OneBotSendModal: React.FC<OneBotSendModalProps> = (props) => {
|
|||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<ChatInputModal />
|
<ChatInputModal />
|
||||||
|
|
||||||
<Button color="danger" variant="flat" onPress={onClose}>
|
<Button color="primary" variant="flat" onPress={onClose}>
|
||||||
取消
|
取消
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
onPress={() => handleSendMessage(onClose)}
|
onPress={() => handleSendMessage(onClose)}
|
||||||
>
|
>
|
||||||
发送
|
发送
|
||||||
|
@@ -10,7 +10,7 @@ function StatusTag({
|
|||||||
color
|
color
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
color: 'success' | 'danger' | 'warning'
|
color: 'success' | 'primary' | 'warning'
|
||||||
}) {
|
}) {
|
||||||
const textClassName = `text-${color} text-sm`
|
const textClassName = `text-${color} text-sm`
|
||||||
const bgClassName = `bg-${color}`
|
const bgClassName = `bg-${color}`
|
||||||
@@ -27,7 +27,7 @@ export default function WSStatus({ state }: WSStatusProps) {
|
|||||||
return <StatusTag title="已连接" color="success" />
|
return <StatusTag title="已连接" color="success" />
|
||||||
}
|
}
|
||||||
if (state === ReadyState.CLOSED) {
|
if (state === ReadyState.CLOSED) {
|
||||||
return <StatusTag title="已关闭" color="danger" />
|
return <StatusTag title="已关闭" color="primary" />
|
||||||
}
|
}
|
||||||
if (state === ReadyState.CONNECTING) {
|
if (state === ReadyState.CONNECTING) {
|
||||||
return <StatusTag title="连接中" color="warning" />
|
return <StatusTag title="连接中" color="warning" />
|
||||||
|
@@ -16,7 +16,7 @@ export interface QQInfoCardProps {
|
|||||||
const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
|
const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<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"
|
shadow="none"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
>
|
>
|
||||||
@@ -30,7 +30,7 @@ const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
) : (
|
) : (
|
||||||
<CardBody className="flex-row items-center gap-2 overflow-hidden relative">
|
<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 />
|
<BsTencentQq />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-shrink-0 z-10">
|
<div className="relative flex-shrink-0 z-10">
|
||||||
@@ -43,14 +43,14 @@ const QQInfoCard: React.FC<QQInfoCardProps> = ({ data, error, loading }) => {
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
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'
|
data?.online ? 'bg-green-500' : 'bg-gray-500'
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-col justify-center">
|
<div className="flex-col justify-center">
|
||||||
<div className="text-lg truncate">{data?.nick}</div>
|
<div className="text-lg truncate">{data?.nick}</div>
|
||||||
<div className="text-danger-500 text-sm">{data?.uin}</div>
|
<div className="text-primary-500 text-sm">{data?.uin}</div>
|
||||||
</div>
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
)}
|
)}
|
||||||
|
@@ -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">
|
<div className="bg-white p-2 rounded-md w-fit mx-auto relative overflow-hidden">
|
||||||
{!qrcode && (
|
{!qrcode && (
|
||||||
<div className="absolute left-2 top-2 right-2 bottom-2 bg-white bg-opacity-50 backdrop-blur flex items-center justify-center">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<QRCodeSVG size={180} value={qrcode} />
|
<QRCodeSVG size={180} value={qrcode} />
|
||||||
|
265
napcat.webui/src/components/rotating_text.tsx
Normal file
265
napcat.webui/src/components/rotating_text.tsx
Normal 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
|
@@ -63,7 +63,7 @@ const SideBar: React.FC<SideBarProps> = (props) => {
|
|||||||
<div className="mt-auto mb-10 md:mb-0">
|
<div className="mt-auto mb-10 md:mb-0">
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
color="danger"
|
color="primary"
|
||||||
radius="full"
|
radius="full"
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={toggleTheme}
|
onPress={toggleTheme}
|
||||||
@@ -75,7 +75,7 @@ const SideBar: React.FC<SideBarProps> = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full mb-2"
|
className="w-full mb-2"
|
||||||
color="danger"
|
color="primary"
|
||||||
radius="full"
|
radius="full"
|
||||||
variant="light"
|
variant="light"
|
||||||
onPress={onRevokeAuth}
|
onPress={onRevokeAuth}
|
||||||
|
@@ -55,15 +55,16 @@ const renderItems = (items: MenuItem[], children = false) => {
|
|||||||
isActive && 'bg-opacity-60',
|
isActive && 'bg-opacity-60',
|
||||||
b64img && 'backdrop-blur-md text-white'
|
b64img && 'backdrop-blur-md text-white'
|
||||||
)}
|
)}
|
||||||
color="danger"
|
color="primary"
|
||||||
endContent={
|
endContent={
|
||||||
canOpen ? (
|
canOpen ? (
|
||||||
// div实现箭头V效果
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'ml-auto relative w-3 h-3 transition-transform',
|
'ml-auto relative w-3 h-3 transition-transform',
|
||||||
open && 'transform rotate-180',
|
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:rounded-full',
|
||||||
'before:content-[""]',
|
'before:content-[""]',
|
||||||
'before:block',
|
'before:block',
|
||||||
@@ -95,8 +96,8 @@ const renderItems = (items: MenuItem[], children = false) => {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
|
'w-3 h-1.5 rounded-full ml-auto shadow-lg',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-danger-500 animate-spinner-ease-spin'
|
? 'bg-primary-500 animate-spinner-ease-spin'
|
||||||
: 'bg-red-300 dark:bg-white'
|
: 'bg-primary-200 dark:bg-white'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -4,6 +4,8 @@ import { Chip } from '@heroui/chip'
|
|||||||
import { Spinner } from '@heroui/spinner'
|
import { Spinner } from '@heroui/spinner'
|
||||||
import { Tooltip } from '@heroui/tooltip'
|
import { Tooltip } from '@heroui/tooltip'
|
||||||
import { useRequest } from 'ahooks'
|
import { useRequest } from 'ahooks'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { BsStars } from 'react-icons/bs'
|
||||||
import { FaCircleInfo, FaInfo, FaQq } from 'react-icons/fa6'
|
import { FaCircleInfo, FaInfo, FaQq } from 'react-icons/fa6'
|
||||||
import { IoLogoChrome, IoLogoOctocat } from 'react-icons/io'
|
import { IoLogoChrome, IoLogoOctocat } from 'react-icons/io'
|
||||||
import { RiMacFill } from 'react-icons/ri'
|
import { RiMacFill } from 'react-icons/ri'
|
||||||
@@ -16,7 +18,6 @@ import { compareVersion } from '@/utils/version'
|
|||||||
import WebUIManager from '@/controllers/webui_manager'
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
import { GithubRelease } from '@/types/github'
|
import { GithubRelease } from '@/types/github'
|
||||||
|
|
||||||
import packageJson from '../../package.json'
|
|
||||||
import TailwindMarkdown from './tailwind_markdown'
|
import TailwindMarkdown from './tailwind_markdown'
|
||||||
|
|
||||||
export interface SystemInfoItemProps {
|
export interface SystemInfoItemProps {
|
||||||
@@ -33,10 +34,10 @@ const SystemInfoItem: React.FC<SystemInfoItemProps> = ({
|
|||||||
endContent
|
endContent
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
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}
|
{icon}
|
||||||
<div className="w-24">{title}</div>
|
<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 className="ml-auto">{endContent}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -61,7 +62,7 @@ const NewVersionTip = (props: NewVersionTipProps) => {
|
|||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="shadow"
|
variant="shadow"
|
||||||
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
|
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
|
||||||
onPress={() => {
|
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 (
|
return (
|
||||||
<Tooltip content="有新版本可用">
|
<Tooltip content="有新版本可用">
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
radius="full"
|
radius="full"
|
||||||
color="danger"
|
color="primary"
|
||||||
variant="shadow"
|
variant="shadow"
|
||||||
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
|
className="!w-5 !h-5 !min-w-0 text-small shadow-md"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -121,6 +158,13 @@ const NewVersionTip = (props: NewVersionTipProps) => {
|
|||||||
<span>最新版本</span>
|
<span>最新版本</span>
|
||||||
<Chip color="primary">{latestVersion}</Chip>
|
<Chip color="primary">{latestVersion}</Chip>
|
||||||
</div>
|
</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">
|
<div className="text-sm space-y-2 !mt-4">
|
||||||
{middleVersions.map((versionInfo) => (
|
{middleVersions.map((versionInfo) => (
|
||||||
<div
|
<div
|
||||||
@@ -190,19 +234,14 @@ const SystemInfo: React.FC<SystemInfoProps> = (props) => {
|
|||||||
error: qqVersionError
|
error: qqVersionError
|
||||||
} = useRequest(WebUIManager.getQQVersion)
|
} = useRequest(WebUIManager.getQQVersion)
|
||||||
return (
|
return (
|
||||||
<Card className="bg-opacity-60 shadow-sm shadow-danger-50 dark:shadow-danger-100 overflow-visible flex-1">
|
<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-danger-500 font-extrabold">
|
<CardHeader className="pb-0 items-center gap-1 text-primary-500 font-extrabold">
|
||||||
<FaCircleInfo className="text-lg" />
|
<FaCircleInfo className="text-lg" />
|
||||||
<span>系统信息</span>
|
<span>系统信息</span>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody className="flex-1">
|
<CardBody className="flex-1">
|
||||||
<div className="flex flex-col justify-between h-full">
|
<div className="flex flex-col justify-between h-full">
|
||||||
<NapCatVersion />
|
<NapCatVersion />
|
||||||
<SystemInfoItem
|
|
||||||
title="WebUI 版本"
|
|
||||||
icon={<IoLogoChrome className="text-xl" />}
|
|
||||||
value={packageJson.version}
|
|
||||||
/>
|
|
||||||
<SystemInfoItem
|
<SystemInfoItem
|
||||||
title="QQ 版本"
|
title="QQ 版本"
|
||||||
icon={<FaQq className="text-lg" />}
|
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
|
<SystemInfoItem
|
||||||
title="系统版本"
|
title="系统版本"
|
||||||
icon={<RiMacFill className="text-xl" />}
|
icon={<RiMacFill className="text-xl" />}
|
||||||
|
@@ -24,7 +24,7 @@ const SystemStatusItem: React.FC<SystemStatusItemProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
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'
|
size === 'lg' ? 'col-span-2' : 'col-span-1 flex justify-between'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -55,7 +55,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<div className="absolute h-full right-0 top-0">
|
||||||
<Image
|
<Image
|
||||||
src={bkg}
|
src={bkg}
|
||||||
@@ -69,7 +69,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
|
|||||||
</div>
|
</div>
|
||||||
<CardBody className="overflow-visible md:flex-row gap-4 items-center justify-stretch z-10">
|
<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">
|
<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" />
|
<GiCpu className="text-xl" />
|
||||||
<span>CPU</span>
|
<span>CPU</span>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -88,7 +88,7 @@ const SystemStatusDisplay: React.FC<SystemStatusDisplayProps> = ({ data }) => {
|
|||||||
unit="%"
|
unit="%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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" />
|
<BiSolidMemoryCard className="text-xl" />
|
||||||
<span>内存</span>
|
<span>内存</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
@@ -62,7 +62,7 @@ export const Tab = forwardRef<HTMLDivElement, TabProps>(
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
'px-2 py-1 flex items-center gap-1 text-sm font-medium border-b-2 transition-colors',
|
'px-2 py-1 flex items-center gap-1 text-sm font-medium border-b-2 transition-colors',
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-danger text-danger'
|
? 'border-primary text-primary'
|
||||||
: 'border-transparent hover:border-default',
|
: 'border-transparent hover:border-default',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
@@ -99,7 +99,7 @@ const XTerm = forwardRef<XTermRef, XTermProps>((props, ref) => {
|
|||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
terminalRef.current.options.theme = {
|
terminalRef.current.options.theme = {
|
||||||
background: '#00000000',
|
background: '#00000000',
|
||||||
black: '#000000',
|
black: '#ffffff',
|
||||||
red: '#cd3131',
|
red: '#cd3131',
|
||||||
green: '#0dbc79',
|
green: '#0dbc79',
|
||||||
yellow: '#e5e510',
|
yellow: '#e5e510',
|
||||||
|
6
napcat.webui/src/const/themes.ts
Normal file
6
napcat.webui/src/const/themes.ts
Normal 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
|
256
napcat.webui/src/const/themes/heroui.ts
Normal file
256
napcat.webui/src/const/themes/heroui.ts
Normal 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
|
256
napcat.webui/src/const/themes/nc_pink.ts
Normal file
256
napcat.webui/src/const/themes/nc_pink.ts
Normal 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
|
@@ -35,6 +35,7 @@ const AudioProvider: React.FC<MusicProviderProps> = ({ children }) => {
|
|||||||
const [musicId, setMusicId] = useState<number>(0)
|
const [musicId, setMusicId] = useState<number>(0)
|
||||||
const [playMode, setPlayMode] = useState<PlayMode>(PlayMode.Loop)
|
const [playMode, setPlayMode] = useState<PlayMode>(PlayMode.Loop)
|
||||||
const music = musicList.find((music) => music.id === musicId)
|
const music = musicList.find((music) => music.id === musicId)
|
||||||
|
const [token] = useLocalStorage(key.token, '')
|
||||||
const onNext = () => {
|
const onNext = () => {
|
||||||
const nextID = getNextMusic(musicList, musicId, playMode)
|
const nextID = getNextMusic(musicList, musicId, playMode)
|
||||||
setMusicId(nextID)
|
setMusicId(nextID)
|
||||||
@@ -60,8 +61,8 @@ const AudioProvider: React.FC<MusicProviderProps> = ({ children }) => {
|
|||||||
setMusicId(res[0].id)
|
setMusicId(res[0].id)
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchMusicList(listId)
|
if (listId && token) fetchMusicList(listId)
|
||||||
}, [listId])
|
}, [listId, token])
|
||||||
return (
|
return (
|
||||||
<AudioContext.Provider
|
<AudioContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@@ -196,4 +196,26 @@ export default class FileManager {
|
|||||||
)
|
)
|
||||||
return data.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -73,4 +73,17 @@ export default class QQManager {
|
|||||||
)
|
)
|
||||||
return data.data.data
|
return data.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getQuickLoginQQ() {
|
||||||
|
const { data } = await serverRequest.post<ServerResponse<string>>(
|
||||||
|
'/QQLogin/GetQuickLoginQQ'
|
||||||
|
)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async setQuickLoginQQ(uin: string) {
|
||||||
|
await serverRequest.post<ServerResponse<null>>('/QQLogin/SetQuickLoginQQ', {
|
||||||
|
uin
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,20 +3,12 @@ import { EventSourcePolyfill } from 'event-source-polyfill'
|
|||||||
import { LogLevel } from '@/const/enum'
|
import { LogLevel } from '@/const/enum'
|
||||||
|
|
||||||
import { serverRequest } from '@/utils/request'
|
import { serverRequest } from '@/utils/request'
|
||||||
|
import CryptoJS from "crypto-js";
|
||||||
export interface Log {
|
export interface Log {
|
||||||
level: LogLevel
|
level: LogLevel
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerminalSession {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TerminalInfo {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class WebUIManager {
|
export default class WebUIManager {
|
||||||
public static async checkWebUiLogined() {
|
public static async checkWebUiLogined() {
|
||||||
const { data } =
|
const { data } =
|
||||||
@@ -25,9 +17,10 @@ export default class WebUIManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static async loginWithToken(token: string) {
|
public static async loginWithToken(token: string) {
|
||||||
|
const sha256 = CryptoJS.SHA256(token + '.napcat').toString();
|
||||||
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
|
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
|
||||||
'/auth/login',
|
'/auth/login',
|
||||||
{ token }
|
{ hash: sha256 }
|
||||||
)
|
)
|
||||||
return data.data.Credential
|
return data.data.Credential
|
||||||
}
|
}
|
||||||
@@ -40,6 +33,13 @@ export default class WebUIManager {
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async checkUsingDefaultToken() {
|
||||||
|
const { data } = await serverRequest.get<ServerResponse<boolean>>(
|
||||||
|
'/auth/check_using_default_token'
|
||||||
|
)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
public static async proxy<T>(url = '') {
|
public static async proxy<T>(url = '') {
|
||||||
const data = await serverRequest.get<ServerResponse<string>>(
|
const data = await serverRequest.get<ServerResponse<string>>(
|
||||||
'/base/proxy?url=' + encodeURIComponent(url)
|
'/base/proxy?url=' + encodeURIComponent(url)
|
||||||
@@ -60,6 +60,20 @@ export default class WebUIManager {
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getThemeConfig() {
|
||||||
|
const { data } =
|
||||||
|
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme')
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async setThemeConfig(theme: ThemeConfig) {
|
||||||
|
const { data } = await serverRequest.post<ServerResponse<boolean>>(
|
||||||
|
'/base/SetTheme',
|
||||||
|
{ theme }
|
||||||
|
)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
public static async getLogList() {
|
public static async getLogList() {
|
||||||
const { data } =
|
const { data } =
|
||||||
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList')
|
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList')
|
||||||
|
69
napcat.webui/src/hooks/use-preload-images.ts
Normal file
69
napcat.webui/src/hooks/use-preload-images.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
// 全局图片缓存
|
||||||
|
const imageCache = new Map<string, HTMLImageElement>()
|
||||||
|
|
||||||
|
export function usePreloadImages(urls: string[]) {
|
||||||
|
const [loadedUrls, setLoadedUrls] = useState<Record<string, boolean>>({})
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const isMounted = useRef(true)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isMounted.current = true
|
||||||
|
|
||||||
|
// 检查是否所有图片都已缓存
|
||||||
|
const allCached = urls.every((url) => imageCache.has(url))
|
||||||
|
if (allCached) {
|
||||||
|
setLoadedUrls(urls.reduce((acc, url) => ({ ...acc, [url]: true }), {}))
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true)
|
||||||
|
const loadedImages: Record<string, boolean> = {}
|
||||||
|
let pendingCount = urls.length
|
||||||
|
|
||||||
|
urls.forEach((url) => {
|
||||||
|
// 如果已经缓存,直接标记为已加载
|
||||||
|
if (imageCache.has(url)) {
|
||||||
|
loadedImages[url] = true
|
||||||
|
pendingCount--
|
||||||
|
if (pendingCount === 0) {
|
||||||
|
setLoadedUrls(loadedImages)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const img = new Image()
|
||||||
|
img.onload = () => {
|
||||||
|
if (!isMounted.current) return
|
||||||
|
loadedImages[url] = true
|
||||||
|
imageCache.set(url, img)
|
||||||
|
pendingCount--
|
||||||
|
|
||||||
|
if (pendingCount === 0) {
|
||||||
|
setLoadedUrls(loadedImages)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.onerror = () => {
|
||||||
|
if (!isMounted.current) return
|
||||||
|
loadedImages[url] = false
|
||||||
|
pendingCount--
|
||||||
|
|
||||||
|
if (pendingCount === 0) {
|
||||||
|
setLoadedUrls(loadedImages)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted.current = false
|
||||||
|
}
|
||||||
|
}, [urls])
|
||||||
|
|
||||||
|
return { loadedUrls, isLoading }
|
||||||
|
}
|
@@ -79,7 +79,7 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
}, [location.pathname])
|
}, [location.pathname])
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-screen relative flex bg-danger-50 dark:bg-black items-stretch"
|
className="h-screen relative flex bg-primary-50 dark:bg-black items-stretch"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(${b64img})`,
|
backgroundImage: `url(${b64img})`,
|
||||||
backgroundSize: 'cover'
|
backgroundSize: 'cover'
|
||||||
@@ -99,9 +99,9 @@ const Layout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'h-10 flex items-center font-bold text-xl backdrop-blur-lg rounded-full',
|
'h-10 flex items-center font-bold text-xl backdrop-blur-lg rounded-full',
|
||||||
'dark:bg-background dark:shadow-danger-100',
|
'dark:bg-background dark:shadow-primary-100',
|
||||||
'bg-background !bg-opacity-50',
|
'bg-background !bg-opacity-50',
|
||||||
'shadow-sm shadow-danger-50',
|
'shadow-sm shadow-primary-50',
|
||||||
'z-30 m-2 mb-0 sticky top-2 left-0'
|
'z-30 m-2 mb-0 sticky top-2 left-0'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@@ -8,6 +8,7 @@ import '@/styles/globals.css'
|
|||||||
|
|
||||||
import key from './const/key'
|
import key from './const/key'
|
||||||
import WebUIManager from './controllers/webui_manager'
|
import WebUIManager from './controllers/webui_manager'
|
||||||
|
import { loadTheme } from './utils/theme'
|
||||||
|
|
||||||
WebUIManager.checkWebUiLogined()
|
WebUIManager.checkWebUiLogined()
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ if (theme && !theme.startsWith('"')) {
|
|||||||
localStorage.setItem(key.theme, JSON.stringify(theme))
|
localStorage.setItem(key.theme, JSON.stringify(theme))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTheme()
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
// <React.StrictMode>
|
// <React.StrictMode>
|
||||||
<BrowserRouter basename="/webui/">
|
<BrowserRouter basename="/webui/">
|
||||||
|
@@ -1,12 +1,19 @@
|
|||||||
import { Chip } from '@heroui/chip'
|
import { Card, CardBody } from '@heroui/card'
|
||||||
import { Image } from '@heroui/image'
|
import { Image } from '@heroui/image'
|
||||||
|
import { Link } from '@heroui/link'
|
||||||
|
import { Skeleton } from '@heroui/skeleton'
|
||||||
import { Spinner } from '@heroui/spinner'
|
import { Spinner } from '@heroui/spinner'
|
||||||
import { useRequest } from 'ahooks'
|
import { useRequest } from 'ahooks'
|
||||||
import clsx from 'clsx'
|
import { useMemo } from 'react'
|
||||||
|
import { BsTelegram, BsTencentQq } from 'react-icons/bs'
|
||||||
|
import { IoDocument } from 'react-icons/io5'
|
||||||
|
|
||||||
import HoverTiltedCard from '@/components/hover_titled_card'
|
import HoverTiltedCard from '@/components/hover_titled_card'
|
||||||
import NapCatRepoInfo from '@/components/napcat_repo_info'
|
import NapCatRepoInfo from '@/components/napcat_repo_info'
|
||||||
import { title } from '@/components/primitives'
|
import RotatingText from '@/components/rotating_text'
|
||||||
|
|
||||||
|
import { usePreloadImages } from '@/hooks/use-preload-images'
|
||||||
|
import { useTheme } from '@/hooks/use-theme'
|
||||||
|
|
||||||
import logo from '@/assets/images/logo.png'
|
import logo from '@/assets/images/logo.png'
|
||||||
import WebUIManager from '@/controllers/webui_manager'
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
@@ -14,54 +21,177 @@ import WebUIManager from '@/controllers/webui_manager'
|
|||||||
function VersionInfo() {
|
function VersionInfo() {
|
||||||
const { data, loading, error } = useRequest(WebUIManager.getPackageInfo)
|
const { data, loading, error } = useRequest(WebUIManager.getPackageInfo)
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 mb-5">
|
<div className="flex items-center gap-2 text-2xl font-bold">
|
||||||
<Chip
|
<div className="flex items-center gap-2">
|
||||||
startContent={
|
<div className="text-primary-500 drop-shadow-md">NapCat</div>
|
||||||
<Chip color="warning" size="sm" className="-ml-0.5 select-none">
|
|
||||||
NapCat
|
|
||||||
</Chip>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{error ? (
|
{error ? (
|
||||||
error.message
|
error.message
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
<Spinner size="sm" />
|
<Spinner size="sm" />
|
||||||
) : (
|
) : (
|
||||||
data?.version
|
<RotatingText
|
||||||
|
texts={['WebUI', data?.version ?? '']}
|
||||||
|
mainClassName="overflow-hidden flex items-center bg-primary-500 px-2 rounded-lg text-default-50 shadow-md"
|
||||||
|
staggerFrom={'last'}
|
||||||
|
initial={{ y: '100%' }}
|
||||||
|
animate={{ y: 0 }}
|
||||||
|
exit={{ y: '-120%' }}
|
||||||
|
staggerDuration={0.025}
|
||||||
|
splitLevelClassName="overflow-hidden"
|
||||||
|
transition={{ type: 'spring', damping: 30, stiffness: 400 }}
|
||||||
|
rotationInterval={2000}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Chip>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
|
const { isDark } = useTheme()
|
||||||
|
|
||||||
|
const imageUrls = useMemo(
|
||||||
|
() => [
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=777721566&limit=30&image_size=auto&color_scheme=light',
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=777721566&limit=30&image_size=auto&color_scheme=dark',
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-activity-trends/thumbnail.png?repo_id=41986369&image_size=auto&color_scheme=light',
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-activity-trends/thumbnail.png?repo_id=41986369&image_size=auto&color_scheme=dark'
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const { loadedUrls, isLoading } = usePreloadImages(imageUrls)
|
||||||
|
|
||||||
|
const getImageUrl = useMemo(
|
||||||
|
() => (baseUrl: string) => {
|
||||||
|
const theme = isDark ? 'dark' : 'light'
|
||||||
|
const fullUrl = baseUrl.replace(
|
||||||
|
/color_scheme=(?:light|dark)/,
|
||||||
|
`color_scheme=${theme}`
|
||||||
|
)
|
||||||
|
return isLoading ? null : loadedUrls[fullUrl] ? fullUrl : null
|
||||||
|
},
|
||||||
|
[isDark, isLoading, loadedUrls]
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderImage = useMemo(
|
||||||
|
() => (baseUrl: string, alt: string) => {
|
||||||
|
const imageUrl = getImageUrl(baseUrl)
|
||||||
|
|
||||||
|
if (!imageUrl) {
|
||||||
|
return <Skeleton className="h-16 rounded-lg" />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
className="flex-1 pointer-events-none select-none rounded-none"
|
||||||
|
src={imageUrl}
|
||||||
|
alt={alt}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[getImageUrl]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<title>关于 NapCat WebUI</title>
|
<title>关于 NapCat WebUI</title>
|
||||||
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
|
<section className="max-w-7xl py-8 md:py-10 px-5 mx-auto space-y-10">
|
||||||
<div className="max-w-full w-[1000px] px-5 flex flex-col items-center">
|
<div className="w-full flex flex-col md:flex-row gap-4">
|
||||||
<div className="flex flex-col md:flex-row items-center mb-6">
|
<div className="flex flex-col md:flex-row items-center">
|
||||||
<HoverTiltedCard imageSrc={logo} />
|
<HoverTiltedCard imageSrc={logo} overlayContent="" />
|
||||||
</div>
|
</div>
|
||||||
<VersionInfo />
|
<div className="flex-1 flex flex-col gap-2 py-2">
|
||||||
<div className="mb-6 flex flex-col items-center gap-4">
|
<VersionInfo />
|
||||||
<p
|
<div className="space-y-1">
|
||||||
className={clsx(
|
<p className="font-bold text-primary-400">NapCat 是什么?</p>
|
||||||
title({
|
<p className="text-default-800">
|
||||||
color: 'cyan',
|
基于TypeScript构建的Bot框架,通过相应的启动器或者框架,主动调用QQ
|
||||||
shadow: true
|
Node模块提供给客户端的接口,实现Bot的功能.
|
||||||
}),
|
</p>
|
||||||
'!text-3xl'
|
<p className="font-bold text-primary-400">魔法版介绍</p>
|
||||||
)}
|
<p className="text-default-800">
|
||||||
>
|
猫猫框架通过魔法的手段获得了 QQ 的发送消息、接收消息等接口。
|
||||||
NapCat Contributors
|
为了方便使用,猫猫框架将通过一种名为 OneBot 的约定将你的 HTTP /
|
||||||
</p>
|
WebSocket 请求按照规范读取,
|
||||||
<Image
|
再去调用猫猫框架所获得的QQ发送接口之类的接口。
|
||||||
className="w-[600px] max-w-full pointer-events-none select-none"
|
</p>
|
||||||
src="https://contrib.rocks/image?repo=bietiaop/NapCatQQ"
|
</div>
|
||||||
alt="Contributors"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-2 flex-wrap justify-around">
|
||||||
|
<Card
|
||||||
|
as={Link}
|
||||||
|
shadow="sm"
|
||||||
|
isPressable
|
||||||
|
isExternal
|
||||||
|
href="https://qm.qq.com/q/F9cgs1N3Mc"
|
||||||
|
>
|
||||||
|
<CardBody className="flex-row items-center gap-2">
|
||||||
|
<span className="p-2 rounded-small bg-primary-50 text-primary-500">
|
||||||
|
<BsTencentQq size={16} />
|
||||||
|
</span>
|
||||||
|
<span>官方社群1</span>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
as={Link}
|
||||||
|
shadow="sm"
|
||||||
|
isPressable
|
||||||
|
isExternal
|
||||||
|
href="https://qm.qq.com/q/hSt0u9PVn"
|
||||||
|
>
|
||||||
|
<CardBody className="flex-row items-center gap-2">
|
||||||
|
<span className="p-2 rounded-small bg-primary-50 text-primary-500">
|
||||||
|
<BsTencentQq size={16} />
|
||||||
|
</span>
|
||||||
|
<span>官方社群2</span>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
as={Link}
|
||||||
|
shadow="sm"
|
||||||
|
isPressable
|
||||||
|
isExternal
|
||||||
|
href="https://t.me/MelodicMoonlight"
|
||||||
|
>
|
||||||
|
<CardBody className="flex-row items-center gap-2">
|
||||||
|
<span className="p-2 rounded-small bg-primary-50 text-primary-500">
|
||||||
|
<BsTelegram size={16} />
|
||||||
|
</span>
|
||||||
|
<span>Telegram</span>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
as={Link}
|
||||||
|
shadow="sm"
|
||||||
|
isPressable
|
||||||
|
isExternal
|
||||||
|
href="https://napcat.napneko.icu/"
|
||||||
|
>
|
||||||
|
<CardBody className="flex-row items-center gap-2">
|
||||||
|
<span className="p-2 rounded-small bg-primary-50 text-primary-500">
|
||||||
|
<IoDocument size={16} />
|
||||||
|
</span>
|
||||||
|
<span>使用文档</span>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col md:flex-row md:items-start gap-4">
|
||||||
|
<div className="w-full flex flex-col gap-4">
|
||||||
|
{renderImage(
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-recent-active-contributors/thumbnail.png?repo_id=777721566&limit=30&image_size=auto&color_scheme=light',
|
||||||
|
'Contributors'
|
||||||
|
)}
|
||||||
|
{renderImage(
|
||||||
|
'https://next.ossinsight.io/widgets/official/compose-activity-trends/thumbnail.png?repo_id=41986369&image_size=auto&color_scheme=light',
|
||||||
|
'Activity Trends'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<NapCatRepoInfo />
|
<NapCatRepoInfo />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -1,20 +1,36 @@
|
|||||||
import { Card, CardBody } from '@heroui/card'
|
import { Card, CardBody } from '@heroui/card'
|
||||||
import { Tab, Tabs } from '@heroui/tabs'
|
import { Tab, Tabs } from '@heroui/tabs'
|
||||||
|
import clsx from 'clsx'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import ChangePasswordCard from './change_password'
|
import ChangePasswordCard from './change_password'
|
||||||
|
import LoginConfigCard from './login'
|
||||||
import OneBotConfigCard from './onebot'
|
import OneBotConfigCard from './onebot'
|
||||||
|
import ThemeConfigCard from './theme'
|
||||||
import WebUIConfigCard from './webui'
|
import WebUIConfigCard from './webui'
|
||||||
|
|
||||||
export interface ConfigPageProps {
|
export interface ConfigPageProps {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
|
size?: 'sm' | 'md' | 'lg'
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
|
const ConfingPageItem: React.FC<ConfigPageProps> = ({
|
||||||
|
children,
|
||||||
|
size = 'md'
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card className="bg-opacity-50 backdrop-blur-sm">
|
<Card className="bg-opacity-50 backdrop-blur-sm">
|
||||||
<CardBody className="items-center py-5">
|
<CardBody className="items-center py-5">
|
||||||
<div className="w-96 max-w-full flex flex-col gap-2">{children}</div>
|
<div
|
||||||
|
className={clsx('max-w-full flex flex-col gap-2', {
|
||||||
|
'w-72': size === 'sm',
|
||||||
|
'w-96': size === 'md',
|
||||||
|
'w-[32rem]': size === 'lg'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
@@ -22,6 +38,11 @@ const ConfingPageItem: React.FC<ConfigPageProps> = ({ children }) => {
|
|||||||
|
|
||||||
export default function ConfigPage() {
|
export default function ConfigPage() {
|
||||||
const isMediumUp = useMediaQuery({ minWidth: 768 })
|
const isMediumUp = useMediaQuery({ minWidth: 768 })
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const search = useSearchParams({
|
||||||
|
tab: 'onebot'
|
||||||
|
})[0]
|
||||||
|
const tab = search.get('tab') ?? 'onebot'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10">
|
<section className="w-[1000px] max-w-full md:mx-auto gap-4 py-8 px-2 md:py-10">
|
||||||
@@ -30,6 +51,10 @@ export default function ConfigPage() {
|
|||||||
fullWidth
|
fullWidth
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isVertical={isMediumUp}
|
isVertical={isMediumUp}
|
||||||
|
selectedKey={tab}
|
||||||
|
onSelectionChange={(key) => {
|
||||||
|
navigate(`/config?tab=${key}`)
|
||||||
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
tabList: 'sticky flex top-14 bg-opacity-50 backdrop-blur-sm',
|
tabList: 'sticky flex top-14 bg-opacity-50 backdrop-blur-sm',
|
||||||
panel: 'w-full relative',
|
panel: 'w-full relative',
|
||||||
@@ -47,12 +72,22 @@ export default function ConfigPage() {
|
|||||||
<WebUIConfigCard />
|
<WebUIConfigCard />
|
||||||
</ConfingPageItem>
|
</ConfingPageItem>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab title="登录配置" key="login">
|
||||||
|
<ConfingPageItem>
|
||||||
|
<LoginConfigCard />
|
||||||
|
</ConfingPageItem>
|
||||||
|
</Tab>
|
||||||
<Tab title="修改密码" key="token">
|
<Tab title="修改密码" key="token">
|
||||||
<ConfingPageItem>
|
<ConfingPageItem>
|
||||||
<ChangePasswordCard />
|
<ChangePasswordCard />
|
||||||
</ConfingPageItem>
|
</ConfingPageItem>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
<Tab title="主题配置" key="theme">
|
||||||
|
<ConfingPageItem size="lg">
|
||||||
|
<ThemeConfigCard />
|
||||||
|
</ConfingPageItem>
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
89
napcat.webui/src/pages/dashboard/config/login.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Input } from '@heroui/input'
|
||||||
|
import { useRequest } from 'ahooks'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { Controller, useForm } from 'react-hook-form'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
|
import PageLoading from '@/components/page_loading'
|
||||||
|
|
||||||
|
import QQManager from '@/controllers/qq_manager'
|
||||||
|
|
||||||
|
const LoginConfigCard = () => {
|
||||||
|
const {
|
||||||
|
data: quickLoginData,
|
||||||
|
loading: quickLoginLoading,
|
||||||
|
error: quickLoginError,
|
||||||
|
refreshAsync: refreshQuickLogin
|
||||||
|
} = useRequest(QQManager.getQuickLoginQQ)
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit: handleOnebotSubmit,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
setValue: setOnebotValue
|
||||||
|
} = useForm<{
|
||||||
|
quickLoginQQ: string
|
||||||
|
}>({
|
||||||
|
defaultValues: {
|
||||||
|
quickLoginQQ: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setOnebotValue('quickLoginQQ', quickLoginData ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await QQManager.setQuickLoginQQ(data.quickLoginQQ)
|
||||||
|
toast.success('保存成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`保存失败: ${msg}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
try {
|
||||||
|
await refreshQuickLogin()
|
||||||
|
toast.success('刷新成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`刷新失败: ${msg}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset()
|
||||||
|
}, [quickLoginData])
|
||||||
|
|
||||||
|
if (quickLoginLoading) return <PageLoading loading={true} />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>OneBot配置 - NapCat WebUI</title>
|
||||||
|
<div className="flex-shrink-0 w-full">快速登录QQ</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="quickLoginQQ"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
label="快速登录QQ"
|
||||||
|
placeholder="请输入QQ号"
|
||||||
|
isDisabled={!!quickLoginError}
|
||||||
|
errorMessage={quickLoginError ? '获取快速登录QQ失败' : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<SaveButtons
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
reset={reset}
|
||||||
|
isSubmitting={isSubmitting || quickLoginLoading}
|
||||||
|
refresh={onRefresh}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginConfigCard
|
@@ -30,9 +30,9 @@ const OneBotConfigCard = () => {
|
|||||||
setOnebotValue('parseMultMsg', config.parseMultMsg)
|
setOnebotValue('parseMultMsg', config.parseMultMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = handleOnebotSubmit((data) => {
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
saveConfigWithoutNetwork(data)
|
await saveConfigWithoutNetwork(data)
|
||||||
toast.success('保存成功')
|
toast.success('保存成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = (error as Error).message
|
const msg = (error as Error).message
|
||||||
|
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
279
napcat.webui/src/pages/dashboard/config/theme.tsx
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import { Accordion, AccordionItem } from '@heroui/accordion'
|
||||||
|
import { Card, CardBody, CardHeader } from '@heroui/card'
|
||||||
|
import { useRequest } from 'ahooks'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { Controller, useForm, useWatch } from 'react-hook-form'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { FaUserAstronaut } from 'react-icons/fa'
|
||||||
|
import { FaPaintbrush } from 'react-icons/fa6'
|
||||||
|
import { IoIosColorPalette } from 'react-icons/io'
|
||||||
|
import { MdDarkMode, MdLightMode } from 'react-icons/md'
|
||||||
|
|
||||||
|
import themes from '@/const/themes'
|
||||||
|
|
||||||
|
import ColorPicker from '@/components/ColorPicker'
|
||||||
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
|
import PageLoading from '@/components/page_loading'
|
||||||
|
|
||||||
|
import { colorKeys, generateTheme, loadTheme } from '@/utils/theme'
|
||||||
|
|
||||||
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
|
|
||||||
|
export type PreviewThemeCardProps = {
|
||||||
|
theme: ThemeInfo
|
||||||
|
onPreview: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
'',
|
||||||
|
'-50',
|
||||||
|
'-100',
|
||||||
|
'-200',
|
||||||
|
'-300',
|
||||||
|
'-400',
|
||||||
|
'-500',
|
||||||
|
'-600',
|
||||||
|
'-700',
|
||||||
|
'-800',
|
||||||
|
'-900'
|
||||||
|
]
|
||||||
|
const colors = [
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'success',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'default'
|
||||||
|
]
|
||||||
|
|
||||||
|
function PreviewThemeCard({ theme, onPreview }: PreviewThemeCardProps) {
|
||||||
|
const style = document.createElement('style')
|
||||||
|
style.innerHTML = generateTheme(theme.theme, theme.name)
|
||||||
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
document.head.appendChild(style)
|
||||||
|
return () => {
|
||||||
|
document.head.removeChild(style)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
ref={cardRef}
|
||||||
|
shadow="sm"
|
||||||
|
radius="sm"
|
||||||
|
isPressable
|
||||||
|
onPress={onPreview}
|
||||||
|
className={clsx('text-primary bg-primary-50', theme.name)}
|
||||||
|
>
|
||||||
|
<CardHeader className="pb-0 flex flex-col items-start gap-1">
|
||||||
|
<div className="px-1 rounded-md bg-primary text-primary-foreground">
|
||||||
|
{theme.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs flex items-center gap-1 text-primary-300">
|
||||||
|
<FaUserAstronaut />
|
||||||
|
{theme.author ?? '未知'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-primary-200">{theme.description}</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{colors.map((color) => (
|
||||||
|
<div className="flex gap-1 items-center flex-wrap" key={color}>
|
||||||
|
<div className="text-xs w-4 text-right">
|
||||||
|
{color[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
{values.map((value) => (
|
||||||
|
<div
|
||||||
|
key={value}
|
||||||
|
className={clsx(
|
||||||
|
'w-2 h-2 rounded-full shadow-small',
|
||||||
|
`bg-${color}${value}`
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeConfigCard = () => {
|
||||||
|
const { data, loading, error, refreshAsync } = useRequest(
|
||||||
|
WebUIManager.getThemeConfig
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit: handleOnebotSubmit,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
setValue: setOnebotValue
|
||||||
|
} = useForm<{
|
||||||
|
theme: ThemeConfig
|
||||||
|
}>({
|
||||||
|
defaultValues: {
|
||||||
|
theme: {
|
||||||
|
dark: {},
|
||||||
|
light: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 使用 useRef 存储 style 标签引用
|
||||||
|
const styleTagRef = useRef<HTMLStyleElement | null>(null)
|
||||||
|
|
||||||
|
// 在组件挂载时创建 style 标签,并在卸载时清理
|
||||||
|
useEffect(() => {
|
||||||
|
const styleTag = document.createElement('style')
|
||||||
|
document.head.appendChild(styleTag)
|
||||||
|
styleTagRef.current = styleTag
|
||||||
|
return () => {
|
||||||
|
if (styleTagRef.current) {
|
||||||
|
document.head.removeChild(styleTagRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const theme = useWatch({ control, name: 'theme' })
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
if (data) setOnebotValue('theme', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = handleOnebotSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await WebUIManager.setThemeConfig(data.theme)
|
||||||
|
toast.success('保存成功')
|
||||||
|
loadTheme()
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`保存失败: ${msg}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
try {
|
||||||
|
await refreshAsync()
|
||||||
|
toast.success('刷新成功')
|
||||||
|
} catch (error) {
|
||||||
|
const msg = (error as Error).message
|
||||||
|
toast.error(`刷新失败: ${msg}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset()
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme && styleTagRef.current) {
|
||||||
|
const css = generateTheme(theme)
|
||||||
|
styleTagRef.current.innerHTML = css
|
||||||
|
}
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
if (loading) return <PageLoading loading={true} />
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<div className="py-24 text-danger-500 text-center">{error.message}</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<title>主题配置 - NapCat WebUI</title>
|
||||||
|
|
||||||
|
<SaveButtons
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
reset={reset}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
refresh={onRefresh}
|
||||||
|
className="items-end w-full p-4"
|
||||||
|
/>
|
||||||
|
<div className="px-4 text-sm text-default-600">实时预览,记得保存!</div>
|
||||||
|
<Accordion variant="splitted" defaultExpandedKeys={['select']}>
|
||||||
|
<AccordionItem
|
||||||
|
key="select"
|
||||||
|
aria-label="Pick Color"
|
||||||
|
title="选择主题"
|
||||||
|
subtitle="可以切换夜间/白昼模式查看对应颜色"
|
||||||
|
className="shadow-small"
|
||||||
|
startContent={<IoIosColorPalette />}
|
||||||
|
>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{themes.map((theme) => (
|
||||||
|
<PreviewThemeCard
|
||||||
|
key={theme.name}
|
||||||
|
theme={theme}
|
||||||
|
onPreview={() => {
|
||||||
|
setOnebotValue('theme', theme.theme)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
|
||||||
|
<AccordionItem
|
||||||
|
key="pick"
|
||||||
|
aria-label="Pick Color"
|
||||||
|
title="自定义配色"
|
||||||
|
className="shadow-small"
|
||||||
|
startContent={<FaPaintbrush />}
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{(['dark', 'light'] as const).map((mode) => (
|
||||||
|
<div
|
||||||
|
key={mode}
|
||||||
|
className={clsx(
|
||||||
|
'p-2 rounded-md',
|
||||||
|
mode === 'dark' ? 'text-white' : 'text-black',
|
||||||
|
mode === 'dark'
|
||||||
|
? 'bg-content1-foreground dark:bg-content1'
|
||||||
|
: 'bg-content1 dark:bg-content1-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h3 className="text-center p-2 rounded-md bg-content2 mb-2 text-default-800 flex items-center justify-center">
|
||||||
|
{mode === 'dark' ? (
|
||||||
|
<MdDarkMode size={24} />
|
||||||
|
) : (
|
||||||
|
<MdLightMode size={24} />
|
||||||
|
)}
|
||||||
|
{mode === 'dark' ? '夜间模式主题' : '白昼模式主题'}
|
||||||
|
</h3>
|
||||||
|
{colorKeys.map((key) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className="grid grid-cols-2 items-center mb-2 gap-2"
|
||||||
|
>
|
||||||
|
<label className="text-right">{key}</label>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={`theme.${mode}.${key}`}
|
||||||
|
render={({ field: { value, onChange } }) => {
|
||||||
|
const hslArray = value?.split(' ') ?? [0, 0, 0]
|
||||||
|
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
|
||||||
|
return (
|
||||||
|
<ColorPicker
|
||||||
|
color={color}
|
||||||
|
onChange={(result) => {
|
||||||
|
onChange(
|
||||||
|
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThemeConfigCard
|
@@ -7,11 +7,13 @@ import toast from 'react-hot-toast'
|
|||||||
import key from '@/const/key'
|
import key from '@/const/key'
|
||||||
|
|
||||||
import SaveButtons from '@/components/button/save_buttons'
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
|
import FileInput from '@/components/input/file_input'
|
||||||
import ImageInput from '@/components/input/image_input'
|
import ImageInput from '@/components/input/image_input'
|
||||||
|
|
||||||
import useMusic from '@/hooks/use-music'
|
import useMusic from '@/hooks/use-music'
|
||||||
|
|
||||||
import { siteConfig } from '@/config/site'
|
import { siteConfig } from '@/config/site'
|
||||||
|
import FileManager from '@/controllers/file_manager'
|
||||||
|
|
||||||
const WebUIConfigCard = () => {
|
const WebUIConfigCard = () => {
|
||||||
const {
|
const {
|
||||||
@@ -59,17 +61,51 @@ const WebUIConfigCard = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<title>WebUI配置 - NapCat WebUI</title>
|
<title>WebUI配置 - NapCat WebUI</title>
|
||||||
<Controller
|
<div className="flex flex-col gap-2">
|
||||||
control={control}
|
<div className="flex-shrink-0 w-full">WebUI字体</div>
|
||||||
name="musicListID"
|
<div className="text-sm text-default-400">
|
||||||
render={({ field }) => (
|
此项不需要手动保存,上传成功后需清空网页缓存并刷新
|
||||||
<Input
|
<FileInput
|
||||||
{...field}
|
label="中文字体"
|
||||||
label="网易云音乐歌单ID(网页内音乐播放器)"
|
onChange={async (file) => {
|
||||||
placeholder="请输入歌单ID"
|
try {
|
||||||
|
await FileManager.uploadWebUIFont(file)
|
||||||
|
toast.success('上传成功')
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('上传失败: ' + (error as Error).message)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDelete={async () => {
|
||||||
|
try {
|
||||||
|
await FileManager.deleteWebUIFont()
|
||||||
|
toast.success('删除成功')
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('删除失败: ' + (error as Error).message)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex-shrink-0 w-full">WebUI音乐播放器</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="musicListID"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
label="网易云音乐歌单ID(网页内音乐播放器)"
|
||||||
|
placeholder="请输入歌单ID"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex-shrink-0 w-full">背景图</div>
|
<div className="flex-shrink-0 w-full">背景图</div>
|
||||||
<Controller
|
<Controller
|
||||||
|
@@ -41,7 +41,7 @@ export default function HttpDebug() {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
radius="md"
|
radius="md"
|
||||||
variant="shadow"
|
variant="shadow"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
@@ -15,7 +15,7 @@ import { useWebSocketDebug } from '@/hooks/use-websocket-debug'
|
|||||||
|
|
||||||
export default function WSDebug() {
|
export default function WSDebug() {
|
||||||
const url = new URL(window.location.origin)
|
const url = new URL(window.location.origin)
|
||||||
url.port = '3000'
|
url.port = '3001'
|
||||||
url.protocol = 'ws:'
|
url.protocol = 'ws:'
|
||||||
const defaultWsUrl = url.href
|
const defaultWsUrl = url.href
|
||||||
const [socketConfig, setSocketConfig] = useLocalStorage(key.wsDebugConfig, {
|
const [socketConfig, setSocketConfig] = useLocalStorage(key.wsDebugConfig, {
|
||||||
@@ -64,7 +64,7 @@ export default function WSDebug() {
|
|||||||
/>
|
/>
|
||||||
<div className="flex-shrink-0 flex gap-2 col-span-2 md:col-span-1">
|
<div className="flex-shrink-0 flex gap-2 col-span-2 md:col-span-1">
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
onPress={handleConnect}
|
onPress={handleConnect}
|
||||||
size="lg"
|
size="lg"
|
||||||
radius="full"
|
radius="full"
|
||||||
|
@@ -332,7 +332,7 @@ export default function FileManagerPage() {
|
|||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="mb-4 flex items-center gap-4 sticky top-14 z-10 bg-content1 py-1">
|
<div className="mb-4 flex items-center gap-4 sticky top-14 z-10 bg-content1 py-1">
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@@ -343,7 +343,7 @@ export default function FileManagerPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@@ -354,7 +354,7 @@ export default function FileManagerPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
@@ -365,7 +365,7 @@ export default function FileManagerPage() {
|
|||||||
<MdRefresh />
|
<MdRefresh />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
isIconOnly
|
isIconOnly
|
||||||
variant="flat"
|
variant="flat"
|
||||||
@@ -379,7 +379,7 @@ export default function FileManagerPage() {
|
|||||||
selectedFiles === 'all') && (
|
selectedFiles === 'all') && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={handleBatchDelete}
|
onPress={handleBatchDelete}
|
||||||
@@ -391,7 +391,7 @@ export default function FileManagerPage() {
|
|||||||
)
|
)
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -406,7 +406,7 @@ export default function FileManagerPage() {
|
|||||||
)
|
)
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={handleBatchDownload}
|
onPress={handleBatchDownload}
|
||||||
|
@@ -105,7 +105,7 @@ const DashboardIndexPage: React.FC = () => {
|
|||||||
<SystemStatusCard setArchInfo={setArchInfo} />
|
<SystemStatusCard setArchInfo={setArchInfo} />
|
||||||
</div>
|
</div>
|
||||||
<Networks />
|
<Networks />
|
||||||
<Card className="bg-opacity-60 shadow-sm shadow-danger-50">
|
<Card className="bg-opacity-60 shadow-sm shadow-primary-100">
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<Hitokoto />
|
<Hitokoto />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
@@ -133,7 +133,7 @@ export default function TerminalPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="min-w-0 w-4 h-4 flex-shrink-0"
|
className="min-w-0 w-4 h-4 flex-shrink-0"
|
||||||
onPress={() => closeTerminal(tab.id)}
|
onPress={() => closeTerminal(tab.id)}
|
||||||
color={selectedTab === tab.id ? 'danger' : 'default'}
|
color={selectedTab === tab.id ? 'primary' : 'default'}
|
||||||
>
|
>
|
||||||
<IoClose />
|
<IoClose />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -143,7 +143,7 @@ export default function TerminalPage() {
|
|||||||
</TabList>
|
</TabList>
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
color="danger"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={createNewTerminal}
|
onPress={createNewTerminal}
|
||||||
|
@@ -1,46 +1,67 @@
|
|||||||
|
import { Spinner } from '@heroui/spinner'
|
||||||
import { AnimatePresence, motion } from 'motion/react'
|
import { AnimatePresence, motion } from 'motion/react'
|
||||||
import { Route, Routes, useLocation } from 'react-router-dom'
|
import { Suspense, useEffect } from 'react'
|
||||||
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
import useAuth from '@/hooks/auth'
|
||||||
|
import useDialog from '@/hooks/use-dialog'
|
||||||
|
|
||||||
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
import DefaultLayout from '@/layouts/default'
|
import DefaultLayout from '@/layouts/default'
|
||||||
|
|
||||||
import DashboardIndexPage from './dashboard'
|
const CheckDefaultPassword = () => {
|
||||||
import AboutPage from './dashboard/about'
|
const { isAuth } = useAuth()
|
||||||
import ConfigPage from './dashboard/config'
|
const dialog = useDialog()
|
||||||
import DebugPage from './dashboard/debug'
|
const navigate = useNavigate()
|
||||||
import HttpDebug from './dashboard/debug/http'
|
const checkDefaultPassword = async () => {
|
||||||
import WSDebug from './dashboard/debug/websocket'
|
const data = await WebUIManager.checkUsingDefaultToken()
|
||||||
import FileManagerPage from './dashboard/file_manager'
|
if (data) {
|
||||||
import LogsPage from './dashboard/logs'
|
dialog.confirm({
|
||||||
import NetworkPage from './dashboard/network'
|
title: '修改默认密码',
|
||||||
import TerminalPage from './dashboard/terminal'
|
content: '检测到当前密码为默认密码,请尽快修改密码。',
|
||||||
|
confirmText: '前往修改',
|
||||||
|
onConfirm: () => {
|
||||||
|
navigate('/config?tab=token')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuth) {
|
||||||
|
checkDefaultPassword()
|
||||||
|
}
|
||||||
|
}, [isAuth])
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export default function IndexPage() {
|
export default function IndexPage() {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<AnimatePresence mode="wait">
|
<CheckDefaultPassword />
|
||||||
<motion.div
|
<Suspense
|
||||||
key={location.pathname}
|
fallback={
|
||||||
initial={{ opacity: 0, y: 50 }}
|
<div className="flex justify-center px-10">
|
||||||
animate={{ opacity: 1, y: 0 }}
|
<Spinner />
|
||||||
exit={{ opacity: 0, y: -50 }}
|
</div>
|
||||||
transition={{ duration: 0.3 }}
|
}
|
||||||
>
|
>
|
||||||
<Routes location={location} key={location.pathname}>
|
<AnimatePresence mode="wait">
|
||||||
<Route element={<DashboardIndexPage />} path="/" />
|
<motion.div
|
||||||
<Route element={<NetworkPage />} path="/network" />
|
key={location.pathname}
|
||||||
<Route element={<ConfigPage />} path="/config" />
|
initial={{ opacity: 0, y: 20 }}
|
||||||
<Route element={<LogsPage />} path="/logs" />
|
animate={{ opacity: 1, y: 0 }}
|
||||||
<Route element={<DebugPage />} path="/debug">
|
transition={{
|
||||||
<Route path="ws" element={<WSDebug />} />
|
type: 'tween',
|
||||||
<Route path="http" element={<HttpDebug />} />
|
ease: 'easeInOut'
|
||||||
</Route>
|
}}
|
||||||
<Route element={<FileManagerPage />} path="/file_manager" />
|
>
|
||||||
<Route element={<TerminalPage />} path="/terminal" />
|
<Outlet />
|
||||||
<Route element={<AboutPage />} path="/about" />
|
</motion.div>
|
||||||
</Routes>
|
</AnimatePresence>
|
||||||
</motion.div>
|
</Suspense>
|
||||||
</AnimatePresence>
|
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -47,6 +47,22 @@ export default function WebLoginPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理全局键盘事件
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !isLoading) {
|
||||||
|
onSubmit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keydown', handleKeyDown)
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyDown)
|
||||||
|
}
|
||||||
|
}, [tokenValue, isLoading]) // 依赖项包含用于登录的状态
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token) {
|
if (token) {
|
||||||
onSubmit()
|
onSubmit()
|
||||||
@@ -79,6 +95,7 @@ export default function WebLoginPage() {
|
|||||||
<CardBody className="flex gap-5 py-5 px-5 md:px-10">
|
<CardBody className="flex gap-5 py-5 px-5 md:px-10">
|
||||||
<Input
|
<Input
|
||||||
isClearable
|
isClearable
|
||||||
|
type="password"
|
||||||
classNames={{
|
classNames={{
|
||||||
label: 'text-black/50 dark:text-white/90',
|
label: 'text-black/50 dark:text-white/90',
|
||||||
input: [
|
input: [
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
.shiny-text {
|
.shiny-text {
|
||||||
@apply text-pink-400 text-opacity-60;
|
@apply text-primary-400 text-opacity-60;
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
120deg,
|
120deg,
|
||||||
rgba(255, 50, 50, 0) 40%,
|
rgba(255, 50, 50, 0) 40%,
|
||||||
rgba(255, 76, 76, 0.8) 50%,
|
hsl(var(--heroui-primary-400) / 0.8) 50%,
|
||||||
rgba(255, 50, 50, 0) 60%
|
rgba(255, 50, 50, 0) 60%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,10 @@
|
|||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
120deg,
|
120deg,
|
||||||
rgba(255, 255, 255, 0) 40%,
|
rgba(255, 255, 255, 0) 40%,
|
||||||
rgba(206, 21, 21, 0.8) 50%,
|
hsl(var(--heroui-primary-600) / 0.8) 50%,
|
||||||
rgba(255, 255, 255, 0) 60%
|
rgba(255, 255, 255, 0) 60%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shine {
|
@keyframes shine {
|
||||||
0% {
|
0% {
|
||||||
background-position: 100%;
|
background-position: 100%;
|
||||||
|
133
napcat.webui/src/types/server.d.ts
vendored
133
napcat.webui/src/types/server.d.ts
vendored
@@ -48,3 +48,136 @@ interface SystemStatus {
|
|||||||
}
|
}
|
||||||
arch: string
|
arch: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThemeConfigItem {
|
||||||
|
'--heroui-background': string
|
||||||
|
'--heroui-foreground-50': string
|
||||||
|
'--heroui-foreground-100': string
|
||||||
|
'--heroui-foreground-200': string
|
||||||
|
'--heroui-foreground-300': string
|
||||||
|
'--heroui-foreground-400': string
|
||||||
|
'--heroui-foreground-500': string
|
||||||
|
'--heroui-foreground-600': string
|
||||||
|
'--heroui-foreground-700': string
|
||||||
|
'--heroui-foreground-800': string
|
||||||
|
'--heroui-foreground-900': string
|
||||||
|
'--heroui-foreground': string
|
||||||
|
'--heroui-focus': string
|
||||||
|
'--heroui-overlay': string
|
||||||
|
'--heroui-divider': string
|
||||||
|
'--heroui-divider-opacity': string
|
||||||
|
'--heroui-content1': string
|
||||||
|
'--heroui-content1-foreground': string
|
||||||
|
'--heroui-content2': string
|
||||||
|
'--heroui-content2-foreground': string
|
||||||
|
'--heroui-content3': string
|
||||||
|
'--heroui-content3-foreground': string
|
||||||
|
'--heroui-content4': string
|
||||||
|
'--heroui-content4-foreground': string
|
||||||
|
'--heroui-default-50': string
|
||||||
|
'--heroui-default-100': string
|
||||||
|
'--heroui-default-200': string
|
||||||
|
'--heroui-default-300': string
|
||||||
|
'--heroui-default-400': string
|
||||||
|
'--heroui-default-500': string
|
||||||
|
'--heroui-default-600': string
|
||||||
|
'--heroui-default-700': string
|
||||||
|
'--heroui-default-800': string
|
||||||
|
'--heroui-default-900': string
|
||||||
|
'--heroui-default-foreground': string
|
||||||
|
'--heroui-default': string
|
||||||
|
// 新增 danger
|
||||||
|
'--heroui-danger-50': string
|
||||||
|
'--heroui-danger-100': string
|
||||||
|
'--heroui-danger-200': string
|
||||||
|
'--heroui-danger-300': string
|
||||||
|
'--heroui-danger-400': string
|
||||||
|
'--heroui-danger-500': string
|
||||||
|
'--heroui-danger-600': string
|
||||||
|
'--heroui-danger-700': string
|
||||||
|
'--heroui-danger-800': string
|
||||||
|
'--heroui-danger-900': string
|
||||||
|
'--heroui-danger-foreground': string
|
||||||
|
'--heroui-danger': string
|
||||||
|
// 新增 primary
|
||||||
|
'--heroui-primary-50': string
|
||||||
|
'--heroui-primary-100': string
|
||||||
|
'--heroui-primary-200': string
|
||||||
|
'--heroui-primary-300': string
|
||||||
|
'--heroui-primary-400': string
|
||||||
|
'--heroui-primary-500': string
|
||||||
|
'--heroui-primary-600': string
|
||||||
|
'--heroui-primary-700': string
|
||||||
|
'--heroui-primary-800': string
|
||||||
|
'--heroui-primary-900': string
|
||||||
|
'--heroui-primary-foreground': string
|
||||||
|
'--heroui-primary': string
|
||||||
|
// 新增 secondary
|
||||||
|
'--heroui-secondary-50': string
|
||||||
|
'--heroui-secondary-100': string
|
||||||
|
'--heroui-secondary-200': string
|
||||||
|
'--heroui-secondary-300': string
|
||||||
|
'--heroui-secondary-400': string
|
||||||
|
'--heroui-secondary-500': string
|
||||||
|
'--heroui-secondary-600': string
|
||||||
|
'--heroui-secondary-700': string
|
||||||
|
'--heroui-secondary-800': string
|
||||||
|
'--heroui-secondary-900': string
|
||||||
|
'--heroui-secondary-foreground': string
|
||||||
|
'--heroui-secondary': string
|
||||||
|
// 新增 success
|
||||||
|
'--heroui-success-50': string
|
||||||
|
'--heroui-success-100': string
|
||||||
|
'--heroui-success-200': string
|
||||||
|
'--heroui-success-300': string
|
||||||
|
'--heroui-success-400': string
|
||||||
|
'--heroui-success-500': string
|
||||||
|
'--heroui-success-600': string
|
||||||
|
'--heroui-success-700': string
|
||||||
|
'--heroui-success-800': string
|
||||||
|
'--heroui-success-900': string
|
||||||
|
'--heroui-success-foreground': string
|
||||||
|
'--heroui-success': string
|
||||||
|
// 新增 warning
|
||||||
|
'--heroui-warning-50': string
|
||||||
|
'--heroui-warning-100': string
|
||||||
|
'--heroui-warning-200': string
|
||||||
|
'--heroui-warning-300': string
|
||||||
|
'--heroui-warning-400': string
|
||||||
|
'--heroui-warning-500': string
|
||||||
|
'--heroui-warning-600': string
|
||||||
|
'--heroui-warning-700': string
|
||||||
|
'--heroui-warning-800': string
|
||||||
|
'--heroui-warning-900': string
|
||||||
|
'--heroui-warning-foreground': string
|
||||||
|
'--heroui-warning': string
|
||||||
|
// 其它配置
|
||||||
|
'--heroui-code-background': string
|
||||||
|
'--heroui-strong': string
|
||||||
|
'--heroui-code-mdx': string
|
||||||
|
'--heroui-divider-weight': string
|
||||||
|
'--heroui-disabled-opacity': string
|
||||||
|
'--heroui-font-size-tiny': string
|
||||||
|
'--heroui-font-size-small': string
|
||||||
|
'--heroui-font-size-medium': string
|
||||||
|
'--heroui-font-size-large': string
|
||||||
|
'--heroui-line-height-tiny': string
|
||||||
|
'--heroui-line-height-small': string
|
||||||
|
'--heroui-line-height-medium': string
|
||||||
|
'--heroui-line-height-large': string
|
||||||
|
'--heroui-radius-small': string
|
||||||
|
'--heroui-radius-medium': string
|
||||||
|
'--heroui-radius-large': string
|
||||||
|
'--heroui-border-width-small': string
|
||||||
|
'--heroui-border-width-medium': string
|
||||||
|
'--heroui-border-width-large': string
|
||||||
|
'--heroui-box-shadow-small': string
|
||||||
|
'--heroui-box-shadow-medium': string
|
||||||
|
'--heroui-box-shadow-large': string
|
||||||
|
'--heroui-hover-opacity': string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeConfig {
|
||||||
|
dark: ThemeConfigItem
|
||||||
|
light: ThemeConfigItem
|
||||||
|
}
|
||||||
|
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
6
napcat.webui/src/types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
interface ThemeInfo {
|
||||||
|
theme: ThemeConfig
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
author?: string
|
||||||
|
}
|
@@ -1,19 +1,21 @@
|
|||||||
import { PlayMode } from '@/const/enum'
|
import { PlayMode } from '@/const/enum'
|
||||||
|
|
||||||
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
import type {
|
import type {
|
||||||
FinalMusic,
|
FinalMusic,
|
||||||
Music163ListResponse,
|
Music163ListResponse,
|
||||||
Music163URLResponse
|
Music163URLResponse
|
||||||
} from '@/types/music'
|
} from '@/types/music'
|
||||||
|
|
||||||
import WebUIManager from '@/controllers/webui_manager'
|
|
||||||
/**
|
/**
|
||||||
* 获取网易云音乐歌单
|
* 获取网易云音乐歌单
|
||||||
* @param id 歌单id
|
* @param id 歌单id
|
||||||
* @returns 歌单信息
|
* @returns 歌单信息
|
||||||
*/
|
*/
|
||||||
export const get163MusicList = async (id: string) => {
|
export const get163MusicList = async (id: string) => {
|
||||||
let res = await WebUIManager.proxy<Music163ListResponse>('https://wavesgame.top/playlist/track/all?id=' + id);
|
let res = await WebUIManager.proxy<Music163ListResponse>(
|
||||||
|
'https://wavesgame.top/playlist/track/all?id=' + id
|
||||||
|
)
|
||||||
// const res = await request.get<Music163ListResponse>(
|
// const res = await request.get<Music163ListResponse>(
|
||||||
// `https://wavesgame.top/playlist/track/all?id=${id}`
|
// `https://wavesgame.top/playlist/track/all?id=${id}`
|
||||||
// )
|
// )
|
||||||
@@ -71,7 +73,7 @@ export const get163MusicListSongs = async (id: string) => {
|
|||||||
if (songURL) {
|
if (songURL) {
|
||||||
finalMusic.push({
|
finalMusic.push({
|
||||||
id: song.id,
|
id: song.id,
|
||||||
url: songURL,
|
url: songURL.replace(/http:\/\//, '//').replace(/https:\/\//, '//'),
|
||||||
title: song.name,
|
title: song.name,
|
||||||
artist: song.ar.map((p) => p.name).join('/'),
|
artist: song.ar.map((p) => p.name).join('/'),
|
||||||
cover: song.al.picUrl
|
cover: song.al.picUrl
|
||||||
|
141
napcat.webui/src/utils/theme.ts
Normal file
141
napcat.webui/src/utils/theme.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { request } from './request'
|
||||||
|
|
||||||
|
const style = document.createElement('style')
|
||||||
|
document.head.appendChild(style)
|
||||||
|
|
||||||
|
export function loadTheme() {
|
||||||
|
request('/files/theme.css?_t=' + Date.now())
|
||||||
|
.then((res) => res.data)
|
||||||
|
.then((css) => {
|
||||||
|
style.innerHTML = css
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error('Failed to load theme.css')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorKeys = [
|
||||||
|
'--heroui-background',
|
||||||
|
|
||||||
|
'--heroui-foreground-50',
|
||||||
|
'--heroui-foreground-100',
|
||||||
|
'--heroui-foreground-200',
|
||||||
|
'--heroui-foreground-300',
|
||||||
|
'--heroui-foreground-400',
|
||||||
|
'--heroui-foreground-500',
|
||||||
|
'--heroui-foreground-600',
|
||||||
|
'--heroui-foreground-700',
|
||||||
|
'--heroui-foreground-800',
|
||||||
|
'--heroui-foreground-900',
|
||||||
|
'--heroui-foreground',
|
||||||
|
|
||||||
|
'--heroui-content1',
|
||||||
|
'--heroui-content1-foreground',
|
||||||
|
'--heroui-content2',
|
||||||
|
'--heroui-content2-foreground',
|
||||||
|
'--heroui-content3',
|
||||||
|
'--heroui-content3-foreground',
|
||||||
|
'--heroui-content4',
|
||||||
|
'--heroui-content4-foreground',
|
||||||
|
|
||||||
|
'--heroui-default-50',
|
||||||
|
'--heroui-default-100',
|
||||||
|
'--heroui-default-200',
|
||||||
|
'--heroui-default-300',
|
||||||
|
'--heroui-default-400',
|
||||||
|
'--heroui-default-500',
|
||||||
|
'--heroui-default-600',
|
||||||
|
'--heroui-default-700',
|
||||||
|
'--heroui-default-800',
|
||||||
|
'--heroui-default-900',
|
||||||
|
'--heroui-default-foreground',
|
||||||
|
'--heroui-default',
|
||||||
|
|
||||||
|
'--heroui-danger-50',
|
||||||
|
'--heroui-danger-100',
|
||||||
|
'--heroui-danger-200',
|
||||||
|
'--heroui-danger-300',
|
||||||
|
'--heroui-danger-400',
|
||||||
|
'--heroui-danger-500',
|
||||||
|
'--heroui-danger-600',
|
||||||
|
'--heroui-danger-700',
|
||||||
|
'--heroui-danger-800',
|
||||||
|
'--heroui-danger-900',
|
||||||
|
'--heroui-danger-foreground',
|
||||||
|
'--heroui-danger',
|
||||||
|
|
||||||
|
'--heroui-primary-50',
|
||||||
|
'--heroui-primary-100',
|
||||||
|
'--heroui-primary-200',
|
||||||
|
'--heroui-primary-300',
|
||||||
|
'--heroui-primary-400',
|
||||||
|
'--heroui-primary-500',
|
||||||
|
'--heroui-primary-600',
|
||||||
|
'--heroui-primary-700',
|
||||||
|
'--heroui-primary-800',
|
||||||
|
'--heroui-primary-900',
|
||||||
|
'--heroui-primary-foreground',
|
||||||
|
'--heroui-primary',
|
||||||
|
|
||||||
|
'--heroui-secondary-50',
|
||||||
|
'--heroui-secondary-100',
|
||||||
|
'--heroui-secondary-200',
|
||||||
|
'--heroui-secondary-300',
|
||||||
|
'--heroui-secondary-400',
|
||||||
|
'--heroui-secondary-500',
|
||||||
|
'--heroui-secondary-600',
|
||||||
|
'--heroui-secondary-700',
|
||||||
|
'--heroui-secondary-800',
|
||||||
|
'--heroui-secondary-900',
|
||||||
|
'--heroui-secondary-foreground',
|
||||||
|
'--heroui-secondary',
|
||||||
|
|
||||||
|
'--heroui-success-50',
|
||||||
|
'--heroui-success-100',
|
||||||
|
'--heroui-success-200',
|
||||||
|
'--heroui-success-300',
|
||||||
|
'--heroui-success-400',
|
||||||
|
'--heroui-success-500',
|
||||||
|
'--heroui-success-600',
|
||||||
|
'--heroui-success-700',
|
||||||
|
'--heroui-success-800',
|
||||||
|
'--heroui-success-900',
|
||||||
|
'--heroui-success-foreground',
|
||||||
|
'--heroui-success',
|
||||||
|
|
||||||
|
'--heroui-warning-50',
|
||||||
|
'--heroui-warning-100',
|
||||||
|
'--heroui-warning-200',
|
||||||
|
'--heroui-warning-300',
|
||||||
|
'--heroui-warning-400',
|
||||||
|
'--heroui-warning-500',
|
||||||
|
'--heroui-warning-600',
|
||||||
|
'--heroui-warning-700',
|
||||||
|
'--heroui-warning-800',
|
||||||
|
'--heroui-warning-900',
|
||||||
|
'--heroui-warning-foreground',
|
||||||
|
'--heroui-warning',
|
||||||
|
|
||||||
|
'--heroui-focus',
|
||||||
|
'--heroui-overlay',
|
||||||
|
'--heroui-divider',
|
||||||
|
'--heroui-code-background',
|
||||||
|
'--heroui-strong',
|
||||||
|
'--heroui-code-mdx'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const generateTheme = (theme: ThemeConfig, validField?: string) => {
|
||||||
|
let css = `:root ${validField ? `.${validField}` : ''}, .light ${validField ? `.${validField}` : ''}, [data-theme="light"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.light) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.light[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
css += `.dark ${validField ? `.${validField}` : ''}, [data-theme="dark"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.dark) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.dark[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
return css
|
||||||
|
}
|
@@ -9,9 +9,84 @@ export default {
|
|||||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||||
],
|
],
|
||||||
|
safelist: [
|
||||||
|
{
|
||||||
|
pattern:
|
||||||
|
/bg-(primary|secondary|success|danger|warning|default)-(50|100|200|300|400|500|600|700|800|900)/
|
||||||
|
}
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
plugins: [heroui()]
|
plugins: [
|
||||||
|
heroui({
|
||||||
|
themes: {
|
||||||
|
light: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
DEFAULT: '#f31260',
|
||||||
|
foreground: '#fff',
|
||||||
|
50: '#fee7ef',
|
||||||
|
100: '#fdd0df',
|
||||||
|
200: '#faa0bf',
|
||||||
|
300: '#f871a0',
|
||||||
|
400: '#f54180',
|
||||||
|
500: '#f31260',
|
||||||
|
600: '#c20e4d',
|
||||||
|
700: '#920b3a',
|
||||||
|
800: '#610726',
|
||||||
|
900: '#310413'
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
DEFAULT: '#DB3694',
|
||||||
|
foreground: '#fff',
|
||||||
|
50: '#FEEAF6',
|
||||||
|
100: '#FDD7DD',
|
||||||
|
200: '#FBAFC4',
|
||||||
|
300: '#F485AE',
|
||||||
|
400: '#E965A3',
|
||||||
|
500: '#DB3694',
|
||||||
|
600: '#BC278B',
|
||||||
|
700: '#9D1B7F',
|
||||||
|
800: '#7F1170',
|
||||||
|
900: '#690A66'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
DEFAULT: '#f31260',
|
||||||
|
foreground: '#fff',
|
||||||
|
50: '#310413',
|
||||||
|
100: '#610726',
|
||||||
|
200: '#920b3a',
|
||||||
|
300: '#c20e4d',
|
||||||
|
400: '#f31260',
|
||||||
|
500: '#f54180',
|
||||||
|
600: '#f871a0',
|
||||||
|
700: '#faa0bf',
|
||||||
|
800: '#fdd0df',
|
||||||
|
900: '#fee7ef'
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
DEFAULT: '#DB3694',
|
||||||
|
foreground: '#fff',
|
||||||
|
50: '#690A66',
|
||||||
|
100: '#7F1170',
|
||||||
|
200: '#9D1B7F',
|
||||||
|
300: '#BC278B',
|
||||||
|
400: '#DB3694',
|
||||||
|
500: '#E965A3',
|
||||||
|
600: '#F485AE',
|
||||||
|
700: '#FBAFC4',
|
||||||
|
800: '#FDD7DD',
|
||||||
|
900: '#FEEAF6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
ws: true,
|
ws: true,
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
'/api': backendDebugUrl
|
'/api': backendDebugUrl,
|
||||||
|
'/files': backendDebugUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
26
package.json
26
package.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.5.1",
|
"version": "4.7.45",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
"@eslint/compat": "^1.2.2",
|
"@eslint/compat": "^1.2.2",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@ffmpeg.wasm/main": "^0.13.1",
|
|
||||||
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.12.0-beta.5",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@napneko/nap-proto-core": "^0.0.4",
|
"@napneko/nap-proto-core": "^0.0.4",
|
||||||
@@ -32,7 +31,11 @@
|
|||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/multer": "^1.4.12",
|
"@types/multer": "^1.4.12",
|
||||||
"@types/node": "^22.0.1",
|
"@types/node": "^22.0.1",
|
||||||
|
"@types/on-finished": "^2.3.4",
|
||||||
"@types/qrcode-terminal": "^0.12.2",
|
"@types/qrcode-terminal": "^0.12.2",
|
||||||
|
"@types/react-color": "^3.0.13",
|
||||||
|
"@types/type-is": "^1.6.7",
|
||||||
|
"@types/wordcloud": "^1.2.2",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
@@ -40,30 +43,33 @@
|
|||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"commander": "^13.0.0",
|
"commander": "^13.0.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"esbuild": "0.24.0",
|
"esbuild": "0.25.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^4.0.0",
|
||||||
"eslint-plugin-import": "^2.29.1",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"express-rate-limit": "^7.5.0",
|
"express-rate-limit": "^7.5.0",
|
||||||
"fast-xml-parser": "^4.3.6",
|
"fast-xml-parser": "^4.3.6",
|
||||||
"file-type": "^20.0.0",
|
"file-type": "^20.0.0",
|
||||||
"globals": "^15.12.0",
|
"globals": "^16.0.0",
|
||||||
"image-size": "^1.1.1",
|
"image-size": "^1.1.1",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"typescript-eslint": "^8.13.0",
|
"typescript-eslint": "^8.13.0",
|
||||||
"vite": "^6.0.1",
|
"vite": "^6.0.1",
|
||||||
"vite-plugin-cp": "^4.0.8",
|
"vite-plugin-cp": "^6.0.0",
|
||||||
"vite-tsconfig-paths": "^5.1.0",
|
"vite-tsconfig-paths": "^5.1.0",
|
||||||
"winston": "^3.17.0"
|
"napcat.protobuf": "^1.1.4",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
"compressing": "^1.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ffmpeg.wasm/core-mt": "^0.13.2",
|
"@napi-rs/canvas": "^0.1.67",
|
||||||
"compressing": "^1.10.1",
|
"@node-rs/jieba": "^2.0.1",
|
||||||
"express": "^5.0.0",
|
"express": "^5.0.0",
|
||||||
"piscina": "^4.7.0",
|
"napcat.protobuf": "^1.1.2",
|
||||||
"silk-wasm": "^3.6.1",
|
"silk-wasm": "^3.6.1",
|
||||||
|
"wordcloud": "^1.2.3",
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,20 @@
|
|||||||
import { encode } from 'silk-wasm';
|
import { encode } from 'silk-wasm';
|
||||||
|
import { parentPort } from 'worker_threads';
|
||||||
|
|
||||||
export interface EncodeArgs {
|
export interface EncodeArgs {
|
||||||
input: ArrayBufferView | ArrayBuffer
|
input: ArrayBufferView | ArrayBuffer
|
||||||
sampleRate: number
|
sampleRate: number
|
||||||
}
|
}
|
||||||
export default async ({ input, sampleRate }: EncodeArgs) => {
|
export function recvTask<T>(cb: (taskData: T) => Promise<unknown>) {
|
||||||
|
parentPort?.on('message', async (taskData: T) => {
|
||||||
|
try {
|
||||||
|
let ret = await cb(taskData);
|
||||||
|
parentPort?.postMessage(ret);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
parentPort?.postMessage({ error: (error as Error).message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
recvTask<EncodeArgs>(async ({ input, sampleRate }) => {
|
||||||
return await encode(input, sampleRate);
|
return await encode(input, sampleRate);
|
||||||
};
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user