Compare commits
1661
Commits
main
..
gzy_browser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a723a9ca02 | ||
|
|
49d9ced04f | ||
|
|
c2153ec238 | ||
|
|
6b724bd00d | ||
|
|
65d6b61d5d | ||
|
|
1c891df8fb | ||
|
|
6d326a21cd | ||
|
|
f5bf7897b9 | ||
|
|
fbd8dec48f | ||
|
|
dd0ee282df | ||
|
|
553f72604a | ||
|
|
9455160101 | ||
|
|
519e4c0c01 | ||
|
|
dbf62e5889 | ||
|
|
aeb9a55497 | ||
|
|
051e66f7d8 | ||
|
|
1706a24182 | ||
|
|
38857c425b | ||
|
|
99f1ca6029 | ||
|
|
a020458eeb | ||
|
|
8e49fd2e95 | ||
|
|
57b6a89f20 | ||
|
|
90e94994f5 | ||
|
|
8e0d755b57 | ||
|
|
40542db400 | ||
|
|
ac92f29db1 | ||
|
|
f0ddade8a7 | ||
|
|
fbf9031e25 | ||
|
|
2254841822 | ||
|
|
74fc849241 | ||
|
|
dd560ceb46 | ||
|
|
a4f95a4fa9 | ||
|
|
b92a26cdd4 | ||
|
|
7066798ab0 | ||
|
|
222595d6c4 | ||
|
|
3384f2e51c | ||
|
|
ff80cc110d | ||
|
|
2ab3ab8444 | ||
|
|
f7e6ed65bd | ||
|
|
4964967295 | ||
|
|
49c01cb72b | ||
|
|
9afb9750e5 | ||
|
|
f18994fd38 | ||
|
|
7c15ca6710 | ||
|
|
31cfa93408 | ||
|
|
b164e70541 | ||
|
|
17cf849361 | ||
|
|
4ac7bc21cd | ||
|
|
832e180c52 | ||
|
|
875001a8c0 | ||
|
|
36653c53a6 | ||
|
|
37517c2778 | ||
|
|
3715af3138 | ||
|
|
973500a0f0 | ||
|
|
d9a94245fc | ||
|
|
1764d0d5f2 | ||
|
|
84370e2072 | ||
|
|
0289a76fce | ||
|
|
32e509b2b3 | ||
|
|
440b490b0d | ||
|
|
52b9cbf707 | ||
|
|
1b43205c1b | ||
|
|
11326eeb1c | ||
|
|
485a8fa9d9 | ||
|
|
4ce2f3a502 | ||
|
|
6e9e89a6cb | ||
|
|
90d29ed232 | ||
|
|
d9de41dd67 | ||
|
|
f1245da3e4 | ||
|
|
38c3de39f0 | ||
|
|
33639e5b89 | ||
|
|
676b00afb6 | ||
|
|
0980eea975 | ||
|
|
eba3008482 | ||
|
|
9b904ac3dd | ||
|
|
704438b3d2 | ||
|
|
d35d075669 | ||
|
|
910cee9e67 | ||
|
|
21f3787b8b | ||
|
|
33b719d460 | ||
|
|
c6fcf51e0c | ||
|
|
62a3faa7c8 | ||
|
|
d87dfec2de | ||
|
|
5c0864bf0a | ||
|
|
8ce3a62826 | ||
|
|
8c3d6a02fc | ||
|
|
e053b36b6e | ||
|
|
9f29ecae9e | ||
|
|
4b86106a24 | ||
|
|
043b0900d5 | ||
|
|
0c5e2e8cb0 | ||
|
|
40d06b781a | ||
|
|
c5328e6a35 | ||
|
|
babc5e95c6 | ||
|
|
1736227723 | ||
|
|
30943159bf | ||
|
|
007ed5a7de | ||
|
|
ebc727e4fd | ||
|
|
bc49df4561 | ||
|
|
cdb8aa5f81 | ||
|
|
e818c24769 | ||
|
|
49babb6d25 | ||
|
|
3785345353 | ||
|
|
daaa0eab26 | ||
|
|
541bc3280f | ||
|
|
b81d200eca | ||
|
|
51afebb394 | ||
|
|
a5a9c952e8 | ||
|
|
4c9f75f31e | ||
|
|
7c34e18ad6 | ||
|
|
11f928bc95 | ||
|
|
e881c242fe | ||
|
|
e860357563 | ||
|
|
c69fde22bd | ||
|
|
0ce060693b | ||
|
|
be4851555d | ||
|
|
3afa76894f | ||
|
|
acf538ac01 | ||
|
|
5e1765d889 | ||
|
|
30799f17be | ||
|
|
dcfaa199bf | ||
|
|
33b0af333c | ||
|
|
172250e6b5 | ||
|
|
15a46b903a | ||
|
|
8af4fed89d | ||
|
|
64a5305bbf | ||
|
|
819e537aa3 | ||
|
|
68c4896088 | ||
|
|
771957429a | ||
|
|
5bf08c83a6 | ||
|
|
3ab2c9b282 | ||
|
|
94cf37a2ae | ||
|
|
47faa9bcdf | ||
|
|
5901246030 | ||
|
|
44e0b7596b | ||
|
|
178572bc7f | ||
|
|
70da7c5649 | ||
|
|
5c586652af | ||
|
|
067f602732 | ||
|
|
96bfdacee5 | ||
|
|
500dab29ab | ||
|
|
4d688ddbd8 | ||
|
|
8193cc53d5 | ||
|
|
a28ad2140e | ||
|
|
7b37415604 | ||
|
|
c4d244881f | ||
|
|
75963c9ad9 | ||
|
|
11e54550ed | ||
|
|
4f536d9b39 | ||
|
|
b527bc94bb | ||
|
|
54fd0e7316 | ||
|
|
6dbaf40f99 | ||
|
|
3aeb6ac5ce | ||
|
|
5a4881053d | ||
|
|
cebc44ff82 | ||
|
|
56044d9cb7 | ||
|
|
4809ee7db3 | ||
|
|
a4a1e73b8e | ||
|
|
5123b7bdd7 | ||
|
|
e189349eeb | ||
|
|
f2e81d8875 | ||
|
|
e5e0f3a7c3 | ||
|
|
c4fd9dd7da | ||
|
|
b4d076a2d4 | ||
|
|
bc82072710 | ||
|
|
bf023d746a | ||
|
|
fb5024ea82 | ||
|
|
c9c2dc7ea8 | ||
|
|
2a869f4ff6 | ||
|
|
af509eb670 | ||
|
|
783e698b6e | ||
|
|
ba912acf72 | ||
|
|
e0c09f6704 | ||
|
|
17a12bb7b6 | ||
|
|
f8757d1a43 | ||
|
|
69b95f0d6d | ||
|
|
ca32a22c0d | ||
|
|
3eb307c5c5 | ||
|
|
49af497085 | ||
|
|
8b732b43ee | ||
|
|
69e455274a | ||
|
|
e939dc4548 | ||
|
|
1ffead7ee2 | ||
|
|
a0a75a9964 | ||
|
|
37964d1f95 | ||
|
|
a9d66536c3 | ||
|
|
a968c3b5cd | ||
|
|
e52261fc3d | ||
|
|
4450f342d8 | ||
|
|
213dd841b5 | ||
|
|
816c46d447 | ||
|
|
db857ddd4d | ||
|
|
9be0c6555f | ||
|
|
e475c37f1d | ||
|
|
d86c9d48a6 | ||
|
|
008c9ede6e | ||
|
|
151ecceb85 | ||
|
|
1555d5344b | ||
|
|
bd8ec41914 | ||
|
|
1e21e559fb | ||
|
|
e496221dc3 | ||
|
|
5bf3955b26 | ||
|
|
1ab3329f6d | ||
|
|
e70bb4aa6f | ||
|
|
b22d0200c3 | ||
|
|
fb73ae0471 | ||
|
|
75aad4eef6 | ||
|
|
cec2996861 | ||
|
|
239b798c5d | ||
|
|
f1e53ef5c0 | ||
|
|
a368037e81 | ||
|
|
59f2f56124 | ||
|
|
3779bb69ef | ||
|
|
25153dbd84 | ||
|
|
91ba47bf47 | ||
|
|
28bf1ac7b2 | ||
|
|
45df500a9c | ||
|
|
f32de510fe | ||
|
|
21722b9f5a | ||
|
|
f49def09ea | ||
|
|
4948a80c56 | ||
|
|
7d249a81ce | ||
|
|
1d07025f74 | ||
|
|
91c5d43c48 | ||
|
|
b60d9ed09e | ||
|
|
56740ecf56 | ||
|
|
9f788e37de | ||
|
|
0634337858 | ||
|
|
08ce3dda73 | ||
|
|
c8f69781e4 | ||
|
|
13bfa719ee | ||
|
|
359bd28f2c | ||
|
|
7bd032ec77 | ||
|
|
790267ac43 | ||
|
|
d9844e0180 | ||
|
|
6ade3b69b9 | ||
|
|
ae184f434d | ||
|
|
055523bf33 | ||
|
|
866bc0f8d8 | ||
|
|
1bc203b5cd | ||
|
|
1cd99a5a90 | ||
|
|
2ca8a23102 | ||
|
|
97ce19973d | ||
|
|
783774d3f3 | ||
|
|
85e119db08 | ||
|
|
5436719bfb | ||
|
|
e571caaf2c | ||
|
|
5d2997b6a0 | ||
|
|
f8cb7be18c | ||
|
|
b93b82fcaf | ||
|
|
d0b4832ed7 | ||
|
|
4ffd5a3c94 | ||
|
|
2d6992d656 | ||
|
|
bb7291a97c | ||
|
|
a699545c4c | ||
|
|
2c331aa744 | ||
|
|
82dd9fb08f | ||
|
|
b97aa77adc | ||
|
|
d7d101814f | ||
|
|
871d98e6f2 | ||
|
|
3cd093a8a2 | ||
|
|
4a59da7ff0 | ||
|
|
d7ad0624b7 | ||
|
|
27e6b0b23e | ||
|
|
55b5c2cc9f | ||
|
|
d5d5199f6d | ||
|
|
0bd0ac41cd | ||
|
|
b1c396717b | ||
|
|
388a9133fc | ||
|
|
221e947488 | ||
|
|
a1c63a1fe5 | ||
|
|
db2f21191c | ||
|
|
195ef3ee40 | ||
|
|
f3511e5cd0 | ||
|
|
96947d7636 | ||
|
|
034a379ecd | ||
|
|
da856f2b58 | ||
|
|
845c87e3ee | ||
|
|
7d9c0894f8 | ||
|
|
ae30a20739 | ||
|
|
7e7d3c9846 | ||
|
|
daff70c34f | ||
|
|
94188db9ea | ||
|
|
b12497898a | ||
|
|
3e51208024 | ||
|
|
36a3851233 | ||
|
|
54190d7a83 | ||
|
|
2215ec3c49 | ||
|
|
d21dbeb214 | ||
|
|
ba0c32dcee | ||
|
|
f5efe9b041 | ||
|
|
a4a44e1154 | ||
|
|
cef08638af | ||
|
|
025e43f4c5 | ||
|
|
fa04ab8b5a | ||
|
|
f3e5a3bb35 | ||
|
|
0547fb1715 | ||
|
|
bb6b9bd84b | ||
|
|
f485d4fff3 | ||
|
|
4bfaeb728b | ||
|
|
2b0ba2a68b | ||
|
|
c8cdd1d0b2 | ||
|
|
8fbfd35296 | ||
|
|
dfb745a5e4 | ||
|
|
c23350f17b | ||
|
|
124eb04a6b | ||
|
|
b4206d6421 | ||
|
|
537e2e4d06 | ||
|
|
9268c73776 | ||
|
|
6378cd33bd | ||
|
|
8c6d08d6bf | ||
|
|
29082b349d | ||
|
|
7d8656bfde | ||
|
|
45ce14e15a | ||
|
|
8947223255 | ||
|
|
5a420189b1 | ||
|
|
17650afe96 | ||
|
|
80fce34c1a | ||
|
|
ae31c50424 | ||
|
|
794aab1007 | ||
|
|
be06cac223 | ||
|
|
b0db8cc3d8 | ||
|
|
0dab5a8121 | ||
|
|
c012146c18 | ||
|
|
6851c96a45 | ||
|
|
f75f4f9db1 | ||
|
|
f22a6a9957 | ||
|
|
210cb5117f | ||
|
|
0c663f5f7c | ||
|
|
75e8a90ac7 | ||
|
|
d8af6c8f48 | ||
|
|
1ab4216ccb | ||
|
|
d39b58255d | ||
|
|
cd2ca406a5 | ||
|
|
637be00946 | ||
|
|
0dd93421c4 | ||
|
|
e27321cb23 | ||
|
|
6157b42f5a | ||
|
|
be4b180d92 | ||
|
|
e9e4c181b9 | ||
|
|
eb61b332a2 | ||
|
|
4fce6f9104 | ||
|
|
bcd656fb49 | ||
|
|
2d3033434b | ||
|
|
d522498cc9 | ||
|
|
65449ffd71 | ||
|
|
006c5bc4c1 | ||
|
|
a046d19ce1 | ||
|
|
aca089e8ef | ||
|
|
24eb629773 | ||
|
|
1847eb9d6a | ||
|
|
44d7304f8e | ||
|
|
5de74e90dc | ||
|
|
6fdca41a1d | ||
|
|
c978a25b7f | ||
|
|
c11307aa67 | ||
|
|
199269712f | ||
|
|
9572b66ecb | ||
|
|
45315f18d2 | ||
|
|
e889cc25cf | ||
|
|
8385d1913a | ||
|
|
9f462beec5 | ||
|
|
4f0c75e76e | ||
|
|
2097587b42 | ||
|
|
53af352ce8 | ||
|
|
0089af94d3 | ||
|
|
26ab6f663c | ||
|
|
bce0abba6e | ||
|
|
c732fd75e4 | ||
|
|
89232d7521 | ||
|
|
9ef8b1990e | ||
|
|
26f8010a57 | ||
|
|
abf97cdc78 | ||
|
|
4d10590511 | ||
|
|
3d60651491 | ||
|
|
a400989f64 | ||
|
|
2a1f50f726 | ||
|
|
158d157af4 | ||
|
|
1ed004fbb1 | ||
|
|
38f98cdf5c | ||
|
|
a417124c21 | ||
|
|
f3f11461de | ||
|
|
2e79cee24a | ||
|
|
1fd997303e | ||
|
|
cc017a22c7 | ||
|
|
5f01e8dd03 | ||
|
|
ce69905950 | ||
|
|
71e7b9dd63 | ||
|
|
58357547c9 | ||
|
|
fe7c6cb8f7 | ||
|
|
dc5b83e8cd | ||
|
|
27fffbec61 | ||
|
|
d35d121261 | ||
|
|
368673c3b4 | ||
|
|
854de9b43c | ||
|
|
0aa55eb333 | ||
|
|
1d1ab384ef | ||
|
|
864224e07c | ||
|
|
04b38a4a25 | ||
|
|
f8b40353ef | ||
|
|
893f1e6933 | ||
|
|
04e6d0aa00 | ||
|
|
1d6659f9e7 | ||
|
|
a4a67c1a00 | ||
|
|
8f773d3f1b | ||
|
|
c0305c9269 | ||
|
|
6989dd45f1 | ||
|
|
d8b43e7579 | ||
|
|
4f68235c94 | ||
|
|
c1143564ef | ||
|
|
ea2ea9783e | ||
|
|
e3e93712b6 | ||
|
|
afb78a6943 | ||
|
|
1a127ef602 | ||
|
|
acc29c8121 | ||
|
|
ca31490241 | ||
|
|
6c42f86e30 | ||
|
|
5b818836d7 | ||
|
|
3ea8e73389 | ||
|
|
c117e296cb | ||
|
|
42f55251f2 | ||
|
|
304e1b637a | ||
|
|
49173d9c28 | ||
|
|
8678ceb422 | ||
|
|
31cd19aabb | ||
|
|
60029ae3dc | ||
|
|
4f62703477 | ||
|
|
c0af23aefe | ||
|
|
6a6195f883 | ||
|
|
1a87077642 | ||
|
|
e9c8d1cea2 | ||
|
|
d2c76100f2 | ||
|
|
6aff165d09 | ||
|
|
5ecf28e60c | ||
|
|
fb103b6ad9 | ||
|
|
71a865dc0c | ||
|
|
8ed54448c2 | ||
|
|
5a2441cc9f | ||
|
|
089d3e171f | ||
|
|
ed71df1b60 | ||
|
|
85cd2c888e | ||
|
|
6673121f3f | ||
|
|
4898ea0a85 | ||
|
|
7d9a0637ca | ||
|
|
ba885ca201 | ||
|
|
ec60b77a71 | ||
|
|
ae3788a87d | ||
|
|
3e68260afa | ||
|
|
f551c7af9b | ||
|
|
b1ef4ccb1a | ||
|
|
e3631a9fd6 | ||
|
|
8241f8dd6b | ||
|
|
3d2cc9e5cd | ||
|
|
e263d86fc3 | ||
|
|
151384b26d | ||
|
|
030ede6887 | ||
|
|
e6ca2bf64f | ||
|
|
47cff9e2f9 | ||
|
|
6742346eb4 | ||
|
|
b5e682cfd2 | ||
|
|
2193c4484b | ||
|
|
4fc3d7dae2 | ||
|
|
6f0f9fb1fc | ||
|
|
df9c59e21c | ||
|
|
6131eda219 | ||
|
|
14082b3e7b | ||
|
|
00181ebde8 | ||
|
|
320f2ef814 | ||
|
|
8b022bffc5 | ||
|
|
5fddb08e04 | ||
|
|
0fedb6ffd8 | ||
|
|
498e3c6a84 | ||
|
|
85aa242fd5 | ||
|
|
6f94a40303 | ||
|
|
34893007a7 | ||
|
|
053d3e300e | ||
|
|
a1fbfaf864 | ||
|
|
b8c78ba06e | ||
|
|
cd33aaeded | ||
|
|
5aa66fc07d | ||
|
|
23d170cde1 | ||
|
|
d2be3b206d | ||
|
|
1a2ef0a537 | ||
|
|
ce3a5b6bf9 | ||
|
|
91a6183757 | ||
|
|
076cdc746e | ||
|
|
1c2e524b28 | ||
|
|
42ce20b4df | ||
|
|
1cbc0e2dd6 | ||
|
|
fc632f26e9 | ||
|
|
adabfa2fe3 | ||
|
|
ec56fa671b | ||
|
|
f144a59c97 | ||
|
|
dd942cdd60 | ||
|
|
fbec222eb4 | ||
|
|
7c55a577ad | ||
|
|
57ffe1a706 | ||
|
|
06b169d7a1 | ||
|
|
7487395e44 | ||
|
|
26557b30a0 | ||
|
|
f8228847ab | ||
|
|
6b480fdbe9 | ||
|
|
0a456e9146 | ||
|
|
b59cbd51d9 | ||
|
|
23743ee193 | ||
|
|
7748661963 | ||
|
|
e28277b21b | ||
|
|
32476c7ad4 | ||
|
|
ab305b3514 | ||
|
|
16ba5deb0e | ||
|
|
2b6424b3ff | ||
|
|
79f1a004a0 | ||
|
|
b89933e60a | ||
|
|
548cfd1719 | ||
|
|
7d32b41e90 | ||
|
|
6e3896fed3 | ||
|
|
545ec178b8 | ||
|
|
6dd1f9c017 | ||
|
|
6b963fd918 | ||
|
|
f13a1bb7fe | ||
|
|
02115bccfe | ||
|
|
48744441ba | ||
|
|
5ae6261e82 | ||
|
|
a76e28e018 | ||
|
|
f63a1faf3b | ||
|
|
3d75040297 | ||
|
|
8447790c47 | ||
|
|
b5f11ad790 | ||
|
|
d383b0e9dd | ||
|
|
ae2706dad3 | ||
|
|
fba417918f | ||
|
|
b513a0d56c | ||
|
|
b1b1ff98f5 | ||
|
|
3ab674e438 | ||
|
|
9a1b674950 | ||
|
|
9278fd772f | ||
|
|
a8c06ca7dc | ||
|
|
762d3620c4 | ||
|
|
4a403c95c5 | ||
|
|
8ca16c74bc | ||
|
|
60626c384a | ||
|
|
551c748aa8 | ||
|
|
03c155f8a7 | ||
|
|
3b165d9ea5 | ||
|
|
a85d2480b4 | ||
|
|
a9f01bec79 | ||
|
|
e906a911b2 | ||
|
|
8cc2650e98 | ||
|
|
593daa91ec | ||
|
|
a6dd0fdd85 | ||
|
|
30cf0fed91 | ||
|
|
1f2a1aaa76 | ||
|
|
c1c23d9136 | ||
|
|
4b2a93a542 | ||
|
|
23e6685977 | ||
|
|
bc9936bb2b | ||
|
|
ef615bbfb6 | ||
|
|
d8f32690a3 | ||
|
|
31f9838095 | ||
|
|
13ef1942f2 | ||
|
|
2b58fe6c8b | ||
|
|
81ca13280b | ||
|
|
bb7283dcd9 | ||
|
|
0bd4524334 | ||
|
|
ce78cbf936 | ||
|
|
6aaa4c841e | ||
|
|
179a3f2b2f | ||
|
|
678035afed | ||
|
|
758a8fda09 | ||
|
|
3a315798e9 | ||
|
|
bd5623ebc5 | ||
|
|
6a8f118ad0 | ||
|
|
3758122cf7 | ||
|
|
03b6fb724c | ||
|
|
3aa629f0df | ||
|
|
e5ca717783 | ||
|
|
85cca36ddb | ||
|
|
29cc004b69 | ||
|
|
19028ba627 | ||
|
|
77aa895a70 | ||
|
|
f762a76ecf | ||
|
|
095469b8a9 | ||
|
|
16afa9bcbb | ||
|
|
b04b830039 | ||
|
|
a8da6c2632 | ||
|
|
62549ab976 | ||
|
|
32772b7448 | ||
|
|
ecb79ce49c | ||
|
|
500a562bb6 | ||
|
|
b5744a1378 | ||
|
|
b8b71523b5 | ||
|
|
cc06d3f4d2 | ||
|
|
0890b3c375 | ||
|
|
ed887f762e | ||
|
|
bb05f7b1de | ||
|
|
d3880f51b3 | ||
|
|
a2fcd85746 | ||
|
|
47bf0c2a1b | ||
|
|
85dc5466c5 | ||
|
|
c97d88d33f | ||
|
|
60cc6665ce | ||
|
|
d82973c8c8 | ||
|
|
fca39eeaee | ||
|
|
e6a41dbc97 | ||
|
|
6746fe4a78 | ||
|
|
25e4e3dbc9 | ||
|
|
af8db1d24c | ||
|
|
bb2e896fba | ||
|
|
0dca637645 | ||
|
|
54fff09e74 | ||
|
|
39d7dab3e2 | ||
|
|
fc7576fea3 | ||
|
|
6c47da82c4 | ||
|
|
392394b4c9 | ||
|
|
a2bb8c80a2 | ||
|
|
ba0ec20532 | ||
|
|
138a5adb1c | ||
|
|
a2d767e2db | ||
|
|
dd631d57d4 | ||
|
|
a3c8c50212 | ||
|
|
ef1a12ab74 | ||
|
|
1c8b5b23a1 | ||
|
|
8d0759123a | ||
|
|
318f6c0c0c | ||
|
|
d8102e8db8 | ||
|
|
5b610fd807 | ||
|
|
7afdf5b363 | ||
|
|
6c5a2cffe3 | ||
|
|
0bad0eb306 | ||
|
|
5f39879e4d | ||
|
|
342fa8265a | ||
|
|
1fa2338ac3 | ||
|
|
56e17f826c | ||
|
|
bc5c2fa3d4 | ||
|
|
44cb118f95 | ||
|
|
1ece2c5a4f | ||
|
|
2fce5e61c9 | ||
|
|
d5d0494928 | ||
|
|
b380575f16 | ||
|
|
c21a1127f8 | ||
|
|
e621ce88e6 | ||
|
|
761491ec41 | ||
|
|
a35ed77ff0 | ||
|
|
245ac969a7 | ||
|
|
ab21911696 | ||
|
|
380407ae2f | ||
|
|
c0e89044c7 | ||
|
|
4f0494a97a | ||
|
|
baf3fadce6 | ||
|
|
ecf068a61c | ||
|
|
ecaaec939d | ||
|
|
e3c1adb71a | ||
|
|
ce165ece69 | ||
|
|
63bddfdea7 | ||
|
|
208a02a912 | ||
|
|
f750b7f04d | ||
|
|
ba5aeefe98 | ||
|
|
d82b5ed884 | ||
|
|
12f50123dc | ||
|
|
4511c0dee7 | ||
|
|
f6809349d2 | ||
|
|
97da7dc87f | ||
|
|
45049975c2 | ||
|
|
0e4caa5a66 | ||
|
|
f39cffcb9a | ||
|
|
5182ea5d11 | ||
|
|
5ebbb82d66 | ||
|
|
e6662cf673 | ||
|
|
fe0dc8d1a5 | ||
|
|
8794c51f88 | ||
|
|
8d1669adad | ||
|
|
da3d38013e | ||
|
|
b2ef4ce89e | ||
|
|
f525c49267 | ||
|
|
838e986670 | ||
|
|
fa70b31873 | ||
|
|
3855c30edd | ||
|
|
c7dccf8c0b | ||
|
|
2526304394 | ||
|
|
e1da39253a | ||
|
|
6b49741e69 | ||
|
|
2f414c6e08 | ||
|
|
9ed5420381 | ||
|
|
69c8c14c91 | ||
|
|
88b1a330a6 | ||
|
|
9e481cdcaa | ||
|
|
db50493577 | ||
|
|
3907034a1d | ||
|
|
b1b2540dc0 | ||
|
|
5dc1ed5685 | ||
|
|
1ad8da5ef2 | ||
|
|
22e9f18d08 | ||
|
|
b6c9265e31 | ||
|
|
ee6ab38c37 | ||
|
|
fbd237531e | ||
|
|
49101ef0e8 | ||
|
|
8751ee7361 | ||
|
|
01012ff300 | ||
|
|
2a2bb4d28e | ||
|
|
ce04f14f58 | ||
|
|
16b36e2b9f | ||
|
|
c597941128 | ||
|
|
ab22b50faf | ||
|
|
7785091bae | ||
|
|
56449ed129 | ||
|
|
da2864f422 | ||
|
|
c702119e66 | ||
|
|
30d4f9667b | ||
|
|
cd3a71baab | ||
|
|
391409b6c0 | ||
|
|
25dbeb7a9a | ||
|
|
12360c6f3f | ||
|
|
61754acb87 | ||
|
|
634cb1413b | ||
|
|
2fa40c5e96 | ||
|
|
432c12fbac | ||
|
|
146c8e76f0 | ||
|
|
e9f44c8f0f | ||
|
|
f3202f95b6 | ||
|
|
d5f319d9cb | ||
|
|
74324f4585 | ||
|
|
21e53310f8 | ||
|
|
a7596001c5 | ||
|
|
93243298e1 | ||
|
|
ca098e821f | ||
|
|
00d6bd8595 | ||
|
|
f0c85c4795 | ||
|
|
74113b3e6e | ||
|
|
cca954d01c | ||
|
|
d825cc7cfe | ||
|
|
ffca4261d7 | ||
|
|
865a0bfaf6 | ||
|
|
595da9f339 | ||
|
|
44b4a4beba | ||
|
|
c66c359432 | ||
|
|
1214ea0d91 | ||
|
|
cfa05e01c4 | ||
|
|
6a71272b79 | ||
|
|
b9b55b4e5b | ||
|
|
3f2aef2925 | ||
|
|
7a8a29639f | ||
|
|
becfd9b211 | ||
|
|
c6b17d5946 | ||
|
|
e24e058ab2 | ||
|
|
e74ef4b7dc | ||
|
|
c2f6d4fd6f | ||
|
|
2862eee33b | ||
|
|
3ad920457a | ||
|
|
768c6860f5 | ||
|
|
369efc784b | ||
|
|
522327d807 | ||
|
|
915233c5b6 | ||
|
|
229830ee89 | ||
|
|
dd329e7a4f | ||
|
|
da56a53a9c | ||
|
|
3f014a6fe1 | ||
|
|
d9d5780ea7 | ||
|
|
9002ae157a | ||
|
|
f279613f5d | ||
|
|
b58e731c19 | ||
|
|
9d73f27146 | ||
|
|
81f2a48c6d | ||
|
|
57a87d6322 | ||
|
|
728a4b1170 | ||
|
|
9de85591bc | ||
|
|
086c280e5d | ||
|
|
da677f87d5 | ||
|
|
28098b3155 | ||
|
|
c268348e29 | ||
|
|
b33426416b | ||
|
|
cf3dd2d225 | ||
|
|
11bc442f10 | ||
|
|
41ce95cbf1 | ||
|
|
75258db0a2 | ||
|
|
df8bf895ef | ||
|
|
1b86373a54 | ||
|
|
a4a9a4d26e | ||
|
|
1a41ac97d2 | ||
|
|
55aab67383 | ||
|
|
76e2e67af8 | ||
|
|
b682ff5d08 | ||
|
|
2d74f8883e | ||
|
|
e7b51a30a3 | ||
|
|
0b98d18483 | ||
|
|
3d6f35391e | ||
|
|
10047de745 | ||
|
|
82600a3404 | ||
|
|
b73eb77b95 | ||
|
|
1fca24765a | ||
|
|
f107ebc46d | ||
|
|
f2ffae8a22 | ||
|
|
ea78fe9726 | ||
|
|
7d61bc09bc | ||
|
|
b1231fd981 | ||
|
|
bcc3d617a0 | ||
|
|
6890176c76 | ||
|
|
d12c50ea4e | ||
|
|
31525e79a8 | ||
|
|
c48317672c | ||
|
|
f3ca996116 | ||
|
|
5daa4b3e20 | ||
|
|
80e84de868 | ||
|
|
7efd811e75 | ||
|
|
5e68f81a1f | ||
|
|
bebe4feccf | ||
|
|
e684de30bd | ||
|
|
bd0d922737 | ||
|
|
1668c1da87 | ||
|
|
95dec0e72e | ||
|
|
5a33f66ad6 | ||
|
|
33276dae76 | ||
|
|
b0e48b6bda | ||
|
|
f0aec4cd7f | ||
|
|
5ed636bce4 | ||
|
|
fa6191b448 | ||
|
|
93ca273312 | ||
|
|
82d14351fc | ||
|
|
3845d5090b | ||
|
|
17bb3715d1 | ||
|
|
f28426063f | ||
|
|
6efe1b80c5 | ||
|
|
01696f75e9 | ||
|
|
e97017b297 | ||
|
|
22222308a1 | ||
|
|
7fb3870247 | ||
|
|
c57696cf80 | ||
|
|
0784f30e23 | ||
|
|
9af93ecde6 | ||
|
|
d9e3a87911 | ||
|
|
f01789a17e | ||
|
|
688514eab6 | ||
|
|
3d069179d6 | ||
|
|
f90a6e7082 | ||
|
|
406c02ad07 | ||
|
|
3bc824b462 | ||
|
|
c705e0b5ae | ||
|
|
3a04ba5726 | ||
|
|
60a6db8b47 | ||
|
|
cf5550ab0a | ||
|
|
19aeba5fab | ||
|
|
d9afd8d887 | ||
|
|
7422f083b4 | ||
|
|
06508cfb7c | ||
|
|
980d8ba44f | ||
|
|
d658b05ee9 | ||
|
|
123fe36ee4 | ||
|
|
4bf4b96fa9 | ||
|
|
b8680275e5 | ||
|
|
e03ca7b4bd | ||
|
|
3178b4c45e | ||
|
|
413f92e270 | ||
|
|
bd426ed1d3 | ||
|
|
ca9960c8bc | ||
|
|
6b9e1f3c2a | ||
|
|
b8d3a549df | ||
|
|
546dec67a1 | ||
|
|
d61aa853f1 | ||
|
|
96e0d695c0 | ||
|
|
8fbbfc8093 | ||
|
|
853a349265 | ||
|
|
621d4c82af | ||
|
|
87e0ae683e | ||
|
|
44c0f6189b | ||
|
|
64afa9a1ed | ||
|
|
1ce15fbb15 | ||
|
|
ace30af202 | ||
|
|
559340143e | ||
|
|
ae29bb4ca8 | ||
|
|
f6af9379b7 | ||
|
|
57e286b8ad | ||
|
|
7acedfd385 | ||
|
|
946799d19d | ||
|
|
7282a32859 | ||
|
|
5080f1dc39 | ||
|
|
cba165933c | ||
|
|
e54f42c0eb | ||
|
|
c493639119 | ||
|
|
cc5ada1f3d | ||
|
|
539a8bcec8 | ||
|
|
4416348c51 | ||
|
|
bd0eceda61 | ||
|
|
dddd3c1670 | ||
|
|
10795d55e6 | ||
|
|
230fab013a | ||
|
|
2082d20b88 | ||
|
|
c230f7a6c6 | ||
|
|
3405795371 | ||
|
|
b70ed38d51 | ||
|
|
70bb2783b9 | ||
|
|
d9fc7c9fdf | ||
|
|
1938c80061 | ||
|
|
76320ca5a3 | ||
|
|
4aef81284a | ||
|
|
e7ff07debd | ||
|
|
df3fac1032 | ||
|
|
92a46003f8 | ||
|
|
24db448cfd | ||
|
|
9e14c2e289 | ||
|
|
e750df3c0d | ||
|
|
81e4fd92ce | ||
|
|
683cc6d76e | ||
|
|
fb692a0555 | ||
|
|
40206dc94d | ||
|
|
50d70a5050 | ||
|
|
8463289f75 | ||
|
|
b101ee7fc9 | ||
|
|
d06c8d49d7 | ||
|
|
e193c2df21 | ||
|
|
cc99ebba81 | ||
|
|
80d2db6315 | ||
|
|
c05e7df48e | ||
|
|
834df64650 | ||
|
|
8113b822c5 | ||
|
|
a53e565e5a | ||
|
|
d4a71ce384 | ||
|
|
004853f030 | ||
|
|
37be59b0dd | ||
|
|
273503ddb9 | ||
|
|
ce431ee155 | ||
|
|
12844e341c | ||
|
|
eaf1e075f2 | ||
|
|
7affaaf7c0 | ||
|
|
90f3564d0b | ||
|
|
05d14bb364 | ||
|
|
7b8c11852e | ||
|
|
5164fd362c | ||
|
|
2e689e4482 | ||
|
|
1ee2e6ca0a | ||
|
|
924c8f2fac | ||
|
|
0001f86826 | ||
|
|
9c9c912657 | ||
|
|
024c8ee5a1 | ||
|
|
2e7985639e | ||
|
|
e7c745a512 | ||
|
|
1b75887615 | ||
|
|
a1bcc6eed4 | ||
|
|
0bf6285287 | ||
|
|
4df71f32a8 | ||
|
|
44b7f9fa99 | ||
|
|
03329387b3 | ||
|
|
e5ba898fc7 | ||
|
|
a41b5a6d4c | ||
|
|
cbbde887d4 | ||
|
|
89724d6e4d | ||
|
|
6d5325d444 | ||
|
|
db1b32612b | ||
|
|
f4ccef8c70 | ||
|
|
99aa064e6c | ||
|
|
24a5ca0c06 | ||
|
|
6eda572f31 | ||
|
|
7b6dd9ba6f | ||
|
|
1341767692 | ||
|
|
f845805706 | ||
|
|
b2132dd668 | ||
|
|
fc5eaa2941 | ||
|
|
045bb11abf | ||
|
|
9a31155581 | ||
|
|
9e510ce088 | ||
|
|
a642ba91fa | ||
|
|
b0fdeab995 | ||
|
|
1bf1fc6512 | ||
|
|
392373b4b7 | ||
|
|
f3def88342 | ||
|
|
690d09a3d1 | ||
|
|
9471e72171 | ||
|
|
9ebde3b315 | ||
|
|
eaa061dd06 | ||
|
|
5f6ab41798 | ||
|
|
9adb0fee28 | ||
|
|
c577833e7c | ||
|
|
d57bcd9aa2 | ||
|
|
0e7149eec3 | ||
|
|
e53c1a0f7b | ||
|
|
e689493723 | ||
|
|
e6d3226c47 | ||
|
|
2c2650ff07 | ||
|
|
603d667608 | ||
|
|
e9125b0900 | ||
|
|
d0c696e523 | ||
|
|
6f9afcdba2 | ||
|
|
705de81c78 | ||
|
|
335bc3cf48 | ||
|
|
857798ff2f | ||
|
|
3955bd72b5 | ||
|
|
b5d80025e9 | ||
|
|
5b5e4f8ff0 | ||
|
|
ad5bd22c5e | ||
|
|
b2ed6f1acf | ||
|
|
2a8364e902 | ||
|
|
23a18ec318 | ||
|
|
d713f0209d | ||
|
|
0a21d37146 | ||
|
|
c645142f82 | ||
|
|
0e7a1de4db | ||
|
|
ef3ef433c1 | ||
|
|
0da91be9b1 | ||
|
|
69454ada69 | ||
|
|
1126fedea8 | ||
|
|
69c28e3563 | ||
|
|
b117bb1dc2 | ||
|
|
9c9330114f | ||
|
|
0fd314632a | ||
|
|
0d91c74289 | ||
|
|
d59f9e218f | ||
|
|
9bbd04d6cf | ||
|
|
22be6325a8 | ||
|
|
e96cc97462 | ||
|
|
92f5cb8fe3 | ||
|
|
a5897caec4 | ||
|
|
88c83b4dd7 | ||
|
|
fe0ede178e | ||
|
|
cca3d8eb41 | ||
|
|
5590f7d85a | ||
|
|
90bced3ed2 | ||
|
|
19399482e1 | ||
|
|
7657c45aae | ||
|
|
e6e0832c8a | ||
|
|
8f80b7c1ef | ||
|
|
c766e3fa3b | ||
|
|
788118ce3d | ||
|
|
77f8ded1e6 | ||
|
|
9d863f63e5 | ||
|
|
1e4ef070cf | ||
|
|
c1336ad5b3 | ||
|
|
143e0478ef | ||
|
|
cbd07c9d1b | ||
|
|
e3b0958c23 | ||
|
|
b843ab6542 | ||
|
|
dccce9355d | ||
|
|
6c30e649e3 | ||
|
|
bcebc8d5fb | ||
|
|
225398972d | ||
|
|
87f764bfa9 | ||
|
|
47217e9158 | ||
|
|
10334ddfe1 | ||
|
|
002e71f971 | ||
|
|
1f2b1e3b8a | ||
|
|
5d12616211 | ||
|
|
9704eff9f7 | ||
|
|
362a8268e8 | ||
|
|
c0e3c5d692 | ||
|
|
94c02ded2a | ||
|
|
998bd0ce06 | ||
|
|
6b6465b5e3 | ||
|
|
57da50b8d6 | ||
|
|
f87f6031de | ||
|
|
5b686824c5 | ||
|
|
95487d362f | ||
|
|
f496546cc0 | ||
|
|
9423d7e892 | ||
|
|
062f984262 | ||
|
|
925675ab54 | ||
|
|
c05513ae1e | ||
|
|
f257ccb682 | ||
|
|
d5b0bd4d31 | ||
|
|
5026bd1887 | ||
|
|
b0b0cf7b9e | ||
|
|
bf96cf4cb4 | ||
|
|
aeef1dca59 | ||
|
|
a1d460e220 | ||
|
|
e2a962de32 | ||
|
|
dbf1f46fa1 | ||
|
|
ce07a27b8d | ||
|
|
8f6e08eb2f | ||
|
|
a4b14e4911 | ||
|
|
56114562cb | ||
|
|
ba7834f222 | ||
|
|
d49406d2f1 | ||
|
|
a81d8adc90 | ||
|
|
3eae4a0f45 | ||
|
|
016f921cf0 | ||
|
|
9c8230a867 | ||
|
|
aea7724cdb | ||
|
|
79d87ad661 | ||
|
|
ad4b27b7ca | ||
|
|
40ba81ee6a | ||
|
|
d0e71a8104 | ||
|
|
f2d7d43e38 | ||
|
|
d7cb19ee96 | ||
|
|
9d425a19e8 | ||
|
|
48f37df3d5 | ||
|
|
ab876fc7d8 | ||
|
|
712326387f | ||
|
|
133054e2dd | ||
|
|
adcc5a358b | ||
|
|
5c2379577b | ||
|
|
c5352f2ee7 | ||
|
|
5574364438 | ||
|
|
cb0b30690e | ||
|
|
8442e6ec8d | ||
|
|
8ba3a67401 | ||
|
|
f2dc03b51e | ||
|
|
5d39aa55bd | ||
|
|
22574d3ada | ||
|
|
0727b4564b | ||
|
|
6521218308 | ||
|
|
34a9f15cb4 | ||
|
|
a5b65f4087 | ||
|
|
f590f672be | ||
|
|
d9afaca51f | ||
|
|
3b5525a0a5 | ||
|
|
fdfb888be9 | ||
|
|
02eee4bd3e | ||
|
|
b257963551 | ||
|
|
fb43c036f6 | ||
|
|
9ae6184dc1 | ||
|
|
7efedc7b44 | ||
|
|
e1e9be8daa | ||
|
|
247ff86f2f | ||
|
|
89cb5ab2f1 | ||
|
|
c484481964 | ||
|
|
613cece224 | ||
|
|
5aa1fdca04 | ||
|
|
73700cca54 | ||
|
|
3c92e87156 | ||
|
|
0122d3e361 | ||
|
|
30410b0a93 | ||
|
|
5c5632967c | ||
|
|
d872f4e177 | ||
|
|
677faab273 | ||
|
|
d40ee80680 | ||
|
|
79dd6b9be0 | ||
|
|
49cfc687f9 | ||
|
|
f0ccd6fb09 | ||
|
|
372d4f7933 | ||
|
|
6621519f11 | ||
|
|
1777a0171b | ||
|
|
93e702f9ca | ||
|
|
0d8cdf3afb | ||
|
|
fa41d620fd | ||
|
|
93543919d4 | ||
|
|
001b04ac5a | ||
|
|
417ec01fb2 | ||
|
|
0c5938a5a3 | ||
|
|
2e6eff43ad | ||
|
|
c714351819 | ||
|
|
e9c56f8635 | ||
|
|
5f88efb2c9 | ||
|
|
e6af567206 | ||
|
|
43cbcd86be | ||
|
|
44931f8205 | ||
|
|
bc7622b0bd | ||
|
|
1a1d22d6ee | ||
|
|
ab9034633a | ||
|
|
c860ee85f7 | ||
|
|
16adb90653 | ||
|
|
a5d877207e | ||
|
|
685abb6aa9 | ||
|
|
7f0a0d92a3 | ||
|
|
e4983be9d7 | ||
|
|
e30b44dae1 | ||
|
|
0922c8299f | ||
|
|
8adb4d5cef | ||
|
|
5198f6c4df | ||
|
|
5d0bafd651 | ||
|
|
b749c78efa | ||
|
|
22b93e32eb | ||
|
|
c767566cb6 | ||
|
|
a2cc1832f2 | ||
|
|
169346cdbd | ||
|
|
ea50540bd5 | ||
|
|
8e909ef1c1 | ||
|
|
7ad42ccf16 | ||
|
|
633a350bfc | ||
|
|
23d6f9efc2 | ||
|
|
838cae0d2d | ||
|
|
24e92c437d | ||
|
|
7d45c89d72 | ||
|
|
8e931c5acc | ||
|
|
be37779472 | ||
|
|
27cfd53c06 | ||
|
|
00e74a5e45 | ||
|
|
c890ae5a15 | ||
|
|
786ddaa38f | ||
|
|
d162e5533f | ||
|
|
e68b726f9a | ||
|
|
8ed3b0eb91 | ||
|
|
a9332c0ab0 | ||
|
|
423abf4fde | ||
|
|
07ad87365f | ||
|
|
2c136c1fde | ||
|
|
1a122eb7fd | ||
|
|
4a63399363 | ||
|
|
ee8e2b3225 | ||
|
|
8aa52e7e3c | ||
|
|
de597b602a | ||
|
|
2371364911 | ||
|
|
214e794451 | ||
|
|
ab902f948a | ||
|
|
ce1e36f4fd | ||
|
|
72eb5204ff | ||
|
|
e4da5bec8d | ||
|
|
6102963624 | ||
|
|
b152cb43fc | ||
|
|
ebe1678b30 | ||
|
|
8a0962681a | ||
|
|
9778f9521e | ||
|
|
4a3cfb9dfa | ||
|
|
1cfa93e855 | ||
|
|
4fab13c728 | ||
|
|
a3c69dab07 | ||
|
|
e3dd430e68 | ||
|
|
d018ecd805 | ||
|
|
a4b7ab5e9d | ||
|
|
ef1481be63 | ||
|
|
3a2b5d5ccd | ||
|
|
78f6e37f76 | ||
|
|
1ea521abab | ||
|
|
b8a97f2138 | ||
|
|
dac5ff38db | ||
|
|
ac98d2f585 | ||
|
|
37a9cca82d | ||
|
|
8c53594df7 | ||
|
|
eec7567ac6 | ||
|
|
920fde9cd0 | ||
|
|
742bbdf3ac | ||
|
|
4da8a1301e | ||
|
|
3bd5cfab9d | ||
|
|
4f8b0f1c67 | ||
|
|
c59819f6dc | ||
|
|
96d0f4ae73 | ||
|
|
a35aeee6a2 | ||
|
|
2688cda6b5 | ||
|
|
f668133297 | ||
|
|
89bd07be92 | ||
|
|
a5fb806028 | ||
|
|
83714f628b | ||
|
|
ae3e2fb4e8 | ||
|
|
650102cedb | ||
|
|
61babbdf7d | ||
|
|
3734b6a05f | ||
|
|
0256d527f0 | ||
|
|
7adb93fb8e | ||
|
|
97f24f17eb | ||
|
|
96b0bae6ca | ||
|
|
3632b0d726 | ||
|
|
6792b4f4c4 | ||
|
|
10e35055e0 | ||
|
|
7548620683 | ||
|
|
d5e12e3506 | ||
|
|
23b9d401b7 | ||
|
|
44894765b6 | ||
|
|
c2568f4be9 | ||
|
|
90adb9dfa3 | ||
|
|
d2f3eba434 | ||
|
|
2fe058e0ee | ||
|
|
4410acaee8 | ||
|
|
d03f5fbbc0 | ||
|
|
ae1c2f95a7 | ||
|
|
381547ec78 | ||
|
|
f4672ea6b8 | ||
|
|
51a270a2f7 | ||
|
|
f3c8f2e1ab | ||
|
|
e2b676caaa | ||
|
|
68ac49259c | ||
|
|
d94c0aae4b | ||
|
|
047e700d09 | ||
|
|
9ccc473982 | ||
|
|
4142029f48 | ||
|
|
dbf2cc8d80 | ||
|
|
f200972c00 | ||
|
|
3033597f02 | ||
|
|
a16dd889cf | ||
|
|
dab6ecbf9a | ||
|
|
8bdbf6ab15 | ||
|
|
8f75e12871 | ||
|
|
36311e6cef | ||
|
|
8f5af46ec2 | ||
|
|
aeebf53739 | ||
|
|
1d81ee9ed0 | ||
|
|
e1c9d24aa4 | ||
|
|
b7da062a4c | ||
|
|
6fd6d564eb | ||
|
|
106f1d1794 | ||
|
|
3809d7f3e7 | ||
|
|
c769dd20f7 | ||
|
|
445865e422 | ||
|
|
24d4934c27 | ||
|
|
fe7cdba07e | ||
|
|
c56db15a03 | ||
|
|
deb9993a64 | ||
|
|
1ff8860b98 | ||
|
|
198fc18c50 | ||
|
|
621fdc9244 | ||
|
|
9ece6213a9 | ||
|
|
35c313b689 | ||
|
|
d6b88b842d | ||
|
|
ff90cac755 | ||
|
|
8f93606c81 | ||
|
|
95baa44a1c | ||
|
|
fa65e67069 | ||
|
|
066d5032f9 | ||
|
|
3f8a520624 | ||
|
|
6ab6695be7 | ||
|
|
eda0ccffdb | ||
|
|
1d399c3c21 | ||
|
|
cfd0faf5e7 | ||
|
|
ffee030548 | ||
|
|
9d81858ff4 | ||
|
|
f791210e70 | ||
|
|
794ff93fa9 | ||
|
|
ff8eb930f7 | ||
|
|
d71a21ca8b | ||
|
|
2bd23dd374 | ||
|
|
a501463ac7 | ||
|
|
9bd049923e | ||
|
|
67152f1c83 | ||
|
|
8ca3fdec75 | ||
|
|
8e7fc54548 | ||
|
|
1642c3e694 | ||
|
|
d738c13e9e | ||
|
|
45eb46c3f3 | ||
|
|
7b629d0697 | ||
|
|
89a1fa5042 | ||
|
|
c9c59b761f | ||
|
|
8b2f398015 | ||
|
|
5c5bf7a60e | ||
|
|
6cf4404243 | ||
|
|
56ff310f29 | ||
|
|
b671b49725 | ||
|
|
d3d7db1f34 | ||
|
|
26a388e3bf | ||
|
|
6a9d8e903e | ||
|
|
daba5c0599 | ||
|
|
d1e6cd5a8f | ||
|
|
eeb7f30585 | ||
|
|
29fdb06cea | ||
|
|
6d02e2c89e | ||
|
|
cf8c17d08b | ||
|
|
ad5d55220c | ||
|
|
2ecd50f5dc | ||
|
|
48d22e0d42 | ||
|
|
096e85fbc3 | ||
|
|
e3d8e534f6 | ||
|
|
acadc246cb | ||
|
|
fd2c8d1d64 | ||
|
|
f7575e4ddb | ||
|
|
114ee8f6ac | ||
|
|
b91a45639f | ||
|
|
c4277f8566 | ||
|
|
b7aa8b06ca | ||
|
|
26097b7cfe | ||
|
|
6addf507cb | ||
|
|
f0bf29ba08 | ||
|
|
6ef7701805 | ||
|
|
70814b1026 | ||
|
|
ca1d7e77ba | ||
|
|
a800baf8ae | ||
|
|
2f7f07467e | ||
|
|
f472bba9f0 | ||
|
|
48de418d21 | ||
|
|
8892637b97 | ||
|
|
8c55d4b55d | ||
|
|
dffb8a955b | ||
|
|
86e3b4de06 | ||
|
|
525218f9a7 | ||
|
|
9a70329db2 | ||
|
|
9a63a4b7ca | ||
|
|
357f245aec | ||
|
|
8f9451a24b | ||
|
|
6396ccc701 | ||
|
|
9361c069db | ||
|
|
0b5cc2d639 | ||
|
|
61358b6233 | ||
|
|
05815d9475 | ||
|
|
77db1a6297 | ||
|
|
21254a060a | ||
|
|
f049936ec4 | ||
|
|
8673eb5eca | ||
|
|
43df9175df | ||
|
|
7fcf22de90 | ||
|
|
6f9d81ca68 | ||
|
|
267ce81be2 | ||
|
|
3987ee7ee3 | ||
|
|
87c9986b33 | ||
|
|
a85f1571a0 | ||
|
|
d6ff00226a | ||
|
|
7f1f34e34f | ||
|
|
edb3238f3e | ||
|
|
67d5e43ec4 | ||
|
|
f8e3a3e5b4 | ||
|
|
ef410e298d | ||
|
|
80fc2a9240 | ||
|
|
3ae1202536 | ||
|
|
2c0bb2041b | ||
|
|
ab6b6876aa | ||
|
|
284445e0c9 | ||
|
|
05729505d8 | ||
|
|
4004fb908b | ||
|
|
19660e49d2 | ||
|
|
b6a2c9336e | ||
|
|
ba2bc46042 | ||
|
|
ad8f74c964 | ||
|
|
ded3123af2 | ||
|
|
e076abe759 | ||
|
|
c1bb4864ab | ||
|
|
b3a39cbdf7 | ||
|
|
d59e0514b3 | ||
|
|
6e88c5a1c5 | ||
|
|
c74be2b789 | ||
|
|
a258122e22 | ||
|
|
a01d7123d7 | ||
|
|
7652db4601 | ||
|
|
433c540ee2 | ||
|
|
37e1f7104f | ||
|
|
6467c8a870 | ||
|
|
a892f55cb2 | ||
|
|
ac4424bab2 | ||
|
|
99992c951d | ||
|
|
c516b74b75 | ||
|
|
da53b81bf9 | ||
|
|
f730924572 | ||
|
|
0fb69a38cd | ||
|
|
125e8b271b | ||
|
|
8c2ab6b860 | ||
|
|
d639b24c2d | ||
|
|
6ebbf3aa32 | ||
|
|
b97548f8d6 | ||
|
|
87eff3e133 | ||
|
|
2e6ce67b64 | ||
|
|
843eda6fea | ||
|
|
643f9b2c76 | ||
|
|
2089f5e8ee | ||
|
|
7e98d7dc52 | ||
|
|
a514b2fe79 | ||
|
|
d56230e04d | ||
|
|
03e0d8248d | ||
|
|
065bd8f28a | ||
|
|
ca13a9199f | ||
|
|
b59d476c2d | ||
|
|
c69486556a | ||
|
|
bfd75da308 | ||
|
|
53bbb9b15b | ||
|
|
f1d58983c2 | ||
|
|
a8a545690f | ||
|
|
3748513996 | ||
|
|
18c2a4122f | ||
|
|
04664c8d42 | ||
|
|
628bbe14a8 | ||
|
|
abae7c9d67 | ||
|
|
eeee00f6cd | ||
|
|
6971ea5f9c | ||
|
|
cee38f93c8 | ||
|
|
fa5a6b8176 | ||
|
|
97f8940b15 | ||
|
|
556d23ade5 | ||
|
|
313290b22c | ||
|
|
d339db4799 | ||
|
|
03dff2ce77 | ||
|
|
c2c44f8d29 | ||
|
|
13130c7f27 | ||
|
|
30b986bb33 | ||
|
|
0196b0a445 | ||
|
|
8edd06fb67 | ||
|
|
38b59cc198 | ||
|
|
4fbd0bb5c5 | ||
|
|
c2563661d0 | ||
|
|
f358cfb7c7 | ||
|
|
ef11d3e0ed | ||
|
|
f44e2ccf52 | ||
|
|
7e75c5b9b9 | ||
|
|
15ae11f966 | ||
|
|
226439ef36 | ||
|
|
81aca93b81 | ||
|
|
777c3b0079 | ||
|
|
ab85e4df72 | ||
|
|
cf8185498a | ||
|
|
bf34b7df4e | ||
|
|
028c5a34e6 | ||
|
|
c0ea0b0f5a | ||
|
|
4c38055d38 | ||
|
|
87fcbc5bfb | ||
|
|
043a1aab21 | ||
|
|
9f86c8e40d | ||
|
|
3330da0e59 | ||
|
|
64492bec43 | ||
|
|
2402a84c71 | ||
|
|
294d06f22b | ||
|
|
d6e788dc89 | ||
|
|
070cd07199 | ||
|
|
4ded0ccd4f | ||
|
|
6253a2bca9 | ||
|
|
bd1efd8026 | ||
|
|
4940e0d8ed | ||
|
|
8d6cc65530 | ||
|
|
b8adefbb88 | ||
|
|
2c56a84bb7 | ||
|
|
613e22d6ca | ||
|
|
73e76fc72f | ||
|
|
1e5e2581c8 | ||
|
|
b25c4e33cb | ||
|
|
17b28f43f8 | ||
|
|
e3cde9be25 | ||
|
|
449703a6f0 | ||
|
|
358d5c72a6 | ||
|
|
abd2ced4b4 | ||
|
|
49e5aaea6b | ||
|
|
d1f431d121 | ||
|
|
fd759d6845 | ||
|
|
5be5531722 | ||
|
|
2a4fccde70 | ||
|
|
d6f018e581 | ||
|
|
e0c1cb6581 | ||
|
|
f80a00e211 | ||
|
|
4aee413680 | ||
|
|
2a91e2e7ad | ||
|
|
dba821a70e | ||
|
|
836dd73446 | ||
|
|
8f88427d4e | ||
|
|
817585a4ce | ||
|
|
0386f54449 | ||
|
|
4761677676 | ||
|
|
f61d053010 | ||
|
|
8440d02198 | ||
|
|
ebcdf847d2 | ||
|
|
5c553aae19 | ||
|
|
0af8ca5f78 | ||
|
|
52ba178c4e | ||
|
|
166016a741 | ||
|
|
20786e690d | ||
|
|
c19417a706 | ||
|
|
b768e0affb | ||
|
|
05ff10df1b | ||
|
|
a0a6227368 | ||
|
|
918e65d8d3 | ||
|
|
6cab62d140 | ||
|
|
9526268f1f | ||
|
|
016ec1877a | ||
|
|
772e75f04d | ||
|
|
10d7190ea4 | ||
|
|
cc8703577d | ||
|
|
4533755704 | ||
|
|
da4a08dfe1 | ||
|
|
48ddbf0412 | ||
|
|
39def1c20b | ||
|
|
e8a31b5d37 | ||
|
|
5a25dd08f6 | ||
|
|
b34219ce86 | ||
|
|
05548b0fa6 | ||
|
|
e08e7be24c | ||
|
|
1be600155b | ||
|
|
be757b1f2f | ||
|
|
d592e1e535 | ||
|
|
e381e9f52e | ||
|
|
3b951efd13 | ||
|
|
33161c5bad | ||
|
|
8b6236c796 | ||
|
|
a59d80d7f3 | ||
|
|
a5c76591ac | ||
|
|
c2a975af9e | ||
|
|
ab48cd5b55 | ||
|
|
ed4909a28d | ||
|
|
afc517b3a7 | ||
|
|
de9f6b6711 | ||
|
|
646cb8caeb | ||
|
|
d533ddde88 | ||
|
|
9664cb653a | ||
|
|
1574d44cc3 | ||
|
|
6981fc06ca | ||
|
|
12798c00bc | ||
|
|
ebb6be816b | ||
|
|
ae4e4f67c2 | ||
|
|
013aafde17 | ||
|
|
050b1ad78e | ||
|
|
21a76f5e7c | ||
|
|
f9d7610679 | ||
|
|
aeba15eb16 | ||
|
|
b81f64a253 | ||
|
|
a0d04cba50 | ||
|
|
43bdbcf89c | ||
|
|
6947a3ffa7 | ||
|
|
62d525dae7 | ||
|
|
f8bc74b27f | ||
|
|
044370f13c | ||
|
|
a2c45492cb | ||
|
|
9a67ca7c03 | ||
|
|
40cd8a24af | ||
|
|
28948975db | ||
|
|
1ce1db9689 | ||
|
|
86cdeed6ef | ||
|
|
d6ec0afbd6 | ||
|
|
3ea33e1dee | ||
|
|
3e6e3c08d5 | ||
|
|
6155a2ec2b | ||
|
|
4e0787ec77 | ||
|
|
8ecc32da6d | ||
|
|
eb41da78f2 | ||
|
|
a508969331 | ||
|
|
99febda256 | ||
|
|
543d78e040 | ||
|
|
4ef96c7930 | ||
|
|
7a7c99f752 | ||
|
|
6b1f17d6a8 | ||
|
|
cf191ef874 | ||
|
|
f6d3d013fd | ||
|
|
e6ad3c414f | ||
|
|
8162719e36 | ||
|
|
f5bc02d598 | ||
|
|
139dfb76e4 | ||
|
|
f4769c4046 | ||
|
|
fea3cc741f | ||
|
|
4e764350a4 | ||
|
|
c63811367e | ||
|
|
4cc8b3198b | ||
|
|
5c6ac06656 | ||
|
|
9b6b94d919 | ||
|
|
8f4af5ece4 | ||
|
|
2d9248889f | ||
|
|
804f37dcfa | ||
|
|
bf98713a55 | ||
|
|
2f2ba44c7c | ||
|
|
62292500ba | ||
|
|
a7f1e89ce9 | ||
|
|
20c7e2841c | ||
|
|
c79f5c5a7a | ||
|
|
d3771ed31f | ||
|
|
230eec1baf | ||
|
|
0bae393e07 | ||
|
|
434cf40b36 | ||
|
|
7417a1c950 | ||
|
|
799ccbe300 | ||
|
|
43044232bb | ||
|
|
4567db8836 | ||
|
|
e286e59431 | ||
|
|
65e89a4248 | ||
|
|
db63086c38 | ||
|
|
2abdf28eba | ||
|
|
2476f04a26 | ||
|
|
e458f1eab4 | ||
|
|
b15b2649f8 | ||
|
|
4fce7ba09a | ||
|
|
1a1369f115 | ||
|
|
9434ffabdc | ||
|
|
6aeb428796 | ||
|
|
8cabf32ebe | ||
|
|
85a98fcfd6 | ||
|
|
aa1f81a30a | ||
|
|
1c50c73aa4 | ||
|
|
6cc28c7edd | ||
|
|
1c2cf768f8 | ||
|
|
481e718d86 | ||
|
|
29f74afbf6 | ||
|
|
02111ab5b8 | ||
|
|
a2f7157bc3 | ||
|
|
e6591abfbc | ||
|
|
50bda1ddee | ||
|
|
31abad86d5 | ||
|
|
af7e90c87a | ||
|
|
2e4bdf122e | ||
|
|
83d7fa26fe | ||
|
|
efc4d89fdf | ||
|
|
a27206c543 | ||
|
|
faca2c9a2d | ||
|
|
0cbd137b4f | ||
|
|
cdf41d3e5e | ||
|
|
aa034f8669 | ||
|
|
aee5a50b9f | ||
|
|
b8926f00d7 | ||
|
|
839386f0b4 | ||
|
|
cf2e4bdd9c | ||
|
|
e7a455f22e |
No files matched your search
+185
@@ -0,0 +1,185 @@
|
|||||||
|
# Workspace/editor
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
frontend-web/.vscode/
|
||||||
|
node_modules/
|
||||||
|
# Claude Code local-only settings (per-user permissions, not shared)
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# Local runtime logs and pid files
|
||||||
|
.runtime/
|
||||||
|
logs/
|
||||||
|
offline-backend-20260512/backend/logs/
|
||||||
|
|
||||||
|
# Frontend local/runtime artifacts
|
||||||
|
frontend-web/.env
|
||||||
|
frontend-web/.env.local
|
||||||
|
frontend-web/.env.*.local
|
||||||
|
frontend-web/node_modules/
|
||||||
|
frontend-web/.pnpm-store/
|
||||||
|
frontend-web/tsconfig.tsbuildinfo
|
||||||
|
frontend-web/vite-dev.log
|
||||||
|
frontend-web/vite-dev.err.log
|
||||||
|
|
||||||
|
# Frontend local packages/archives/docs (non-source)
|
||||||
|
frontend-web/pnpm-lock.yaml
|
||||||
|
frontend-web/README.md
|
||||||
|
frontend-web/doc.md
|
||||||
|
frontend-web/src.zip
|
||||||
|
frontend-web/src9.zip
|
||||||
|
frontend-web/src-v1.zip
|
||||||
|
frontend-web/src-*.rar
|
||||||
|
frontend-web/dist
|
||||||
|
# Frontend build artifacts
|
||||||
|
frontend-web/dist/
|
||||||
|
frontend-web/dist-scheduled-check/
|
||||||
|
|
||||||
|
# Offline backend bundles and local deployment artifacts
|
||||||
|
offline-backend-20260505.zip
|
||||||
|
offline-backend-20260505/output/
|
||||||
|
offline-backend-20260505/bundle/.env
|
||||||
|
offline-backend-20260505/bundle/.env.example
|
||||||
|
offline-backend-20260505/bundle/DEPLOY_BACKEND_OFFLINE_ZH.md
|
||||||
|
offline-backend-20260505/bundle/SHA256SUMS
|
||||||
|
offline-backend-20260505/bundle/config.yaml
|
||||||
|
offline-backend-20260505/bundle/config.yaml.example
|
||||||
|
offline-backend-20260505/bundle/data/
|
||||||
|
offline-backend-20260505/bundle/deerflow-backend-offline-image-20260505.tar
|
||||||
|
offline-backend-20260505/bundle/extensions_config.json
|
||||||
|
offline-backend-20260505/bundle/frontend-vite.zip
|
||||||
|
offline-backend-20260505/bundle/skills/
|
||||||
|
offline-backend-20260505/bundle/start_backend.0509.sh
|
||||||
|
offline-backend-20260505/bundle/start_backend.sh
|
||||||
|
offline-backend-20260505/bundle/stop_backend.sh
|
||||||
|
|
||||||
|
# offline-backend-20260512 runtime artifacts (large files, local config, data)
|
||||||
|
offline-backend-20260512/.env.example
|
||||||
|
offline-backend-20260512/.env
|
||||||
|
offline-backend-20260512/DEPLOY_BACKEND_OFFLINE_ZH.md
|
||||||
|
offline-backend-20260512/SHA256SUMS
|
||||||
|
offline-backend-20260512/config.yaml.example
|
||||||
|
offline-backend-20260512/data/
|
||||||
|
offline-backend-20260512/deerflow-backend-offline-image-20260505.tar
|
||||||
|
offline-backend-20260512/extensions_config.json
|
||||||
|
offline-backend-20260512/skills/
|
||||||
|
offline-backend-20260512/wheelhouse/
|
||||||
|
offline-backend-20260512/backend/.deer-flow/data/*.db
|
||||||
|
offline-backend-20260512/backend/.deer-flow/data/*.db-*
|
||||||
|
offline-backend-20260512/backend/.deer-flow/*
|
||||||
|
offline-backend-20260512/backend/.env
|
||||||
|
offline-backend-20260512/backend/.env.local
|
||||||
|
offline-backend-20260512/start_backend.sh
|
||||||
|
offline-backend-20260512/stop_backend.sh
|
||||||
|
offline-backend-20260512/check_backend.sh
|
||||||
|
|
||||||
|
hermes-agent-main/
|
||||||
|
|
||||||
|
|
||||||
|
skills/*
|
||||||
|
|
||||||
|
offline-backend-20260512/backend/.deer-flow/*
|
||||||
|
|
||||||
|
# Per-user skill_evolution overrides — runtime data written by the API
|
||||||
|
offline-backend-20260512/backend/skill_evolution.json
|
||||||
|
skills/
|
||||||
|
hermes-agent-main/
|
||||||
|
.gitignore
|
||||||
|
offline-backend-20260512/backend/tests/*
|
||||||
|
# Keep the openGauss URL/dialect/schema compatibility regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_gauss_database.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_llmwiki_weknora.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_health_auth_bypass.py
|
||||||
|
# Keep formal Word OOXML and embedded-font regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_word_export.py
|
||||||
|
# Keep generated-file library aggregation and filtering regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_artifact_library.py
|
||||||
|
# Keep the model configuration regression coverage versioned even though
|
||||||
|
# this deployment repository otherwise ignores newly added backend tests.
|
||||||
|
!offline-backend-20260512/backend/tests/test_model_management.py
|
||||||
|
# Keep the forced-research agent runtime gate and sandbox-policy regressions versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_forced_research_agent.py
|
||||||
|
# Keep fixed-question exact-match and streaming regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_fixed_questions.py
|
||||||
|
# Keep configurable es_query system-prompt routing regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_es_query_routing_prompt.py
|
||||||
|
# Keep the local LLMWiki vector-index and external API regression coverage versioned.
|
||||||
|
!offline-backend-20260512/backend/tests/test_llmwiki_local_index_repository.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_llmwiki_vector_search.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_llmwiki_wiki_sync.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_external_llmwiki_search.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_llmwiki_wiki_retrieval.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_public_llmwiki_embed.py
|
||||||
|
# Deep-research reliability regression coverage.
|
||||||
|
!offline-backend-20260512/backend/tests/test_deep_research_persistence.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_deep_research_event_sink.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_deep_research_executor_reliability.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_deep_research_start_job.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_summarization_human_message.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_compact_menu_overrides.py
|
||||||
|
# Independent enterprise-research persistence, HTTP and race regressions.
|
||||||
|
!offline-backend-20260512/backend/tests/test_enterprise_research_tasks.py
|
||||||
|
# Position-roundtable per-turn write/present cap (weak-model duplicate artifacts).
|
||||||
|
!offline-backend-20260512/backend/tests/test_position_artifact_cap.py
|
||||||
|
# Roundtable coordinator/seat capability isolation regressions.
|
||||||
|
!offline-backend-20260512/backend/tests/test_roundtable_role_boundary.py
|
||||||
|
# Workflow Studio: schemas / definitions / engine / run-lifecycle regressions
|
||||||
|
# plus the concurrency remediation suite (atomic resume, draft CAS, publish
|
||||||
|
# counter, event seq) — required in CI per docs/WORKFLOW_STUDIO_BACKEND_REMEDIATION_ZH.md P1-06.
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_schemas.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_definitions.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_engine.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_runs.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_concurrency.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_workflow_schema_rebuild.py
|
||||||
|
# Writing-mode first-report pipeline enforcement regressions.
|
||||||
|
!offline-backend-20260512/backend/tests/test_writing_mode_report_middleware.py
|
||||||
|
!offline-backend-20260512/backend/tests/test_writing_rewrite_prompt.py
|
||||||
|
# LLM call timeout retry classification regression coverage.
|
||||||
|
!offline-backend-20260512/backend/tests/test_llm_error_handling_middleware.py
|
||||||
|
# Writing-mode pipeline conversation-material regressions.
|
||||||
|
!offline-backend-20260512/backend/tests/test_deep_research_report_tool.py
|
||||||
|
deploy
|
||||||
|
.history
|
||||||
|
|
||||||
|
# Cloned third-party repos (local only)
|
||||||
|
git-clone/
|
||||||
|
|
||||||
|
# Repo-root offline dependency pack (local only)
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# Frontend offline pack working directories
|
||||||
|
frontend-web/.offline-pack-build/
|
||||||
|
frontend-web/.offline-extract-test/
|
||||||
|
frontend-web-node_modules-offline-*.tar.gz
|
||||||
|
|
||||||
|
# Local dependency caches and backend scratch archives
|
||||||
|
/.pnpm-store/
|
||||||
|
/offline-backend-20260512/backend/backend*.zip
|
||||||
|
|
||||||
|
# Local offline bundles and scratch tooling
|
||||||
|
/*.tar.gz
|
||||||
|
/offline-backend-20260512/*.zip
|
||||||
|
/tmp/
|
||||||
|
|
||||||
|
.codegraph
|
||||||
|
codegraph.mdc
|
||||||
|
|
||||||
|
docs/aidev-docs
|
||||||
|
docs/backups
|
||||||
|
# Frontend skills components must be tracked (excluded from the broad skills/ ignore above)
|
||||||
|
!frontend-web/src/components/workspace/skills/
|
||||||
|
|
||||||
|
# uv lockfile (not tracked per project preference)
|
||||||
|
offline-backend-20260512/backend/uv.lock
|
||||||
|
|
||||||
|
# Local-only scratch directory (not committed)
|
||||||
|
0622/
|
||||||
|
|
||||||
|
# Python bytecode cache
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
@@ -102,7 +102,7 @@ The frontend (`frontend-web/`) is a React 19 + Vite SPA:
|
|||||||
- **UI**: Radix UI primitives + TailwindCSS v4 + shadcn-style components in `src/components/ui/`
|
- **UI**: Radix UI primitives + TailwindCSS v4 + shadcn-style components in `src/components/ui/`
|
||||||
- **Shims**: `src/shims/` provides `next/link` and `next/navigation` polyfills (original code was Next.js)
|
- **Shims**: `src/shims/` provides `next/link` and `next/navigation` polyfills (original code was Next.js)
|
||||||
- **Login URL params** (`LoginPage.tsx`, frontend-only — the backend never sees these): besides the auth params (`ticket`/`userId`/`env`/`theme`/`yUserId`/`token`/`access_token`/`authToken`/`password`), the page supports a **task deep-link** via `?taskId=&goPath=`. After login it calls the consumer API `GET {VITE_AIPI_CONSUMER_URL}/taskAnalyseSearch/cop-task-detail?taskId=` (header `Authorization: Bearer admin`, auto-injected by `utils/request.ts`), stashes the full `data` (`id/overview/taskDirection/taskName/taskContent`) into `sessionStorage["login.copTaskDetail"]`, then routes by `goPath`: base codes `3qfx`/`rwfx`/`xdfx` → that business's configured **single agent** Q&A inside a **任务工作区** (resolved via `/api/business-mapping`; `resolveTaskDeeplinkTarget` calls `persistTaskContext({taskId, agentId, openingText})` and navigates to the `…/chats` **index**, which opens the latest task conversation or 新建 — a 1:N task↔对话 workspace, NOT `pendingAgentMessage`); the `-A` suffix (`3qfx-A`/…) → **multi-agent roundtable** with `roundtablePrefill.businessCode` so the page auto-selects the configured 业务链条. **xdfx's deep-link `?taskId=` is actually an 行动id, not a 任务id** — `resolveDeeplinkTaskId(goPath, rawId)` (called in `LoginPage` before everything) detects `xdfx`/`xdfx-A` and resolves it via `GET {action_detail_url}/{actionId}` → `data.taskId` (`fetchTaskIdFromAction`; `action_test`+`mock.action_detail` for offline testing); the resolved 任务id then drives `cop-task-detail`, the purpose tree, and the 1:N binding (other businesses pass the id through unchanged). **xdfx additionally partitions its chat records by 行动id (action_id), NOT by task_id**: `resolveTaskDeeplinkTarget` sets `partitionByAction:true` + keeps the raw action_id as `TaskContext.actionId`, so new threads are tagged `metadata.{taskId,actionId}` (taskId kept only to preserve the backend「任务深链对话全局共享」semantics, which keys off `metadata.taskId`) and the conversation list filters by `actionId` (+`agent_id`) — different actions of the same task get separate lists. xdfx's first-question opening text also appends `行动id(action_id)为…` (see `buildOpeningText` / `taskThreadMetadata`). goPath→businessCode: `3qfx`→`3Q`, `rwfx`→`6BF`, `xdfx`→`7BF`. The roundtable target also threads the URL `taskId` into `roundtablePrefill.taskId` (`resolveTaskDeeplinkTarget(goPath, detail, taskId)`), which turns on two taskId-scoped behaviors: (1) **separate, unpartitioned per-task storage** — taskId 会商聊天记录 live in their **own** store/table (`roundtable_task_drafts`), keyed **only by `task_id`** (one taskId = many records, shared across users, **no user 分权** at all; `created_by` is audit-only), kept entirely out of the per-user personal store (`roundtable_drafts`, now strictly per-user) so the two never mix. Backend router `/api/roundtable-task-drafts` (`GET /by-task/{id}` + `/by-task/{id}/latest`, `POST`, `GET/PUT/DELETE /{id}` — all user-agnostic); frontend client `roundtable-planning/api/task-drafts.ts`, and `useDraftPersistence`/`useRoundtableDrafts` route **all** CRUD to it when a `taskId` is present (else the personal `drafts.ts`). The matching **background jobs** are shared by task too: `roundtable_jobs.task_id` marks a task job; reads/dedup/stream/resume/cancel pass `?task_id=` so any user opening the task sees the same running 研讨 (progress/flowchart), and the executor writes the final report back to the task-draft store. Existing rows are moved over by `scripts/migrate_roundtable_task_drafts.py` (migration `20260618_01`). The history dropdown filters to the task and 新建 keeps the binding; (2) **Step-3 auto structured output** — after the `roundtable-summary` agent writes the Markdown report it automatically runs a second round emitting a `flow-json` flowchart (`useStep3Summary` `autoFlow`+`chainLevels`), whose hierarchy follows the business chain's stage goals (任务 → 目的 → 行为体 → …; coordinatorPrompt-specified hierarchy wins) and whose nodes never carry 阶段编号/阶段名 (the prior bug). **Both targets render embedded under the 任务管理 layout** — nested routes under `task-management/<business-path>/{roundtable,agent/:agent_id/chats}` in `StrategyRoutes.tsx` (`<business-path>` = `plan-task-sentiment` / `plan-task/plan-task-fx` / `plan-task/plan-task-action`, matching the COH中任务 menu paths in `sidebar-menu.ts`) so the global sidebar keeps 任务管理 → COH中任务 → the matching business highlighted. **All three single-agent businesses share `TaskFxWorkspace`** (`task-components/TaskFxWorkspace.tsx`): left = a「当前任务」card (`readCopTaskDetail()` 任务名称 + 简介, desensitized, 留存 above 新建对话) + per-task 1:N conversation list (`useTaskThreads(taskId, agentId, actionId?)` — filtered by **both** `metadata.taskId` AND `metadata.agent_id` so the three businesses' conversations staLine truncated
|
- **Login URL params** (`LoginPage.tsx`, frontend-only — the backend never sees these): besides the auth params (`ticket`/`userId`/`env`/`theme`/`yUserId`/`token`/`access_token`/`authToken`/`password`), the page supports a **task deep-link** via `?taskId=&goPath=`. After login it calls the consumer API `GET {VITE_AIPI_CONSUMER_URL}/taskAnalyseSearch/cop-task-detail?taskId=` (header `Authorization: Bearer admin`, auto-injected by `utils/request.ts`), stashes the full `data` (`id/overview/taskDirection/taskName/taskContent`) into `sessionStorage["login.copTaskDetail"]`, then routes by `goPath`: base codes `3qfx`/`rwfx`/`xdfx` → that business's configured **single agent** Q&A inside a **任务工作区** (resolved via `/api/business-mapping`; `resolveTaskDeeplinkTarget` calls `persistTaskContext({taskId, agentId, openingText})` and navigates to the `…/chats` **index**, which opens the latest task conversation or 新建 — a 1:N task↔对话 workspace, NOT `pendingAgentMessage`); the `-A` suffix (`3qfx-A`/…) → **multi-agent roundtable** with `roundtablePrefill.businessCode` so the page auto-selects the configured 业务链条. **xdfx's deep-link `?taskId=` is actually an 行动id, not a 任务id** — `resolveDeeplinkTaskId(goPath, rawId)` (called in `LoginPage` before everything) detects `xdfx`/`xdfx-A` and resolves it via `GET {action_detail_url}/{actionId}` → `data.taskId` (`fetchTaskIdFromAction`; `action_test`+`mock.action_detail` for offline testing); the resolved 任务id then drives `cop-task-detail`, the purpose tree, and the 1:N binding (other businesses pass the id through unchanged). **xdfx additionally partitions its chat records by 行动id (action_id), NOT by task_id**: `resolveTaskDeeplinkTarget` sets `partitionByAction:true` + keeps the raw action_id as `TaskContext.actionId`, so new threads are tagged `metadata.{taskId,actionId}` (taskId kept only to preserve the backend「任务深链对话全局共享」semantics, which keys off `metadata.taskId`) and the conversation list filters by `actionId` (+`agent_id`) — different actions of the same task get separate lists. xdfx's first-question opening text also appends `行动id(action_id)为…` (see `buildOpeningText` / `taskThreadMetadata`). goPath→businessCode: `3qfx`→`3Q`, `rwfx`→`6BF`, `xdfx`→`7BF`. The roundtable target also threads the URL `taskId` into `roundtablePrefill.taskId` (`resolveTaskDeeplinkTarget(goPath, detail, taskId)`), which turns on two taskId-scoped behaviors: (1) **separate, unpartitioned per-task storage** — taskId 会商聊天记录 live in their **own** store/table (`roundtable_task_drafts`), keyed **only by `task_id`** (one taskId = many records, shared across users, **no user 分权** at all; `created_by` is audit-only), kept entirely out of the per-user personal store (`roundtable_drafts`, now strictly per-user) so the two never mix. Backend router `/api/roundtable-task-drafts` (`GET /by-task/{id}` + `/by-task/{id}/latest`, `POST`, `GET/PUT/DELETE /{id}` — all user-agnostic); frontend client `roundtable-planning/api/task-drafts.ts`, and `useDraftPersistence`/`useRoundtableDrafts` route **all** CRUD to it when a `taskId` is present (else the personal `drafts.ts`). The matching **background jobs** are shared by task too: `roundtable_jobs.task_id` marks a task job; reads/dedup/stream/resume/cancel pass `?task_id=` so any user opening the task sees the same running 研讨 (progress/flowchart), and the executor writes the final report back to the task-draft store. Existing rows are moved over by `scripts/migrate_roundtable_task_drafts.py` (migration `20260618_01`). The history dropdown filters to the task and 新建 keeps the binding; (2) **Step-3 auto structured output** — after the `roundtable-summary` agent writes the Markdown report it automatically runs a second round emitting a `flow-json` flowchart (`useStep3Summary` `autoFlow`+`chainLevels`), whose hierarchy follows the business chain's stage goals (任务 → 目的 → 行为体 → …; coordinatorPrompt-specified hierarchy wins) and whose nodes never carry 阶段编号/阶段名 (the prior bug). **Both targets render embedded under the 任务管理 layout** — nested routes under `task-management/<business-path>/{roundtable,agent/:agent_id/chats}` in `StrategyRoutes.tsx` (`<business-path>` = `plan-task-sentiment` / `plan-task/plan-task-fx` / `plan-task/plan-task-action`, matching the COH中任务 menu paths in `sidebar-menu.ts`) so the global sidebar keeps 任务管理 → COH中任务 → the matching business highlighted. **All three single-agent businesses share `TaskFxWorkspace`** (`task-components/TaskFxWorkspace.tsx`): left = a「当前任务」card (`readCopTaskDetail()` 任务名称 + 简介, desensitized, 留存 above 新建对话) + per-task 1:N conversation list (`useTaskThreads(taskId, agentId, actionId?)` — filtered by **both** `metadata.taskId` AND `metadata.agent_id` so the three businesses' conversations staLine truncated
|
||||||
- **agentfx 任务深链(新系统 iframe 嵌入的智能体问答,3qfx 单智能体 lite 模式的独立复制)**: goPath=`agentfx`(`${loginBase}?password=…&taskId=…&goPath=agentfx&embed=1&hideJump=1&theme=dark-blue`)。与 3qfx 的差异:①任务详情不走外部 consumer,改调**本系统 Gateway** `GET {backend}/taskAnalyseSearch/cop-task-detail?taskId=`(`apiFetch` 带刚登录的 DeerFlow 态;任务数据来自 TaskCOP 任务表,与任务列表/报告导入同一份);②**固定使用内置智能体** `agentfx-analyst`(不走业务映射);③路由 `task-management/plan-task-agentfx/agent/:agent_id/chats`,页面为独立复制的 `AgentFxWorkspace.tsx`(`AgentFxWorkspace`/`AgentFxIndexResolver`/`AgentFxChat` —— 左「当前任务」卡 + 1:N 对话列表 + 新建对话,`AgentChatPage taskBind` 自动发开场白;无 TaskJumpBar/清除记录/闸门/xdfx 行动卡)。模块 `core/auth/agentfx-deeplink.ts`(`isAgentfxGoPath`/`fetchAgentfxTaskDetail`/`resolveAgentfxDeeplinkTarget`);任务详情整包仍存共享键 `login.copTaskDetail`(按标签页隔离),`AgentChatPage` 的 taskBind 自恢复对 agentfx 生效。`LoginPage` 在既有深链分支前用 `isAgentfxGoPath` 分流。**内置「任务研判报告助手」**:后端 seeder `app/gateway/routers/_agentfx_seed.py` + `_agentfx_seed_assets/`(config.yaml + SOUL.md,目录存在不覆盖;app.py lifespan 在 `_sync_legacy_agents` 之前调用,自动进 agents 表 user_id IS NULL)——SOUL 规定工作流「搜集信息(`web_search`,**首步不可跳过**)→ 每次检索 JSON 原样落盘 `/mnt/user-data/workspace/hits/qN.json` → **一条命令** `task-report-build`(group `all`)技能函数转换成 14 类 `task-reports.json`(模型禁止手写该 JSON;分页技能 `task-report-{enemy,our,env,judge}` 仅用于事后补跑单页;五者都是共享引擎 `task-report-lib/convert_lib.py` 的瘦 CLI,`resolve_hit_files` 对虚拟绝对路径读空的情况自动回退到相对写法并在 `warnings` 里列出尝试过的路径,免得智能体自己写脚本排查)→ `write_file` 生成 `/mnt/user-data/outputs/report.md` → 聊天总结 → 引导入库」,硬性约束:categoryType 逐字用 14 个全称(禁缩写自造如「DQ关键事件」)、JSON 直接写汉字禁 `\uXXXX` 转义、每类必须有实质内容不许空数组/纯占位、文件一律用 `/mnt/user-data/...` 虚拟路径(防宿主路径泄漏进 tool args)。**检索依赖 `config.yaml tools.web_search`(DDG,enabled 已开;内网需换 configurable_search 时改回并填 endpoint)**。任务工作区输入框在 `taskCtxOn` 时隐藏「写作/记忆/参考文献」(InputBox `hideContextTools` prop)与「知识空间」(不传 knowledgeSelector);消息流里 write_file 文件卡片只显示文件名(`artifactDisplayName` 收敛宿主/虚拟路径)。**入库两条路径**:(a) **前端协助卡** —— `AgentChatPage`(taskBind 且非 taskGate)检测线程 artifacts 出现 `task-reports.json`(按文件名后缀匹配,兼容本地沙箱记录的宿主路径)且流式结束时,向 displayThread 尾部注入 `additional_kwargs.task_report_import_approval` 虚拟 ai 消息(`core/messages/utils.ts` 分组 `assistant:task-report-import` + `MessageList` 的 `taskReportImportSlot` 插槽渲染 `TaskReportImportCard`),点「开始入库」前端校验(`strategy-components/lib/agentfx-report-schema.ts`,14 类 + contentJson 合法 JSON + **\u 转义归一化还原为汉字**,与技能脚本同规则)后 `importTaskReport`(`strategy-components/api/task-reports.ts`)POST 导入接口整体替换;任务已有详情**不抑制**卡片(入库即整体替换,卡片文案说明覆盖语义),导入成功按 thread 记 sessionStorage(刷新不重复询问)、「暂不入库」为会话内记忆;(b) **入库技能** `task-report-import`(seeder 一并复制到 `skills/public/`,public 类技能 extensions_config 缺省即启用)—— 用户聊天里说「入库」时智能体按 SKILL.md 用 bash 跑 `scripts/task_report_import.py`(纯标准库:同样的校验规则 + 转义归一化;**默认关闭 SSL 证书校验** `ssl._create_unverified_context` + 忽略代理,适配内网自签证书;鉴权 `--token`/env `AGENTFX_IMPORT_TOKEN`→`DEERFLOW_AUTH_TOKEN`→`TOOLS_TOKEN` 优先,缺省经公共端点 `POST /api/parallel-agents/auth/token` 以 `agentfx-import` 用户现签 token——内网既有鉴权旁路;gateway 地址 env `AGENTFX_GATEWAY_URL` 或 `http://127.0.0.1:{DEER_FLOW_GATEWAY_PORT}`);`--strict` 可要求 14 类齐全,默认允许部分导入并在 stdout JSON 的 `missing_categories` 里列缺。入库成功后任务 TaskCOP 状态自动置 25(已完成分析)。**入库成功通知宿主**(iframe 嵌入场景):两条路径成功后都会 `window.top.postMessage({source: "magent-web", type: "Line truncated
|
- **agentfx 任务深链(新系统 iframe 嵌入的智能体问答,3qfx 单智能体 lite 模式的独立复制)**: goPath=`agentfx`(`${loginBase}?password=…&taskId=…&goPath=agentfx&embed=1&hideJump=1&theme=dark-blue`)。与 3qfx 的差异:①任务详情不走外部 consumer,改调**本系统 Gateway** `GET {backend}/taskAnalyseSearch/cop-task-detail?taskId=`(`apiFetch` 带刚登录的 DeerFlow 态;任务数据来自 TaskCOP 任务表,与任务列表/报告导入同一份);②**固定使用内置智能体** `agentfx-analyst`(不走业务映射);③路由 `task-management/plan-task-agentfx/agent/:agent_id/chats`,页面为独立复制的 `AgentFxWorkspace.tsx`(`AgentFxWorkspace`/`AgentFxIndexResolver`/`AgentFxChat` —— 左「当前任务」卡 + 1:N 对话列表 + 新建对话,`AgentChatPage taskBind` 自动发开场白;无 TaskJumpBar/清除记录/闸门/xdfx 行动卡)。模块 `core/auth/agentfx-deeplink.ts`(`isAgentfxGoPath`/`fetchAgentfxTaskDetail`/`resolveAgentfxDeeplinkTarget`);任务详情整包仍存共享键 `login.copTaskDetail`(按标签页隔离),`AgentChatPage` 的 taskBind 自恢复对 agentfx 生效。`LoginPage` 在既有深链分支前用 `isAgentfxGoPath` 分流。**内置「任务研判报告助手」**:后端 seeder `app/gateway/routers/_agentfx_seed.py` + `_agentfx_seed_assets/`(config.yaml + SOUL.md,目录存在不覆盖;app.py lifespan 在 `_sync_legacy_agents` 之前调用,自动进 agents 表 user_id IS NULL)——SOUL 规定工作流「搜集信息(`web_search`,**首步不可跳过**)→ `write_file` 生成 `/mnt/user-data/outputs/report.md` + `task-reports.json`(14 个 categoryType 的 `{contentJson, categoryType}` 数组,contentJson 为 JSON 字符串)→ 聊天总结 → 引导入库」,硬性约束:categoryType 逐字用 14 个全称(禁缩写自造如「DQ关键事件」)、JSON 直接写汉字禁 `\uXXXX` 转义、每类必须有实质内容不许空数组/纯占位、文件一律用 `/mnt/user-data/...` 虚拟路径(防宿主路径泄漏进 tool args)。**检索依赖 `config.yaml tools.web_search`(DDG,enabled 已开;内网需换 configurable_search 时改回并填 endpoint)**。任务工作区输入框在 `taskCtxOn` 时隐藏「写作/记忆/参考文献」(InputBox `hideContextTools` prop)与「知识空间」(不传 knowledgeSelector);消息流里 write_file 文件卡片只显示文件名(`artifactDisplayName` 收敛宿主/虚拟路径)。**入库两条路径**:(a) **前端协助卡** —— `AgentChatPage`(taskBind 且非 taskGate)检测线程 artifacts 出现 `task-reports.json`(按文件名后缀匹配,兼容本地沙箱记录的宿主路径)且流式结束时,向 displayThread 尾部注入 `additional_kwargs.task_report_import_approval` 虚拟 ai 消息(`core/messages/utils.ts` 分组 `assistant:task-report-import` + `MessageList` 的 `taskReportImportSlot` 插槽渲染 `TaskReportImportCard`),点「开始入库」前端校验(`strategy-components/lib/agentfx-report-schema.ts`,14 类 + contentJson 合法 JSON + **\u 转义归一化还原为汉字**,与技能脚本同规则)后 `importTaskReport`(`strategy-components/api/task-reports.ts`)POST 导入接口整体替换;任务已有详情**不抑制**卡片(入库即整体替换,卡片文案说明覆盖语义),导入成功按 thread 记 sessionStorage(刷新不重复询问)、「暂不入库」为会话内记忆;(b) **入库技能** `task-report-import`(seeder 一并复制到 `skills/public/`,public 类技能 extensions_config 缺省即启用)—— 用户聊天里说「入库」时智能体按 SKILL.md 用 bash 跑 `scripts/task_report_import.py`(纯标准库:同样的校验规则 + 转义归一化;**默认关闭 SSL 证书校验** `ssl._create_unverified_context` + 忽略代理,适配内网自签证书;鉴权 `--token`/env `AGENTFX_IMPORT_TOKEN`→`DEERFLOW_AUTH_TOKEN`→`TOOLS_TOKEN` 优先,缺省经公共端点 `POST /api/parallel-agents/auth/token` 以 `agentfx-import` 用户现签 token——内网既有鉴权旁路;gateway 地址 env `AGENTFX_GATEWAY_URL` 或 `http://127.0.0.1:{DEER_FLOW_GATEWAY_PORT}`);`--strict` 可要求 14 类齐全,默认允许部分导入并在 stdout JSON 的 `missing_categories` 里列缺。入库成功后任务 TaskCOP 状态自动置 25(已完成分析)。**入库成功通知宿主**(iframe 嵌入场景):两条路径成功后都会 `window.top.postMessage({source: "magent-web", type: "task-report:saved", taskId: String(taskId)}, "*")`(`notifyTaskReportSaved`,`agentfx-report-schema.ts`)——卡片路径在 `TaskReportImportCard` 导入成功回调里直发;技能路径由 `AgentChatPage` 从消息流检测 `task_report_import.py` 的成功 stdout(`"success": true` + `task_id`)代发——**仅在回合结束后检测**(`!thread.isLoading` 才扫描;流式过程中 messages Line truncated
|
||||||
- **iframe 嵌入模式 (外部系统用 iframe 套用深链问答/会商)** — 三个**按标签页**开关,全部由深链 URL 参数开启、存进同一份 `sessionStorage` 会话(`embed.session`,键名常量在 `core/embed/embed-session.ts`),跨登录跳转/刷新/页内导航存活,**绝不跨标签污染**同浏览器另开的正常系统页。核心隔离手法:用 `sessionStorage`(按标签页/浏览上下文隔离,iframe 即便同源也有独立一份)而非 `localStorage`(跨标签共享)。开关:(1) **`?embed=1`(可带 `&theme=`)** — 去全局 chrome(`PageLayout` 的 `pageSidebarAllowed && !isEmbedActive()` → 隐藏 Header + PageSidebar)+ 按外部主题色**强制**明/暗:走 next-themes 的 `forcedTheme`(`App.tsx` `AppProviders` 调 `resolveForcedEmbedTheme`,**不写** localStorage `strategy-theme`),离散映射 `dark-blue`/`dark`→深、其余→浅;嵌入侧**绝不**调 `setTheme`/`persistSidebarEnabled`(那俩落 localStorage 跨标签污染,`LoginPage` 据 `readEmbedParam` 跳过)。(2) **`?hideJump=1`** — 隐藏深链底部「快捷跳转」栏 `TaskJumpBar` 的两部分(底部「下一步」等按钮 + 右侧垂直居中悬浮菜单),单智能体问答 + 会商共用该栏,`isJumpBarHidden()` 为真时整条 `return null`。(3) **登录用户隔离** — 嵌入态下鉴权/身份键(`deerflow.auth` token、`userInfo`、`login.*`、`tools_token`、maxkey/userDetail token)全部经 `core/auth/scoped-storage.ts` 的 `authStorage()`(嵌入→sessionStorage / 正常→localStorage)读写,使 iframe 里登录的用户与正常登录用户**完全隔离**、互不覆盖;所有 token 读取都过 `getStoredAuth()`/`getAuthorizationHeaderValue()`(API 客户端 `core/api/*` 全走这俩),改中心函数即让全体调用方自动隔离;跨标签账号变更监听 `AuthSyncWatcher`(`core/auth/sync.tsx`)在嵌入态禁用。嵌入会话在 `main.tsx` 渲染前 `syncEmbedSessionFromCurrentUrl()` 同步启用(早于任何鉴权写入,避免首帧 effect 顺序漏写 localStorage);`AppProviders` 的 effect 再按 SPA 导航同步。`embed=0`/`hideJump=0` 可显式关闭。正常(非嵌入)标签页所有 `isXxx()` 恒 false,行为**零变化**。第三个开关 `?isolate=1`(**独立登录会话**)把同一套**登录用户隔离**单独提供给**普通(带 chrome)标签页**——只切换鉴权存储、不去 chrome / 不强制主题 / 不动跳转栏:`authStorage()` 的条件从「仅嵌入」放宽为 `isEmbedActive() || isIsolatedSession()`,`AuthSyncWatcher` 的跨标签监听对嵌入态**或**独立态都即时忽略(handler 内实时判,运行时开关也即刻生效)。除 URL 参数外,**设置 → 外观 →「登录会话」** 有一个 Switch「独立登录会话(本标签页)」运行时切换(`setIsolatedSession`,`appearance-settings-page.tsx`):**开启时先 `migrateAuthToSession()`**(`scoped-storage.ts`,把 localStorage 的受保护键 `deerflow.auth`/`userInfo`/`userId`/`tools_token` + `login.*` 前缀快照搬进 sessionStorage)再翻标志,故无感不登出;此后本标签页登录态独立——别处登录/换号影响不到它,它换号也影响不到别人,但关掉该标签即结束(需重登)。开启给 toast 提示。
|
- **iframe 嵌入模式 (外部系统用 iframe 套用深链问答/会商)** — 三个**按标签页**开关,全部由深链 URL 参数开启、存进同一份 `sessionStorage` 会话(`embed.session`,键名常量在 `core/embed/embed-session.ts`),跨登录跳转/刷新/页内导航存活,**绝不跨标签污染**同浏览器另开的正常系统页。核心隔离手法:用 `sessionStorage`(按标签页/浏览上下文隔离,iframe 即便同源也有独立一份)而非 `localStorage`(跨标签共享)。开关:(1) **`?embed=1`(可带 `&theme=`)** — 去全局 chrome(`PageLayout` 的 `pageSidebarAllowed && !isEmbedActive()` → 隐藏 Header + PageSidebar)+ 按外部主题色**强制**明/暗:走 next-themes 的 `forcedTheme`(`App.tsx` `AppProviders` 调 `resolveForcedEmbedTheme`,**不写** localStorage `strategy-theme`),离散映射 `dark-blue`/`dark`→深、其余→浅;嵌入侧**绝不**调 `setTheme`/`persistSidebarEnabled`(那俩落 localStorage 跨标签污染,`LoginPage` 据 `readEmbedParam` 跳过)。(2) **`?hideJump=1`** — 隐藏深链底部「快捷跳转」栏 `TaskJumpBar` 的两部分(底部「下一步」等按钮 + 右侧垂直居中悬浮菜单),单智能体问答 + 会商共用该栏,`isJumpBarHidden()` 为真时整条 `return null`。(3) **登录用户隔离** — 嵌入态下鉴权/身份键(`deerflow.auth` token、`userInfo`、`login.*`、`tools_token`、maxkey/userDetail token)全部经 `core/auth/scoped-storage.ts` 的 `authStorage()`(嵌入→sessionStorage / 正常→localStorage)读写,使 iframe 里登录的用户与正常登录用户**完全隔离**、互不覆盖;所有 token 读取都过 `getStoredAuth()`/`getAuthorizationHeaderValue()`(API 客户端 `core/api/*` 全走这俩),改中心函数即让全体调用方自动隔离;跨标签账号变更监听 `AuthSyncWatcher`(`core/auth/sync.tsx`)在嵌入态禁用。嵌入会话在 `main.tsx` 渲染前 `syncEmbedSessionFromCurrentUrl()` 同步启用(早于任何鉴权写入,避免首帧 effect 顺序漏写 localStorage);`AppProviders` 的 effect 再按 SPA 导航同步。`embed=0`/`hideJump=0` 可显式关闭。正常(非嵌入)标签页所有 `isXxx()` 恒 false,行为**零变化**。第三个开关 `?isolate=1`(**独立登录会话**)把同一套**登录用户隔离**单独提供给**普通(带 chrome)标签页**——只切换鉴权存储、不去 chrome / 不强制主题 / 不动跳转栏:`authStorage()` 的条件从「仅嵌入」放宽为 `isEmbedActive() || isIsolatedSession()`,`AuthSyncWatcher` 的跨标签监听对嵌入态**或**独立态都即时忽略(handler 内实时判,运行时开关也即刻生效)。除 URL 参数外,**设置 → 外观 →「登录会话」** 有一个 Switch「独立登录会话(本标签页)」运行时切换(`setIsolatedSession`,`appearance-settings-page.tsx`):**开启时先 `migrateAuthToSession()`**(`scoped-storage.ts`,把 localStorage 的受保护键 `deerflow.auth`/`userInfo`/`userId`/`tools_token` + `login.*` 前缀快照搬进 sessionStorage)再翻标志,故无感不登出;此后本标签页登录态独立——别处登录/换号影响不到它,它换号也影响不到别人,但关掉该标签即结束(需重登)。开启给 toast 提示。
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|||||||
Submodule
+1
Submodule _skills_src/ai-skills added at cde1c8385e.
Submodule
+1
Submodule _skills_src/anthropics-skills added at 5754626092.
Submodule
+1
Submodule _skills_src/awesome-claude-skills added at 92568c1eda.
Submodule
+1
Submodule _skills_src/ce-kit added at d4973c4827.
Submodule
+1
Submodule _skills_src/csm added at 3fa16a94e0.
Submodule
+1
Submodule _skills_src/superpowers added at 6fd4507659.
Submodule
+1
Submodule _skills_src/tapestry-skills added at 80e1dc56df.
@@ -1,117 +0,0 @@
|
|||||||
# Wiki 数据流转与本地编码验收(2026-09-03)
|
|
||||||
|
|
||||||
## 结论
|
|
||||||
|
|
||||||
本地 BGE-M3 小批量编码已安装、配置并实际运行。已有 Wiki 的向量化、自动沉淀、
|
|
||||||
普通库文件导出、助手库异步导入、助手库文件导出、空普通库回流及两端向量检索已实测通过。
|
|
||||||
**技能自动生成 Wiki 尚未完成验收**:WeKnora 的两个生成模型都返回 HTTP 402
|
|
||||||
`Insufficient Balance`。不能把后半段通过等同于全链路通过。
|
|
||||||
|
|
||||||
本次修改运行在本地 DeerFlow 开发服务(前端 5175、后端 8001),连接用户提供的远端
|
|
||||||
WeKnora;没有把业务代码发布到服务器,没有提交 Git,也没有直接写 WeKnora 图数据库。
|
|
||||||
|
|
||||||
## 本地编码配置
|
|
||||||
|
|
||||||
- 实现:FastEmbed / ONNX Runtime CPU,官方 `BAAI/bge-m3` 权重。
|
|
||||||
- 配置名:`bge-embedding-m3`,1024 维,每批 2 条,2 个 CPU 推理线程。
|
|
||||||
- 运行依赖:FastEmbed 随标准后端依赖安装;模型必须提前放在离线目录中,仍按需加载,不影响主服务启动。
|
|
||||||
- 权重:约 2.3 GB,持久缓存 `C:\Users\ji\.cache\deerflow\embeddings`,不进入业务仓库。
|
|
||||||
- 配置:`llmwiki.local_wiki_index.embedding.provider: local`;可用
|
|
||||||
`local_model_path` 指定离线权重目录,详见后端 README。
|
|
||||||
- 查询向量在本地实时编码。已有 Wiki 在内容哈希、模型指纹匹配且向量非空时直接复用。
|
|
||||||
不同模型或维度不能混用;导入不静默重新编码。
|
|
||||||
- 原始文档分段向量与模型生成后的 Wiki 文本不同,不能相互冒用。本次新建的是 Wiki
|
|
||||||
正文的 BGE-M3 索引,不是把既有 512 维文档向量改名为 1024 维。
|
|
||||||
|
|
||||||
## 实际数据验收
|
|
||||||
|
|
||||||
来源是已有普通知识库“图片测试111”,没有改写其 Wiki 内容。
|
|
||||||
|
|
||||||
| 阶段 | Wiki | Wiki 向量 | 实体 | 引用关系 |
|
|
||||||
| --- | ---: | ---: | ---: | ---: |
|
|
||||||
| 普通知识库导出 | 19 | 101 | 19 | 108 |
|
|
||||||
| 助手知识库导入后导出 | 19 | 101 | 19 | 108 |
|
|
||||||
| 新建普通知识库回流后导出 | 19 | 101 | 19 | 108 |
|
|
||||||
| 知识梳理总库导出(7 个来源记录) | 19 | 101 | 19 | 108 |
|
|
||||||
|
|
||||||
逐项断言通过:
|
|
||||||
|
|
||||||
1. 三份包的 Wiki slug、标题、正文、摘要、页面类型、别名、分类路径、出链一致。
|
|
||||||
2. 101 条向量的原始 float32 字节、分段编号、维度和模型指纹完全一致。
|
|
||||||
3. 将导入前后实体 ID 映射回原 Wiki slug 后,108 条关系的端点及谓词集合完全一致。
|
|
||||||
4. 总库导出没有因多个来源重复同步而重复导出同一当前页面的分段向量。
|
|
||||||
5. 元数据回归测试另覆盖 tags、目录恢复、原始文档片段不计入 Wiki 数量、原生技能无
|
|
||||||
ownership 记录时可被选中、短 Wiki 不漏向量、模型指纹不兼容时不假装向量检索。
|
|
||||||
|
|
||||||
普通包约 1.01 MB,助手包约 0.79 MB,回流包约 0.83 MB。普通包还带 2 个原始文档及
|
|
||||||
108 个原始片段;助手导出提供 Wiki、Wiki 向量和关系,不重新上传原始文档触发二次生成。
|
|
||||||
|
|
||||||
### 检索
|
|
||||||
|
|
||||||
实际查询:“军方用什么工具评估极端天气带来的风险?”
|
|
||||||
|
|
||||||
- 回流普通库 `/api/llmwiki/search` 返回 `mode=vector`,首条“国防气候评估工具”。
|
|
||||||
- 助手库 `/api/assistant-knowledge/search` 返回 `retrieval_mode=vector`,首条同上;
|
|
||||||
首个命中分段余弦相似度约 0.6343。
|
|
||||||
- 浏览器实际提交总库检索,显示“向量检索 · 余弦相似度”和相同命中;不是只检查返回字段。
|
|
||||||
- 普通库采用已有页面级聚合评分,因此页面最终分数与单分段余弦值不必相等。
|
|
||||||
- 回流的向量写入 **DeerFlow 管理的本地 Wiki 索引**。未写入 WeKnora 原生向量数据库,
|
|
||||||
本次结论适用于本系统问答检索,不能声称原生 WeKnora 自身的检索也已完成该向量导入。
|
|
||||||
|
|
||||||
### 页面
|
|
||||||
|
|
||||||
普通与助手库复用同一 Wiki 阅读组件:目录树、类型/标签筛选、分类面包屑、别名标签、
|
|
||||||
正文内链、出链、反向链接及邻接关系视图。窄窗口可收起目录。
|
|
||||||
|
|
||||||
浏览器验证了“索引 → 国防气候评估工具”、展开关系视图、助手 Wiki 跳回普通库来源,
|
|
||||||
以及普通 Wiki 的“在知识梳理中查看”反向跳转。
|
|
||||||
|
|
||||||
### 验收数据位置
|
|
||||||
|
|
||||||
- 原有普通库:`48f669dc-433e-4aab-b5ea-897b6f7a3d2f`
|
|
||||||
- 知识梳理总库:`c3fd9841-0718-44dc-860e-aae3020fd485`
|
|
||||||
- 最终助手测试库:`489be905-08c8-40fe-8d60-42ac21dd945a`
|
|
||||||
- 最终普通回流测试库:`428162aa-03f6-4c61-8994-360f2f39e0c9`
|
|
||||||
|
|
||||||
新建测试库名称均以“Wiki链路验收-0903-1826”开头,保留用于复核;未删除用户已有库。
|
|
||||||
本地 `.runtime/wiki-live-result.json` 保存验收结果,`.runtime/wiki_live_acceptance.py`
|
|
||||||
保存本次验收流程,均为忽略的运行产物,不包含认证口令。
|
|
||||||
|
|
||||||
## 这次实际修复的问题
|
|
||||||
|
|
||||||
- 内置技能只有磁盘目录、没有归属记录时,被错误判定为不存在。
|
|
||||||
- 技能应上传去代码后的知识正文,让 WeKnora 生成 Wiki,而不是伪造一个 `skill` 类型页面。
|
|
||||||
- Wiki 生成等待使用实际 stats 接口;零页结果明确失败,不无期限等待。
|
|
||||||
- 向量化完成后自动导出沉淀到总库,并阻断导出回调重复触发全量同步。
|
|
||||||
- 短摘要和短正文同时存在时,旧分段阈值会将整页过滤为零向量。
|
|
||||||
- 分类路径必须先创建 WeKnora folder 并绑定 folder_id;只传 category_path 会被服务端清空。
|
|
||||||
- Wiki 的标签/分类/别名/原始引用保留;图节点绑定真实 Wiki,避免生成重复的空实体页面。
|
|
||||||
- 保留归一化 float32 原始字节;导入中不能用 `section_index or index`,因为合法编号 0 会被改写。
|
|
||||||
- 总库多个来源保留审计,但检索与导出只使用当前 Wiki 版本对应的向量,避免旧贡献污染检索。
|
|
||||||
- 包解析改为同盘临时 SQLite,上传先落盘再异步导入,导出完成后才提供文件下载。
|
|
||||||
|
|
||||||
## 阻塞与未通过的范围
|
|
||||||
|
|
||||||
1. 技能 `knowledge-base-ingest` 已送入专门创建的普通测试库,但远端
|
|
||||||
`deepseek-v4-flash` 和 `qwen3.6-flash` 均因余额不足失败。WeKnora 即使失败也可能把
|
|
||||||
文档 parse_status 标为 completed。需恢复可用生成模型后重新运行技能全链路验收。
|
|
||||||
2. 已采用磁盘暂存和有界批处理,但本次只有约 1 MB 实测,**未做数 GB 压测、断点续传、
|
|
||||||
进程中断恢复验收**。大包部署仍需磁盘容量、代理 body size 和超时配置。
|
|
||||||
3. 当前实体去重主要按类型及规范化名称;这次没有完成跨别名、歧义实体的模型对齐及
|
|
||||||
多来源事实冲突合并验收,不能视为这一历史需求已全面完成。
|
|
||||||
4. 重复上传仍会保留独立来源记录。向量导出已消除旧版本重复,但上传身份幂等、部分远端
|
|
||||||
写入失败后的无损重试、跨实例图片资源打包仍需独立验证。
|
|
||||||
5. 当前版本已补充自动增量链路:服务启动立即补扫,之后默认每 30 秒扫描全部普通
|
|
||||||
知识库;Wiki 抽取完成后无需手工点击向量化,并自动以来源快照覆盖同步到知识梳理总库。
|
|
||||||
6. 回流当前只允许新建空 Wiki 普通库,避免覆盖已有页面。普通库现有页面列表的超大规模
|
|
||||||
分页与助手问答的最终生成回答不属于这轮通过结论。
|
|
||||||
|
|
||||||
## 自动化检查
|
|
||||||
|
|
||||||
- 后端相关测试:118 passed(assistant repository、Wiki sync、package roundtrip、
|
|
||||||
WeKnora 接口、向量检索与知识库管理)。
|
|
||||||
- 修改的 Python 功能文件 Ruff 检查通过;app.py 既有启动顺序导致的 E402 不在此次改动中处理。
|
|
||||||
- `git diff --check` 通过(只有仓库换行提示)。
|
|
||||||
- 前端完整 `pnpm typecheck` 未通过:57 个既有/本轮范围外错误,涉及 nextra 缺失、Markdown
|
|
||||||
插件类型及其他模块;本轮新建/修改的 Wiki 组件、助手 API、技能面板没有出现在错误清单中。
|
|
||||||
浏览器实际页面与跳转已验证,但不能声称全项目构建通过。
|
|
||||||
@@ -1,25 +1,13 @@
|
|||||||
# AgentScope 2.0 多智能体报告协作工作台——前端实施计划
|
# AgentScope 2.0 多智能体报告协作工作台——前端实施计划
|
||||||
|
|
||||||
> 文档状态:前端代码已落地、契约冻结;联调等待后端运行中干预与报告改写
|
> 文档状态:待评审、尚未开始编码
|
||||||
> 编写日期:2026-09-02;进度更新日期:2026-09-06
|
> 编写日期:2026-09-02
|
||||||
> 目标项目:`F:\react01\deeflow-code\deerflow-server\frontend-web`
|
> 目标项目:`F:\react01\deeflow-code\deerflow-server\frontend-web`
|
||||||
> 视觉与交互参考:`F:\react01\coze-frontend\frontend\apps\coze-studio`
|
> 视觉与交互参考:`F:\react01\coze-frontend\frontend\apps\coze-studio`
|
||||||
> 参考页面:`http://localhost:3000/work_flow?space_id=1&workflow_id=wf_88cb9f4c6642`
|
> 参考页面:`http://localhost:3000/work_flow?space_id=1&workflow_id=wf_88cb9f4c6642`
|
||||||
> 配套后端计划:`docs/agentscope-多智能体报告协作工作台-后端实施计划.md`
|
> 配套后端计划:`docs/agentscope-多智能体报告协作工作台-后端实施计划.md`
|
||||||
> 本文只规划新增页面,不修改现有多智能体会商页面,不复用旧 Workflow Studio 执行状态。
|
> 本文只规划新增页面,不修改现有多智能体会商页面,不复用旧 Workflow Studio 执行状态。
|
||||||
|
|
||||||
## 0. 当前进度
|
|
||||||
|
|
||||||
`frontend-web/src/report-collaboration/` 已实现两栏工作台、会话/方案/run API 客户端、mock driver、SSE reducer、`MessageList` 适配、方案卡、画布、节点抽屉、Composer、报告预览与历史。`RC-FE-001`~`013` 的页面骨架与契约层视为完成。
|
|
||||||
|
|
||||||
后端已通规划前路径(澄清 → 出方案 → 选定 → `queued` run),见后端计划 §0。前端真实执行体验仍受阻:
|
|
||||||
|
|
||||||
- `GET /runs/{id}/stream` 已挂载(`RC-BE-011`);dispatcher/租约已落地(`RC-BE-012`),默认 `worker_enabled=false` 仍不自动领取 queued run。
|
|
||||||
- 报告改写 / 恢复接口已由后端 `RC-BE-015` 挂载。
|
|
||||||
- 质量角色已由 `QualityRoleKernel` 跑通(`RC-BE-013`);真实 AgentScope 模型/`web_search` 仍待后续票。
|
|
||||||
|
|
||||||
联调顺序:先打开 `report_collaboration.enabled` 验证规划前 REST 与 `GET /runs/{id}/stream` 重放;画布自动执行需同时打开 `worker_enabled`(默认关)。
|
|
||||||
|
|
||||||
## 1. 前端目标
|
## 1. 前端目标
|
||||||
|
|
||||||
新增独立的「报告协作」工作台。用户可以通过对话提出任意报告需求,查看系统生成的多个协作方案,点击方案卡片在流程图中预览协作结构,确认后观察多智能体执行,并在全过程通过统一对话框提出修改意见。
|
新增独立的「报告协作」工作台。用户可以通过对话提出任意报告需求,查看系统生成的多个协作方案,点击方案卡片在流程图中预览协作结构,确认后观察多智能体执行,并在全过程通过统一对话框提出修改意见。
|
||||||
@@ -674,8 +662,6 @@ POST /api/report-collaboration/sessions/{session_id}/report-rewrites/{id}/appl
|
|||||||
|
|
||||||
## 9. 前端开发顺序
|
## 9. 前端开发顺序
|
||||||
|
|
||||||
页面与契约层已完成(代码在 `frontend-web/src/report-collaboration/`):
|
|
||||||
|
|
||||||
1. `RC-FE-001`:视觉截图与行为基线。
|
1. `RC-FE-001`:视觉截图与行为基线。
|
||||||
2. `RC-FE-002`:路由、菜单和左对话/右画布两栏空壳。
|
2. `RC-FE-002`:路由、菜单和左对话/右画布两栏空壳。
|
||||||
3. `RC-FE-003`:页面 token、弹窗、抽屉和响应式。
|
3. `RC-FE-003`:页面 token、弹窗、抽屉和响应式。
|
||||||
@@ -692,17 +678,15 @@ POST /api/report-collaboration/sessions/{session_id}/report-rewrites/{id}/appl
|
|||||||
|
|
||||||
依赖规则:
|
依赖规则:
|
||||||
|
|
||||||
- 契约已冻结为 `src/report-collaboration/api/types.ts`;后端 `RC-BE-001` 已对齐。
|
- `RC-FE-004` 必须等待前后端契约冻结。
|
||||||
- 真实 SSE 联调:后端 `RC-BE-011` 已挂载 `GET /runs/{id}/stream`;默认无后台循环时流只会重放已入库事件。
|
- 页面骨架和视觉可与后端 BE-0~BE-3 并行。
|
||||||
- 报告版本操作已接后端 `RC-BE-015`(改写候选 / apply / restore)。
|
- 真实 SSE 联调必须等待后端 durable event store。
|
||||||
- 执行画布实时状态依赖打开 `worker_enabled`;质量角色内核已在 `RC-BE-013` 落地,真实成员见 `RC-BE-017`。
|
- 报告版本操作必须等待后端候选应用语义完成。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 前端完成定义
|
## 10. 前端完成定义
|
||||||
|
|
||||||
页面与 mock 联调路径已落地。真实后端可联调 `GET /runs/{id}/stream`、领取/恢复(`RC-BE-012`,默认不挂 worker)、质量角色(`RC-BE-013`)以及报告改写/恢复(`RC-BE-015`)。此前以 mock driver 覆盖交互,不以「前端未编码」计未完成。
|
|
||||||
|
|
||||||
前端只有同时满足以下条件才算完成:
|
前端只有同时满足以下条件才算完成:
|
||||||
|
|
||||||
1. 页面结构与 Coze 参考页面逐状态验收通过。
|
1. 页面结构与 Coze 参考页面逐状态验收通过。
|
||||||
|
|||||||
@@ -1,481 +0,0 @@
|
|||||||
# 报告协作工作台 — 前端对接指南
|
|
||||||
|
|
||||||
> **适用版本:** RC-BE-000~018 已落地(2026-09-06)
|
|
||||||
> **配套文档:**
|
|
||||||
> - [前端实施计划](./agentscope-多智能体报告协作工作台-前端实施计划.md)
|
|
||||||
> - [后端实施计划](./agentscope-多智能体报告协作工作台-后端实施计划.md)
|
|
||||||
> - 模块 README:`frontend-web/src/report-collaboration/README.md`
|
|
||||||
|
|
||||||
本文说明**前端如何对接真实后端**:环境配置、API 契约、用户流程、状态机、错误处理与联调验收。前端 API 客户端与页面已实现,通常**无需再写一套 HTTP 层**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 结论速览
|
|
||||||
|
|
||||||
| 问题 | 答案 |
|
|
||||||
|------|------|
|
|
||||||
| 前端还要写接口吗? | **不用**。`src/report-collaboration/api/` 已封装 REST + SSE,页面通过 `getReportCollaborationDriver()` 调用。 |
|
|
||||||
| 为什么调不通? | 最常见:`config.yaml` 里 `report_collaboration.enabled: false` → 全部 **503**;或 `worker_enabled: false` → run 不入队执行。 |
|
|
||||||
| Mock 和真后端怎么切? | **Mock 已移除**。前端永远请求真实后端(`deerflow.rc-mock` / `VITE_RC_MOCK` 开关已删除);联调需 `enabled: true`。 |
|
|
||||||
| 字段会和 MySQL 对不上吗? | 前端只认 JSON 契约(`api/types.ts`),不直连表。缺列表现为后端 500/SQL 错误,部署后需**重启 Gateway** 跑 `create_all` + 列同步。 |
|
|
||||||
| 从哪进页面? | `/page/workspace/report-collaboration`(新会话)或 `/page/workspace/report-collaboration/:sessionId`。侧边栏入口默认注释,可直接 URL 访问。 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 架构:前端已有什么
|
|
||||||
|
|
||||||
```
|
|
||||||
ReportCollaborationPage
|
|
||||||
└─ useReportCollaborationSession(唯一数据入口)
|
|
||||||
├─ driver.getSnapshot() ← REST 冷启动
|
|
||||||
├─ driver.openRunStream() ← SSE 实时事件
|
|
||||||
├─ driver.sendMessage / createCommand / startRun …
|
|
||||||
└─ event-reducer ← 事件 → UI 状态
|
|
||||||
|
|
||||||
ReportConversationPanel
|
|
||||||
└─ MessageList(message-adapter 映射 CollaborationMessage → LangGraph Message)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.1 目录结构
|
|
||||||
|
|
||||||
```text
|
|
||||||
frontend-web/src/report-collaboration/
|
|
||||||
api/
|
|
||||||
types.ts # 冻结契约(snake_case,与后端 Pydantic 一致)
|
|
||||||
client.ts # rcFetch:鉴权、幂等键、乐观锁、错误映射
|
|
||||||
sessions.ts # 会话 CRUD、消息
|
|
||||||
plans.ts # 方案请求 / 列表 / 选定
|
|
||||||
runs.ts # Run、SSE、命令、节点合同
|
|
||||||
reports.ts # 报告版本、改写、恢复
|
|
||||||
driver.ts # 真实后端统一门面(Mock 驱动已移除)
|
|
||||||
state/
|
|
||||||
event-normalizer.ts
|
|
||||||
event-reducer.ts
|
|
||||||
selectors.ts
|
|
||||||
message-adapter.ts
|
|
||||||
hooks/
|
|
||||||
useReportCollaborationSession.ts # 快照 → SSE → reducer → 动作
|
|
||||||
useReportCollaborationStream.ts # 断线重连、after_seq 续接
|
|
||||||
useReportCollaborationMessages.ts
|
|
||||||
components/ …
|
|
||||||
pages/ReportCollaborationPage.tsx
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 事实源原则
|
|
||||||
|
|
||||||
- **冷启动:** `GET /sessions/{id}` 返回 `SessionSnapshot`(session、messages、plans、run、nodes、agent_runs、commands、report_versions、last_seq)。
|
|
||||||
- **运行中:** `GET /runs/{id}/stream?after_seq=N` 订阅 durable SSE。
|
|
||||||
- **断线 / 序号缺口:** `GET /runs/{id}/events?after_seq=N` REST 补拉。
|
|
||||||
- **澄清 / 规划阶段(尚无 run):** 每 2.5s 轮询快照(后端事件主要在 run 流上)。
|
|
||||||
|
|
||||||
纯展示状态(预览方案、聚焦成员、面板宽度)**不**写入服务器,仅存前端。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 后端启用与部署
|
|
||||||
|
|
||||||
### 3.1 必改配置
|
|
||||||
|
|
||||||
文件:`offline-backend-20260512/backend/config.yaml`
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
report_collaboration:
|
|
||||||
enabled: false # 联调必须改为 true
|
|
||||||
worker_enabled: false # 要看成员执行必须 true
|
|
||||||
planner_model: null # 建议填可用模型名
|
|
||||||
coordinator_model: null
|
|
||||||
research_model: null
|
|
||||||
writer_model: null
|
|
||||||
reviewer_model: null
|
|
||||||
# … token_budget、max_concurrent_runs_per_user 等见同文件
|
|
||||||
```
|
|
||||||
|
|
||||||
| 配置 | 现象 |
|
|
||||||
|------|------|
|
|
||||||
| `enabled: false` | 所有路由 **503**,`code: REPORT_COLLABORATION_DISABLED` |
|
|
||||||
| `enabled: true`,`worker_enabled: false` | REST/SSE 可用,run 入队但不自动执行(测 API 可用,无成员流式输出) |
|
|
||||||
| 两者 `true` + 模型配置完整 | 完整联调 |
|
|
||||||
|
|
||||||
### 3.2 MySQL / 表结构
|
|
||||||
|
|
||||||
生产环境 Alembic 自动升级默认关闭,启动时走 `create_all` + `_ensure_orm_columns_sync`。
|
|
||||||
|
|
||||||
- 迁移脚本(可选手动跑):`20260905_01`、`20260906_01`、`20260907_01`
|
|
||||||
- ORM:`packages/harness/deerflow/persistence/report_collaboration/model.py`(13 张表)
|
|
||||||
- **部署后务必重启 Gateway**,观察日志是否有 `Failed to auto-add column report_collaboration_*`
|
|
||||||
|
|
||||||
已有 RC-003 时代旧表会通过列同步补上 `template_snapshot_json`、`budget_snapshot_json`、`usage_json`、`budget_exhausted_reason` 等可空列,并创建 `rewrite_proposals`、`audits`。
|
|
||||||
|
|
||||||
### 3.3 前端环境变量
|
|
||||||
|
|
||||||
| 变量 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `VITE_BACKEND_BASE_URL` | Gateway 地址,默认 `http://127.0.0.1:8001` |
|
|
||||||
|
|
||||||
请求经 `apiFetch`,自动携带登录 JWT(`credentials: "include"`)。**必须先登录 DeerFlow**。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. API 基址与请求约定
|
|
||||||
|
|
||||||
### 4.1 Base URL
|
|
||||||
|
|
||||||
```text
|
|
||||||
{VITE_BACKEND_BASE_URL}/api/report-collaboration
|
|
||||||
```
|
|
||||||
|
|
||||||
代码常量:`REPORT_COLLABORATION_BASE()`(`api/client.ts`)。
|
|
||||||
|
|
||||||
### 4.2 请求头
|
|
||||||
|
|
||||||
| Header | 何时携带 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| `Authorization` | 全部 | 由 `apiFetch` 注入 |
|
|
||||||
| `X-Idempotency-Key` | 写操作 | 格式 `rc-{uuid}`;重复提交返回首次结果 |
|
|
||||||
| `X-Expected-Revision` | 选方案、开 run、改会话等 | 会话 `requirement_revision` 或方案 `revision` |
|
|
||||||
| `Accept: text/event-stream` | SSE | `openRunStream` |
|
|
||||||
|
|
||||||
### 4.3 错误响应格式
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "REPORT_COLLABORATION_DISABLED",
|
|
||||||
"message": "报告协作工作台未启用",
|
|
||||||
"detail": { }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
前端映射(`api/client.ts`):
|
|
||||||
|
|
||||||
| HTTP | 异常类 | 典型场景 |
|
|
||||||
|------|--------|----------|
|
|
||||||
| 409 | `ConflictError` | revision 过期、active run 冲突 |
|
|
||||||
| 422 | `ValidationError` | 参数/语义错误、缺幂等键 |
|
|
||||||
| 429 | `RateLimitError` | 预算耗尽、每用户并发 run 上限 |
|
|
||||||
| 5xx | `ServerError` | 可带同一幂等键重试 |
|
|
||||||
| 0 | `NetworkError` | fetch 失败 |
|
|
||||||
|
|
||||||
### 4.4 完整路由清单(与前端 client 对齐)
|
|
||||||
|
|
||||||
| 方法 | 路径 | 前端模块 |
|
|
||||||
|------|------|----------|
|
|
||||||
| POST | `/sessions` | `sessions.createSession` |
|
|
||||||
| GET | `/sessions` | `sessions.listSessions` |
|
|
||||||
| GET | `/sessions/{id}` | `sessions.getSessionSnapshot` |
|
|
||||||
| PATCH | `/sessions/{id}` | `sessions.updateSession` |
|
|
||||||
| DELETE | `/sessions/{id}` | `sessions.deleteSession` |
|
|
||||||
| GET | `/sessions/{id}/messages` | `sessions.listMessages` |
|
|
||||||
| POST | `/sessions/{id}/messages` | `sessions.sendMessage` |
|
|
||||||
| POST | `/sessions/{id}/plan-requests` | `plans.requestPlans` |
|
|
||||||
| GET | `/sessions/{id}/plans` | `plans.listPlans` |
|
|
||||||
| POST | `/sessions/{id}/plans/{planId}/select` | `plans.selectPlan` |
|
|
||||||
| POST | `/sessions/{id}/runs` | `runs.startRun` |
|
|
||||||
| GET | `/sessions/{id}/reports` | `reports.listReportVersions` |
|
|
||||||
| POST | `/sessions/{id}/report-rewrites` | `reports.createRewrite` |
|
|
||||||
| POST | `/sessions/{id}/report-rewrites/{id}/apply` | `reports.applyRewrite` |
|
|
||||||
| POST | `/sessions/{id}/report-versions/{id}/restore` | `reports.restoreReportVersion` |
|
|
||||||
| GET | `/runs/{id}` | `runs.getRun` |
|
|
||||||
| GET | `/runs/{id}/events` | `runs.listRunEvents` |
|
|
||||||
| GET | `/runs/{id}/stream` | `runs.openRunStream` |
|
|
||||||
| POST | `/runs/{id}/cancel` | `runs.cancelRun` |
|
|
||||||
| POST | `/runs/{id}/commands` | `runs.createCommand` |
|
|
||||||
| POST | `/runs/{id}/commands/{id}/confirm` | `runs.confirmCommand` |
|
|
||||||
| POST | `/runs/{id}/commands/{id}/cancel` | `runs.cancelCommand` |
|
|
||||||
| GET | `/runs/{id}/nodes/{nodeId}/contract` | `runs.getNodeContract` |
|
|
||||||
| GET | `/runs/{id}/artifacts` | (后端已暴露,前端按需接) |
|
|
||||||
| GET | `/runs/{id}/sources` | (后端已暴露,前端按需接) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 用户流程与 API 时序
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant U as 用户
|
|
||||||
participant FE as 前端 RC
|
|
||||||
participant API as Gateway
|
|
||||||
participant W as Worker
|
|
||||||
|
|
||||||
U->>FE: 输入需求
|
|
||||||
FE->>API: POST /sessions
|
|
||||||
FE->>API: POST /sessions/{id}/messages
|
|
||||||
API-->>FE: 澄清(快照 messages)
|
|
||||||
U->>FE: 回答澄清
|
|
||||||
FE->>API: POST /messages
|
|
||||||
FE->>API: POST /plan-requests
|
|
||||||
API-->>FE: 候选方案(快照 plans)
|
|
||||||
U->>FE: 采用方案
|
|
||||||
FE->>API: POST /plans/{id}/select
|
|
||||||
U->>FE: 开始协作
|
|
||||||
FE->>API: POST /runs
|
|
||||||
FE->>API: GET /runs/{id}/stream
|
|
||||||
W-->>API: 持久化事件
|
|
||||||
API-->>FE: SSE
|
|
||||||
U->>FE: 运行中干预
|
|
||||||
FE->>API: POST /commands → confirm
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.1 阶段 0:进入页面
|
|
||||||
|
|
||||||
| 路由 | 行为 |
|
|
||||||
|------|------|
|
|
||||||
| `/page/workspace/report-collaboration` | 空态;`POST /sessions` 后跳转带 `sessionId` |
|
|
||||||
| `/page/workspace/report-collaboration/:sessionId` | 加载快照;有 `location.state.rcInitialMessage` 时自动发首条消息 |
|
|
||||||
|
|
||||||
路由注册:`frontend-web/src/pages/WorkspaceRoutes.tsx`。
|
|
||||||
侧边栏入口已在 `workspace-nav-chat-list.tsx` 中放开(「报告协作」项),也可直接访问 URL。
|
|
||||||
|
|
||||||
### 5.2 阶段 1:需求澄清(无 run)
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/report-collaboration/sessions
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/messages
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{ "text": "请写一份…", "target_node_id": null, "agent_run_id": null }
|
|
||||||
```
|
|
||||||
|
|
||||||
- `useReportCollaborationSession.send()`:当 `session.status` 不是 `running` / `awaiting_input` / `reviewing` 时走 `sendMessage`。
|
|
||||||
- 会话状态:`empty` → `clarifying` → `planning` → `proposal_ready`。
|
|
||||||
- 澄清问题以 `CollaborationMessage` 进入时间线;业务卡通过 `MessageList` 的 `businessCardSlot` 渲染(`additional_kwargs.report_collaboration_card`)。
|
|
||||||
|
|
||||||
### 5.3 阶段 2:候选方案
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/plan-requests
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/plans/{planId}/select
|
|
||||||
```
|
|
||||||
|
|
||||||
- **预览方案**(`previewPlanId`):纯前端,不调 API。
|
|
||||||
- **采用方案**:`select` 写 `selected_plan_id`,**不**创建 run。
|
|
||||||
- 409:方案 revision 过期 → 前端 `refreshSnapshot`,提示用户重选。
|
|
||||||
- 右栏 `ReportFlowCanvas` 使用 `PlanNode` / `PlanEdge` 渲染 DAG。
|
|
||||||
|
|
||||||
### 5.4 阶段 3:开始执行与 SSE
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/runs
|
|
||||||
{ "plan_id": "plan-xxx" }
|
|
||||||
|
|
||||||
GET /api/report-collaboration/runs/{runId}/stream?after_seq=0
|
|
||||||
Accept: text/event-stream
|
|
||||||
```
|
|
||||||
|
|
||||||
SSE 每条 `data:` 为 JSON:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface CollaborationEvent {
|
|
||||||
eventId: string;
|
|
||||||
sessionId: string;
|
|
||||||
runId?: string | null;
|
|
||||||
seq: number;
|
|
||||||
type: CollaborationEventType;
|
|
||||||
timestamp: string;
|
|
||||||
data: unknown;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
常用 `type`(完整列表见 `api/types.ts`):
|
|
||||||
|
|
||||||
| 类型 | 含义 |
|
|
||||||
|------|------|
|
|
||||||
| `message.created` / `message.delta` / `message.completed` | 对话镜像流式 |
|
|
||||||
| `tool_call.*` / `tool_result.*` | 工具调用与返回 |
|
|
||||||
| `team_message.*` | 成员 / 协调者广播 |
|
|
||||||
| `plan.proposed` / `plan.selected` | 方案生命周期 |
|
|
||||||
| `run.status.changed` | Run 状态 |
|
|
||||||
| `node.status.changed` / `node.progress` | 节点执行 |
|
|
||||||
| `command.confirmation.required` | 需用户确认的干预 |
|
|
||||||
| `report.version.created` | 报告版本落盘 |
|
|
||||||
| `heartbeat` | 保活 |
|
|
||||||
|
|
||||||
### 5.5 阶段 4:运行中干预
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/report-collaboration/runs/{runId}/commands
|
|
||||||
POST /api/report-collaboration/runs/{runId}/commands/{commandId}/confirm
|
|
||||||
POST /api/report-collaboration/runs/{runId}/commands/{commandId}/cancel
|
|
||||||
POST /api/report-collaboration/runs/{runId}/cancel
|
|
||||||
```
|
|
||||||
|
|
||||||
- Run 活跃时 `send()` 自动走 `createCommand`(而非 `sendMessage`)。
|
|
||||||
- 可从节点抽屉 / `AgentStageSelector` 传入 `target_node_id`、`agent_run_id`。
|
|
||||||
- `InterventionCard` 展示 `confirmation_required` 命令;确认后调 `confirmCommand`。
|
|
||||||
|
|
||||||
`CommandIntent` 枚举(后端分类,前端展示 reason/impact):`answer_question`、`update_requirements`、`research_again`、`rewrite_section`、`rerun_node`、`replan`、`cancel` 等。
|
|
||||||
|
|
||||||
### 5.6 阶段 5:报告版本与改写
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/report-collaboration/sessions/{sessionId}/reports
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/report-rewrites
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/report-rewrites/{rewriteId}/apply
|
|
||||||
POST /api/report-collaboration/sessions/{sessionId}/report-versions/{versionId}/restore
|
|
||||||
```
|
|
||||||
|
|
||||||
- `createRewrite` 只生成候选,不直接覆盖正式版本。
|
|
||||||
- `applyRewrite` / `restoreReportVersion` 产生新的 head 版本,历史保留。
|
|
||||||
- `ReportPreviewDialog` 展示 `ReportVersion.markdown`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 前端状态机(接入方须知)
|
|
||||||
|
|
||||||
**不要**在页面里用零散 REST 字段拼 UI 状态。统一路径:
|
|
||||||
|
|
||||||
```text
|
|
||||||
SessionSnapshot → applySessionSnapshot()
|
|
||||||
CollaborationEvent[] → normalizeCollaborationEvent() → applyCollaborationEvents()
|
|
||||||
→ selectors(derivePagePhase、selectPlanForCanvas、…)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.1 核心 Selector
|
|
||||||
|
|
||||||
| Selector | 用途 |
|
|
||||||
|----------|------|
|
|
||||||
| `derivePagePhase` | Composer 占位、方案卡、空态 |
|
|
||||||
| `selectPlanForCanvas` | 画布 DAG(含预览方案) |
|
|
||||||
| `selectAgentTabs` | 并行成员 Tab |
|
|
||||||
| `selectNodeStatusMap` | 节点颜色 / 状态 |
|
|
||||||
| `selectPendingConfirmationCommands` | 干预确认卡 |
|
|
||||||
| `selectHeadReportVersion` | 报告预览入口 |
|
|
||||||
| `selectStreamingAgentRunIds` | 流式成员指示 |
|
|
||||||
|
|
||||||
### 6.2 SSE 连接策略(`useReportCollaborationStream`)
|
|
||||||
|
|
||||||
- Run 非终态时连接;`completed` / `failed` / `cancelled` 后关流并 REST 最终补拉。
|
|
||||||
- 断线:指数退避重连(1s~30s),`after_seq` 取 reducer 内 `lastSeq`。
|
|
||||||
- `hasSeqGap`:暂停 live 应用,先 `listRunEvents` 回填。
|
|
||||||
|
|
||||||
### 6.3 乐观消息
|
|
||||||
|
|
||||||
用户发送时插入 `metadata.optimistic: true` 的 pending 消息;服务端镜像带相同 `idempotency_key` 时 reducer 原地替换。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 消息与 MessageList 复用
|
|
||||||
|
|
||||||
`useReportCollaborationMessages` 将 `CollaborationMessage[]` 转为 `@langchain/langgraph-sdk` 的 `Message[]`,并构造合成 `BaseStream` 供 `MessageList` 消费。
|
|
||||||
|
|
||||||
| 来源 | 渲染 |
|
|
||||||
|------|------|
|
|
||||||
| `human` / `ai` 文本 | 普通气泡 |
|
|
||||||
| `tool_calls` + `tool` | 现有工具卡 |
|
|
||||||
| `team_message` 事件 | 带 `name` 的成员发言 |
|
|
||||||
| 业务卡 | `businessCardSlot`(方案组、命令确认、报告可用) |
|
|
||||||
|
|
||||||
分组键:`core/messages/utils.ts` 中 `assistant:task-report-import` 同级扩展 `assistant:business-card`,判据 `additional_kwargs.report_collaboration_card`。
|
|
||||||
|
|
||||||
### 7.1 在其他页面嵌入 RC(最小集)
|
|
||||||
|
|
||||||
1. `useReportCollaborationSession(sessionId)`
|
|
||||||
2. `ThreadContext.Provider` + `MessageList` + `businessCardSlot`
|
|
||||||
3. 引入 `report-collaboration/styles/report-collaboration.css`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 单测
|
|
||||||
|
|
||||||
Mock 驱动(原 `api/mock.ts`)与 `deerflow.rc-mock` / `VITE_RC_MOCK` 开关已移除,前端只请求真实后端;本地演示请直接联调后端。
|
|
||||||
|
|
||||||
单测:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend-web
|
|
||||||
pnpm test:report-collaboration
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 联调验收清单
|
|
||||||
|
|
||||||
### 9.1 准备
|
|
||||||
|
|
||||||
- [ ] `report_collaboration.enabled: true`
|
|
||||||
- [ ] `report_collaboration.worker_enabled: true`
|
|
||||||
- [ ] 五个 `*_model` 已配置且可用
|
|
||||||
- [ ] Gateway 已重启(MySQL 表/列就绪)
|
|
||||||
- [ ] 前端已登录(JWT 有效)
|
|
||||||
|
|
||||||
### 9.2 探活
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -H "Authorization: Bearer <token>" \
|
|
||||||
http://127.0.0.1:8001/api/report-collaboration/sessions
|
|
||||||
```
|
|
||||||
|
|
||||||
期望:**200** + `{ "sessions": [...] }`,而非 503。
|
|
||||||
|
|
||||||
### 9.3 页面流程(浏览器 Network)
|
|
||||||
|
|
||||||
1. 打开 `/#/page/workspace/report-collaboration`
|
|
||||||
2. 输入需求 → `POST .../sessions` **200**
|
|
||||||
3. 跳转 → `GET .../sessions/{id}` **200**,响应含 `SessionSnapshot`
|
|
||||||
4. 首条消息 → `POST .../messages` **200**
|
|
||||||
5. 请求方案 → `POST .../plan-requests` **200**
|
|
||||||
6. 采用方案 → `POST .../plans/{id}/select` **200**
|
|
||||||
7. 开始协作 → `POST .../runs` **200**
|
|
||||||
8. `GET .../runs/{id}/stream` 持续收到 SSE(`message.delta`、`node.status.changed` 等)
|
|
||||||
9. 刷新页面 → 消息与 `last_seq` 续接正确
|
|
||||||
10. 运行中输入「重新检索」→ `POST .../commands` → 确认卡 → `confirm` **200**
|
|
||||||
|
|
||||||
### 9.4 常见失败
|
|
||||||
|
|
||||||
| 现象 | 原因 | 处理 |
|
|
||||||
|------|------|------|
|
|
||||||
| 全部 503 | `enabled: false` | 改配置并重启 |
|
|
||||||
| run 一直 queued | `worker_enabled: false` | 改为 true |
|
|
||||||
| 409 选方案 | revision 过期 | 刷新后重选(前端已自动 refresh) |
|
|
||||||
| 429 | 预算/并发上限 | 调大配置或取消其他 run |
|
|
||||||
| 500 + SQL | 表缺列 | 重启 Gateway,查列同步日志 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 契约与类型维护
|
|
||||||
|
|
||||||
- **单一事实源:** `frontend-web/src/report-collaboration/api/types.ts`
|
|
||||||
- **后端镜像:** `offline-backend-20260512/backend/app/report_collaboration/contracts/`
|
|
||||||
- 修改字段须同步两份实施计划与本指南,并跑前后端相关测试。
|
|
||||||
|
|
||||||
`SessionSnapshot` 形状(摘要):
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface SessionSnapshot {
|
|
||||||
session: ReportSession;
|
|
||||||
messages: CollaborationMessage[];
|
|
||||||
plans: ReportPlanCandidate[];
|
|
||||||
run: ReportRun | null;
|
|
||||||
nodes: CollaborationNodeState[];
|
|
||||||
agent_runs: AgentRun[];
|
|
||||||
commands: CollaborationCommand[];
|
|
||||||
report_versions: ReportVersion[];
|
|
||||||
last_seq: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`ReportRun` 可选字段(RC-BE-016):`usage`、`budget_exhausted_reason`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 后续工作(非对接阻塞)
|
|
||||||
|
|
||||||
| 项 | 说明 |
|
|
||||||
|----|------|
|
|
||||||
| RC-BE-019 | 灰度发布与离线部署文档 |
|
|
||||||
| 侧边栏入口 | 已放开(`workspace-nav-chat-list.tsx` 的「报告协作」项) |
|
|
||||||
| iframe 嵌入 | RC 页尚未专门适配 `embed=1`,可参考现有深链模式扩展 |
|
|
||||||
| 真人盲评 | RC-BE-018 为确定性评测集,上线前仍需计划 §11.4 人工评审 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. 相关代码索引
|
|
||||||
|
|
||||||
| 用途 | 路径 |
|
|
||||||
|------|------|
|
|
||||||
| API 客户端 | `frontend-web/src/report-collaboration/api/` |
|
|
||||||
| 会话 Hook | `frontend-web/src/report-collaboration/hooks/useReportCollaborationSession.ts` |
|
|
||||||
| 页面 | `frontend-web/src/report-collaboration/pages/ReportCollaborationPage.tsx` |
|
|
||||||
| 路由 | `frontend-web/src/pages/WorkspaceRoutes.tsx` |
|
|
||||||
| 后端路由 | `offline-backend-20260512/backend/app/gateway/routers/report_collaboration.py` |
|
|
||||||
| 配置 | `offline-backend-20260512/backend/config.yaml` → `report_collaboration` |
|
|
||||||
| 持久化 | `offline-backend-20260512/backend/packages/harness/deerflow/persistence/report_collaboration/` |
|
|
||||||
@@ -1,44 +1,11 @@
|
|||||||
# AgentScope 2.0 多智能体报告协作工作台——后端实施计划
|
# AgentScope 2.0 多智能体报告协作工作台——后端实施计划
|
||||||
|
|
||||||
> 文档状态:实施中(`RC-BE-000`~`018` 已完成;下一票 `RC-BE-019`)
|
> 文档状态:待评审、尚未开始编码
|
||||||
> 编写日期:2026-09-05;进度更新日期:2026-09-06
|
> 编写日期:2026-09-02
|
||||||
> 目标项目:`F:\react01\deeflow-code\deerflow-server\offline-backend-20260512\backend`
|
> 目标项目:`F:\react01\deeflow-code\deerflow-server\offline-backend-20260512\backend`
|
||||||
> 协作框架:[AgentScope 2.0](https://github.com/agentscope-ai/agentscope)
|
> 协作框架:[AgentScope 2.0](https://github.com/agentscope-ai/agentscope)
|
||||||
> 前端契约来源:`frontend-web/src/report-collaboration/api/types.ts`、`api/mock.ts`、`state/event-reducer.ts`(前端已完成,契约冻结)
|
> 配套前端计划:`docs/agentscope-多智能体报告协作工作台-前端实施计划.md`
|
||||||
> 尖峰基线:`offline-backend-20260512/backend/docs/AGENTSCOPE_BASELINE.md`
|
|
||||||
> 本文只规划新增的报告协作后端,不修改现有多智能体会商,不使用旧 Workflow Studio DAG 调度器。
|
> 本文只规划新增的报告协作后端,不修改现有多智能体会商,不使用旧 Workflow Studio DAG 调度器。
|
||||||
> 下文 §8 的 `BE-n` 与 §10 的 `RC-BE-00n` 是同一批工单的两种编号;进度以 `RC-BE-*` 为准。
|
|
||||||
|
|
||||||
## 0. 当前进度
|
|
||||||
|
|
||||||
已打通 **建会话 → 澄清需求 → 生成 2~3 套候选方案 → 选定 → 创建 `queued` run(冻结模型 + 模板 + 预算)**,以及 **TaskLedger 波次调度 + 受控成员 / 真实 AgentScope 内核 + 主张级 QualityGate + 正式报告版本/改写 + 产品 durable SSE + 租约 dispatcher/恢复 + 运行期自然语言干预 + owner/预算/审计**。主开关默认关闭:`config.yaml → report_collaboration.enabled` / `worker_enabled` 均为 `false`。
|
|
||||||
|
|
||||||
运行期 IntentRouter 的歧义兜底已可调用冻结的 coordinator 模型,但明确命令走高精度规则;该智能体无工具且不能调度。`POST /runs` 仍只入队。`worker_enabled` 为 false 时 lifespan **不**挂载后台循环;测试用 `dispatch_once()` / `WaveExecutor` 驱动。`GET /runs/{id}/stream` 可重放/尾随已持久化事件。生产 worker 在双开时注入 `AgentScopeMemberKernel`。20 项质量评测已在 `RC-BE-018` 落地(确定性打分,不跑真实模型)。
|
|
||||||
|
|
||||||
| 工单 | 状态 | 代码落点 / 缺口 |
|
|
||||||
|---|---|---|
|
|
||||||
| `RC-BE-000` | 条件完成 | `agentscope_runtime/event_adapter.py`。`agentscope==2.0.5` 仅为 optional extra,**未写入 `uv.lock`**(等纵向 POC)。 |
|
|
||||||
| `RC-BE-001` | 完成 | `app/report_collaboration/contracts/` + `execution/completion.py`。 |
|
|
||||||
| `RC-BE-002` | 完成 | `deerflow.config.report_collaboration_config`;默认双关。 |
|
|
||||||
| `RC-BE-003` | 完成 | `deerflow.persistence.report_collaboration/`,迁移 `20260905_01`。 |
|
|
||||||
| `RC-BE-004` | 完成 | `/api/report-collaboration`。改写 / apply / restore 见 `015`。SSE 见 `011`。 |
|
|
||||||
| `RC-BE-005` | 完成 | `requirement/`:规划前澄清;「直接开始」记默认假设;澄清完成 ≠ 出方案。 |
|
|
||||||
| `RC-BE-006` | 完成 | `planning/`:目录投影(无 SOUL/密钥)+ 三策略方案;select 与 start-run 独立。 |
|
|
||||||
| `RC-BE-007` | 完成 | `DeerFlowChatModelAdapter` + `freeze_plan_models`;缺模型/能力不匹配在 `POST /runs` 前 422。 |
|
|
||||||
| `RC-BE-008` | 完成 | `permission_adapter.py` + `tool_adapter.py`:角色 × 工具矩阵、注入扫描。 |
|
|
||||||
| `RC-BE-009` | 完成 | `execution/ledger.py` + `member_runner.py`:TaskLedger 按依赖波次分派;AgentScope/协调者建议不能选下一节点;空输出/非法 JSON 进入新 attempt;超限失败不完成整个 run。内核目前为 `ScriptedMemberKernel`(单测),真实模型/web_search 仍待接。 |
|
|
||||||
| `RC-BE-010` | 完成 | `execution/quality_gate.py` + `ArtifactBoard.supersede_previous`:冲突数字/缺口径拒绝;未核验或争议主张不能进分析/章节;审稿必须映射到节点或章节;覆盖矩阵缺口拒绝。Ledger 先 schema 再质检,通过才 `validated`。 |
|
|
||||||
| `RC-BE-011` | 完成 | `execution/live_hub.py`:run-scoped Live Hub、`PublishingReportCollaborationStore`(先入库再发布)、`after_seq`/`Last-Event-ID` 重放、心跳、token delta 合帧、SSE 断开不取消 run。`GET /runs/{id}/stream` 已挂载。报告正式版本见 `015`。 |
|
|
||||||
| `RC-BE-012` | 完成 | store `list_claimable` / `renew_lease` / `finalize_cancel`;`execution/{dispatcher,executor,recovery}.py`:原子 claim、心跳续租、两阶段取消、过期租约接管。恢复保留已完成节点,遗留 in-flight 标 `interrupted`/`superseded` 并开新 `planned` attempt。晚到成果 `validation_status=superseded`,不改终态。`start()` 仅在 `worker_enabled` 时挂循环;默认双关。 |
|
|
||||||
| `RC-BE-013` | 完成 | `execution/quality_roles.py`:Verifier/Analyst/Writer/Reviewer/Reviser 作为受控成员(确定性内核,非 AgentScope Team)。核验标记过期/跨包冲突;缺失角度只重开检索链;审稿 `revise` 只重开目标写作节点 + 审稿自身。`TaskResult.additional_drafts` 允许多条 VerificationResult。脚本内核仍供单测;生产 worker 见 `017`。 |
|
|
||||||
| `RC-BE-014` | 完成 | `interventions/`:运行期 IntentRouterAgent(规则优先、模型兜底且无工具)+ 确定性 ImpactAnalyzer + command CAS/safe-point。支持节点/agent 归属校验、角度定向、确认闸门、等待边界、overlay、新 attempt、成果失效及晚到结果隔离。 |
|
|
||||||
| `RC-BE-015` | 完成 | `reporting/`:run 开始冻结模板;`ReportFinalizer` 在审稿通过且 lint 后落正式版本并发 `report.version.created`;改写候选 / apply / restore 已挂路由,不再 503。 |
|
|
||||||
| `RC-BE-016` | 完成 | owner 隔离、跨会话幂等拒绝、`sanitize_for_user` + 审计落库、每用户 active run 上限、每 run 预算 fail-closed。 |
|
|
||||||
| `RC-BE-017` | 完成 | `AgentScopeMemberKernel` 真实 `reply_stream` + 工具 + 结构化成果;生产 worker 注入该内核;冒烟可跳过未安装的 `agentscope`。并发交错/租约恢复仍由 `012` 测试覆盖。 |
|
|
||||||
| `RC-BE-018` | 完成 | `app/report_collaboration/eval/`:20 项标注任务(政策/市场/企业/事件/对比各 4)、确定性指标、金标/失败实例、会商盲评对照、20 次固定任务可靠性计数。`cite_required` 与当前默认 knobs 为通过评测的推荐画像。真实模型盲评仍属上线决策,不阻塞本票。 |
|
|
||||||
| `RC-BE-019` | 未开始 | 灰度与离线发版。 |
|
|
||||||
|
|
||||||
当前可联调路径:打开 `report_collaboration.enabled` 后,规划前轮询 `GET /sessions/{id}` 即可看到澄清消息和方案卡;`POST /runs` 只入队;`GET /runs/{id}/stream` 重放已入库事件。默认 **不**自动领取 queued run(`worker_enabled=false`)。测试可调 `dispatch_once()`。
|
|
||||||
|
|
||||||
## 1. 后端目标
|
## 1. 后端目标
|
||||||
|
|
||||||
@@ -47,7 +14,7 @@
|
|||||||
1. 理解任意报告需求并在必要时澄清。
|
1. 理解任意报告需求并在必要时澄清。
|
||||||
2. 从当前用户可见的智能体、技能、模型和资料源中筛选能力。
|
2. 从当前用户可见的智能体、技能、模型和资料源中筛选能力。
|
||||||
3. 生成 2~3 个真实有差异的候选协作计划。
|
3. 生成 2~3 个真实有差异的候选协作计划。
|
||||||
4. 将选定计划实例化为受 `TaskLedger` 约束的 AgentScope 任务成员,而非让自由会商自行决定流程。
|
4. 将选定计划实例化为 AgentScope leader-worker 团队。
|
||||||
5. 通过结构化成果物完成检索、核验、分析、综合、写作、审稿和返工。
|
5. 通过结构化成果物完成检索、核验、分析、综合、写作、审稿和返工。
|
||||||
6. 接受全过程自然语言干预,并只重开受影响的任务和下游成果。
|
6. 接受全过程自然语言干预,并只重开受影响的任务和下游成果。
|
||||||
7. 使用数据库租约、durable events、幂等命令和版本化报告保证恢复与一致性。
|
7. 使用数据库租约、durable events、幂等命令和版本化报告保证恢复与一致性。
|
||||||
@@ -56,27 +23,11 @@
|
|||||||
|
|
||||||
框架能提供协作机制,不能单独保证报告质量。质量由证据合同、来源核验、独立审稿、定向返工、模板约束和真实评测集共同保证。
|
框架能提供协作机制,不能单独保证报告质量。质量由证据合同、来源核验、独立审稿、定向返工、模板约束和真实评测集共同保证。
|
||||||
|
|
||||||
### 1.1 前端冻结契约:后端必须适配,不要求前端回退
|
|
||||||
|
|
||||||
已完成的双栏前端以 `SessionSnapshot` 和 run-scoped durable SSE 为唯一事实来源;后端实现不得另造一套摘要卡或节点结果接口来补足过程。
|
|
||||||
|
|
||||||
| 前端已实现行为 | 后端适配要求 |
|
|
||||||
|---|---|
|
|
||||||
| run 创建前轮询 `GET /sessions/{id}` | 澄清、需求修订、候选方案与选择结果必须同步写入完整快照;不能只存在于内存任务。 |
|
|
||||||
| run 后以 `seq` 重放/订阅 SSE | 每个 run 的事件先持久化、再发布,`seq` 严格单调且可用 `after_seq` 补拉。 |
|
|
||||||
| 并行成员横向阶段卡 | 同一并行波次使用相同 `phase_id`;`node_run_id` 固定为 `${node_id}-attempt{attempt}`,不可使用前端无法反查节点的随机值。 |
|
|
||||||
| 复用 `MessageList` 展示过程 | assistant、TeamSay、工具调用、工具结果与流式增量均写入 `report_collaboration_messages` 并按原消息协议推送。 |
|
|
||||||
| 成员状态来自局部快照合并 | 前端没有 `agent_run.*` 事件;成员开始、等待输入、完成、失败、取消时必须发 `session.snapshot: {agent_runs:[...], run?:...}`。 |
|
|
||||||
| 报告在对话流中渐显 | 写作阶段发 `report.delta`;质量门和版本落库完成后才发 `report.version.created`。 |
|
|
||||||
| 节点抽屉只读 | `GET /runs/{id}/nodes/{node_id}/contract` 只返回职责合同,永不返回执行结果正文。 |
|
|
||||||
|
|
||||||
每个写操作读取前端发送的 `X-Idempotency-Key` 与 `X-Expected-Revision`;用户消息的服务端镜像必须将同一幂等键放入 `metadata.idempotency_key`,以替换前端乐观消息而不重复显示。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 关键架构决策
|
## 2. 关键架构决策
|
||||||
|
|
||||||
### 2.1 AgentScope 用作任务执行内核,不把 Agent Team 自主会商当作调度器
|
### 2.1 使用 AgentScope Core,不采用演示服务作为事实源
|
||||||
|
|
||||||
AgentScope 官方提供 Event System、Agent Team、任务规划和 Agent Service 示例,但团队可靠性与多进程/分布式服务仍在持续完善:
|
AgentScope 官方提供 Event System、Agent Team、任务规划和 Agent Service 示例,但团队可靠性与多进程/分布式服务仍在持续完善:
|
||||||
|
|
||||||
@@ -85,16 +36,13 @@ AgentScope 官方提供 Event System、Agent Team、任务规划和 Agent Servic
|
|||||||
- [Agent Team 可靠性跟踪](https://github.com/agentscope-ai/agentscope/issues/1802)
|
- [Agent Team 可靠性跟踪](https://github.com/agentscope-ai/agentscope/issues/1802)
|
||||||
- [多进程与分布式能力跟踪](https://github.com/agentscope-ai/agentscope/issues/1722)
|
- [多进程与分布式能力跟踪](https://github.com/agentscope-ai/agentscope/issues/1722)
|
||||||
|
|
||||||
因此采用下列职责分界:
|
因此采用:
|
||||||
|
|
||||||
- AgentScope Core:单个受控任务成员的模型调用、流式事件、工具调用、权限封装与结构化输出修复。
|
- AgentScope Core:Agent、Team、Tool、Event、Permission。
|
||||||
- DeerFlow `TaskLedger`:计划实例化、依赖拓扑、并发波次、输入成果选择、状态迁移、重试、租约、取消、命令安全点与业务终态。
|
|
||||||
- DeerFlow `ArtifactBoard` / `QualityGate`:成果物事实源、验收、失效传播、引用约束和报告版本准入。
|
|
||||||
- `Coordinator`:受限的规划/协调智能体,只能提出 `PlanProposal`、`TaskAssignment`、`RepairRequest` 和 `ClarificationRequest`;它不能自行创建任意图、改变数据库或宣告节点完成。
|
|
||||||
- AgentScope TeamSay:允许作为可见的协作交接文本,但不是任务分派、完成判定或数据依赖的事实来源。
|
|
||||||
- DeerFlow:身份、权限、配置、数据库、消息、命令、作业、租约、SSE、成果、文件、报告版本和审计。
|
- DeerFlow:身份、权限、配置、数据库、消息、命令、作业、租约、SSE、成果、文件、报告版本和审计。
|
||||||
- AgentScope Event 通过无损 adapter 转为 DeerFlow 领域事件和 LangChain 兼容消息;用户可见内容不能在 adapter 中压缩为进度摘要。
|
- AgentScope Event 既驱动运行状态,也通过无损 adapter 转为 DeerFlow 领域事件和 LangChain 兼容消息;其用户可见内容不能在 adapter 中被压缩为进度摘要。
|
||||||
- 不运行 AgentScope `examples/web_ui`,不使用 AgentScope Session、MessageBus 或内存对象作为恢复/重放依据,也不使用其 session SSE 作为产品 SSE。
|
- 不运行 AgentScope `examples/web_ui`。
|
||||||
|
- 不把 AgentScope Session、MessageBus 或内存对象作为恢复依据。
|
||||||
- 节点公开接口只返回职责合同、图关系和状态,不返回节点实际输出;实际阶段消息、工具过程和成果仍从会话消息流以原生消息、工具步骤、引用和报告文件卡的形式呈现。
|
- 节点公开接口只返回职责合同、图关系和状态,不返回节点实际输出;实际阶段消息、工具过程和成果仍从会话消息流以原生消息、工具步骤、引用和报告文件卡的形式呈现。
|
||||||
|
|
||||||
### 2.2 新功能独立,不进入旧运行内核
|
### 2.2 新功能独立,不进入旧运行内核
|
||||||
@@ -115,7 +63,7 @@ AgentScope 官方提供 Event System、Agent Team、任务规划和 Agent Servic
|
|||||||
- 展示当前状态和返工范围。
|
- 展示当前状态和返工范围。
|
||||||
- 供用户理解和选择方案。
|
- 供用户理解和选择方案。
|
||||||
|
|
||||||
真正调度由 `TaskLedger + ArtifactBoard + QualityGate` 完成。协调者只能提出服务端可验证的建议;`TaskLedger` 才能执行分派、依赖解锁、重试和终态迁移,不能返回任意 Python、任意图或数据库操作。
|
真正调度由 `Coordinator + TaskLedger + ArtifactBoard + QualityGate` 完成。协调者只能从服务端允许的动作中选择,不能返回任意 Python、任意图或数据库操作。
|
||||||
|
|
||||||
### 2.4 数据库是唯一事实源
|
### 2.4 数据库是唯一事实源
|
||||||
|
|
||||||
@@ -141,12 +89,12 @@ flowchart LR
|
|||||||
API --> DISP[Lease Dispatcher]
|
API --> DISP[Lease Dispatcher]
|
||||||
|
|
||||||
DISP --> EXEC[Report Collaboration Executor]
|
DISP --> EXEC[Report Collaboration Executor]
|
||||||
|
EXEC --> AS[AgentScope Core]
|
||||||
|
AS --> LEAD[Coordinator]
|
||||||
|
AS --> TEAM[Research/Verify/Analyze/Write/Review Team]
|
||||||
|
|
||||||
EXEC --> LEDGER[Task Ledger]
|
EXEC --> LEDGER[Task Ledger]
|
||||||
EXEC --> BOARD[Typed Artifact Board]
|
EXEC --> BOARD[Typed Artifact Board]
|
||||||
LEDGER --> COORD[Constrained Coordinator]
|
|
||||||
LEDGER --> AS[AgentScope Core Task Workers]
|
|
||||||
COORD --> LEDGER
|
|
||||||
AS --> TEAM[Research / Verify / Analyze / Write / Review]
|
|
||||||
EXEC --> MODEL[DeerFlow Model Adapter]
|
EXEC --> MODEL[DeerFlow Model Adapter]
|
||||||
EXEC --> TOOL[Allowlisted Tool Adapter]
|
EXEC --> TOOL[Allowlisted Tool Adapter]
|
||||||
|
|
||||||
@@ -342,59 +290,6 @@ completed -> reopened -> queued
|
|||||||
- 期望成果物落库、Schema 通过、验收器通过后才能完成。
|
- 期望成果物落库、Schema 通过、验收器通过后才能完成。
|
||||||
- 返工创建新 attempt,保留旧 attempt 和旧成果。
|
- 返工创建新 attempt,保留旧 attempt 和旧成果。
|
||||||
|
|
||||||
### 5.6 `TaskLedger`:智能体协作的确定性边界
|
|
||||||
|
|
||||||
每个计划节点被实例化为一条可审计的 ledger task;并行只是多个 ready task 位于同一波次,不是让多个智能体在群聊中自行决定谁做什么。
|
|
||||||
|
|
||||||
```text
|
|
||||||
TaskAssignment
|
|
||||||
node_id / node_run_id / phase_id / attempt
|
|
||||||
requirement_revision
|
|
||||||
allowed_input_artifact_ids[]
|
|
||||||
expected_artifact_type + acceptance_criteria
|
|
||||||
agent_profile_snapshot + allowed_tools + budgets
|
|
||||||
command_overlay_revision
|
|
||||||
|
|
||||||
TaskResult
|
|
||||||
artifact_draft | clarification_request | repair_request | failure
|
|
||||||
cited_artifact_ids[]
|
|
||||||
source_ids[]
|
|
||||||
```
|
|
||||||
|
|
||||||
调度规则:
|
|
||||||
|
|
||||||
1. `PlanAssembler` 将角色建议转为固定节点、边、成果合同和质量门;只允许无环主图。
|
|
||||||
2. `TaskLedger` 仅在依赖的成果物均为 `validated`、未 supersede 且预算可用时使节点 ready。
|
|
||||||
3. 每个成员只读 requirement revision、自己的 `TaskAssignment` 与允许的已验证上游成果;不能把其他成员的原始上下文、工具返回或内部 prompt 当作输入。
|
|
||||||
4. 并行节点共享 `phase_id`,但各自有独立 `agent_run_id`;串行节点用独立 `phase_id`。成员的 `node_run_id` 一律为 `${node_id}-attempt{attempt}`。
|
|
||||||
5. 只有 `TaskResult` 转为成果物并通过 `QualityGate` 后,ledger 才解锁下游;TeamSay 与普通文本没有解锁能力。
|
|
||||||
6. 修复/返工创建新 attempt;旧 attempt 及其消息留存但被标为 `superseded`,不得重新进入下游。
|
|
||||||
|
|
||||||
### 5.7 用户命令的约束化协议
|
|
||||||
|
|
||||||
每句话先持久化为用户消息和 `CollaborationCommand`,再由独立的**意图理解智能体(`IntentRouterAgent`)**进行两段式判定:规则/上下文高精度路由优先,必要时调用模型并输出受 Pydantic 约束的 `IntentDecision`。它负责理解,不负责调度或执行。
|
|
||||||
|
|
||||||
同一能力有两种受控入口:
|
|
||||||
|
|
||||||
| 时机 | `IntentRouterAgent` 的工作 | 后续处理 |
|
|
||||||
|---|---|---|
|
|
||||||
| 尚未开始 run | 提取主题、目标、受众、范围、时间、角度、模板和来源约束;判断真正缺失的信息 | `RequirementResolver` 写 requirement revision;`PlanController` 生成候选计划。 |
|
|
||||||
| run 正在执行或已有报告 | 结合当前选中的 node/agent、等待澄清、成果血缘和报告章节,识别用户是在回答、补角度、重检索、重分析、重跑、改章节、润色、查询还是改规划 | `ImpactAnalyzer` 计算实际影响;`TaskLedger` 在确认和安全点后执行。 |
|
|
||||||
|
|
||||||
输入包含用户文本、session/run 状态、最新 requirement、当前 `target_node_id` / `agent_run_id`、awaiting clarification、当前报告版本和可见成果摘要;不得输入其他成员的内部 prompt、隐藏推理或无权访问的资料。
|
|
||||||
|
|
||||||
```text
|
|
||||||
IntentDecision
|
|
||||||
intent / confidence / normalized_instruction
|
|
||||||
scope: conversation | node | artifact_lineage | section | plan
|
|
||||||
requested_target_ids[] / requirement_patch?
|
|
||||||
requires_confirmation / cost_level / reason
|
|
||||||
```
|
|
||||||
|
|
||||||
服务端必须验证 `target_node_id`、`agent_run_id` 属于当前 session/run,再解析到 node 和成果物血缘;不可相信客户端传入的任意 ID。`ImpactAnalyzer` 产出实际 `impact_nodes`、失效成果和新 attempt 计划,用户所见的命令状态由它和 `TaskLedger` 写入,而非由协调者文本决定。
|
|
||||||
|
|
||||||
`IntentRouterAgent` 的置信度不足、目标不唯一、涉及 requirement/plan revision、预计成本为 high 或影响多个并行分支时,必须返回 `requires_confirmation=true`;只有用户确认后 `TaskLedger` 才可执行。对于处于 `awaiting_input` 的节点,明确回答优先分类为 `answer_question`,只解锁该节点的依赖链。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 数据库设计
|
## 6. 数据库设计
|
||||||
@@ -405,9 +300,9 @@ IntentDecision
|
|||||||
| `report_collaboration_messages` | 对话原文镜像 | message_id、role、content、tool_calls_json、tool_call_id、agent_run_id、node_run_id、phase_id、visibility、metadata、command_id、created_at、completed_at |
|
| `report_collaboration_messages` | 对话原文镜像 | message_id、role、content、tool_calls_json、tool_call_id、agent_run_id、node_run_id、phase_id、visibility、metadata、command_id、created_at、completed_at |
|
||||||
| `report_collaboration_plans` | 候选及修订 | proposal_group_id、revision、status、graph_json、validation_json |
|
| `report_collaboration_plans` | 候选及修订 | proposal_group_id、revision、status、graph_json、validation_json |
|
||||||
| `report_collaboration_runs` | 正式执行 | plan_id、status、lease_owner、lease_until、cancel_requested、next_event_seq |
|
| `report_collaboration_runs` | 正式执行 | plan_id、status、lease_owner、lease_until、cancel_requested、next_event_seq |
|
||||||
| `report_collaboration_node_runs` | 节点尝试/ledger task | node_id、attempt、`node_run_id=${node_id}-attempt{attempt}`、status、contract_json、input_artifact_ids、command_overlay_revision、agent_snapshot、error、started_at、ended_at |
|
| `report_collaboration_node_runs` | 节点尝试 | node_id、attempt、status、contract_json、agent_snapshot、error、started_at、ended_at |
|
||||||
| `report_collaboration_agent_runs` | 实际团队成员实例 | agent_run_id、node_run_id、phase_id、role、display_name、avatar_key、status、agent_snapshot、started_at、ended_at |
|
| `report_collaboration_agent_runs` | 实际团队成员实例 | agent_run_id、node_run_id、phase_id、role、display_name、avatar_key、status、agent_snapshot、started_at、ended_at |
|
||||||
| `report_collaboration_artifacts` | 成果物及血缘 | artifact_type、producer_node_run_id、requirement_revision、schema_version、content_json、validation_status、superseded_by |
|
| `report_collaboration_artifacts` | 成果物 | artifact_type、producer_node_run_id、schema_version、content_json、validation_status |
|
||||||
| `report_collaboration_events` | durable SSE | run_id、seq、event_type、payload、created_at |
|
| `report_collaboration_events` | durable SSE | run_id、seq、event_type、payload、created_at |
|
||||||
| `report_collaboration_commands` | 用户命令 | command_id、idempotency_key、intent、scope、status、expected_revision |
|
| `report_collaboration_commands` | 用户命令 | command_id、idempotency_key、intent、scope、status、expected_revision |
|
||||||
| `report_collaboration_report_versions` | 报告版本 | version、parent_version_id、markdown、source_index、change_summary |
|
| `report_collaboration_report_versions` | 报告版本 | version、parent_version_id、markdown、source_index、change_summary |
|
||||||
@@ -421,7 +316,6 @@ IntentDecision
|
|||||||
- 事件 seq 在 run 行内原子分配。
|
- 事件 seq 在 run 行内原子分配。
|
||||||
- 用户消息、协调者消息、成员 TeamSay、流式回复、工具调用和工具返回使用稳定 `message_id` 与 timeline/event seq;同一 `agentRunId` 内严格保持因果顺序,保证 `MessageList` 能以原生消息重建全过程。
|
- 用户消息、协调者消息、成员 TeamSay、流式回复、工具调用和工具返回使用稳定 `message_id` 与 timeline/event seq;同一 `agentRunId` 内严格保持因果顺序,保证 `MessageList` 能以原生消息重建全过程。
|
||||||
- 并行成员的消息分别归属 `agent_run_id`,同一事件流仍全量持久化;前端选择标签只能过滤/聚焦,不能影响事件写入或消息顺序。
|
- 并行成员的消息分别归属 `agent_run_id`,同一事件流仍全量持久化;前端选择标签只能过滤/聚焦,不能影响事件写入或消息顺序。
|
||||||
- 成员状态的 event 投影使用 `session.snapshot` 中的增量 `agent_runs[]` 合并;不可自定义未被前端处理的 `agent_run.*` 事件。
|
|
||||||
- 删除 session 显式删除子资源,不依赖 SQLite cascade pragma。
|
- 删除 session 显式删除子资源,不依赖 SQLite cascade pragma。
|
||||||
- 写事务在 commit 前完成必要序列化。
|
- 写事务在 commit 前完成必要序列化。
|
||||||
- 不使用跨连接 post-commit refresh。
|
- 不使用跨连接 post-commit refresh。
|
||||||
@@ -452,7 +346,6 @@ GET /api/report-collaboration/runs/{run_id}/events?after_seq={seq}
|
|||||||
GET /api/report-collaboration/runs/{run_id}/stream?after_seq={seq}
|
GET /api/report-collaboration/runs/{run_id}/stream?after_seq={seq}
|
||||||
POST /api/report-collaboration/runs/{run_id}/commands
|
POST /api/report-collaboration/runs/{run_id}/commands
|
||||||
POST /api/report-collaboration/runs/{run_id}/commands/{command_id}/confirm
|
POST /api/report-collaboration/runs/{run_id}/commands/{command_id}/confirm
|
||||||
POST /api/report-collaboration/runs/{run_id}/commands/{command_id}/cancel
|
|
||||||
POST /api/report-collaboration/runs/{run_id}/cancel
|
POST /api/report-collaboration/runs/{run_id}/cancel
|
||||||
GET /api/report-collaboration/runs/{run_id}/nodes/{node_id}/contract
|
GET /api/report-collaboration/runs/{run_id}/nodes/{node_id}/contract
|
||||||
|
|
||||||
@@ -464,17 +357,10 @@ POST /api/report-collaboration/sessions/{session_id}/report-rewrites/{id}/appl
|
|||||||
POST /api/report-collaboration/sessions/{session_id}/report-versions/{id}/restore
|
POST /api/report-collaboration/sessions/{session_id}/report-versions/{id}/restore
|
||||||
```
|
```
|
||||||
|
|
||||||
`GET /sessions/{session_id}` 必须返回完整 `SessionSnapshot`:
|
写请求必须支持:
|
||||||
|
|
||||||
```text
|
- `idempotencyKey`
|
||||||
session / messages / plans / run / nodes / agent_runs / commands /
|
- `expectedRevision`
|
||||||
report_versions / last_seq
|
|
||||||
```
|
|
||||||
|
|
||||||
写请求必须读取并回显:
|
|
||||||
|
|
||||||
- `X-Idempotency-Key`(服务端唯一去重键;用户消息镜像写入 `metadata.idempotency_key`)
|
|
||||||
- `X-Expected-Revision`(陈旧修改返回 409,不静默覆盖)
|
|
||||||
|
|
||||||
计划请求和 run 请求只创建 durable command/job,不在请求生命周期中等待完整模型输出。
|
计划请求和 run 请求只创建 durable command/job,不在请求生命周期中等待完整模型输出。
|
||||||
|
|
||||||
@@ -551,11 +437,10 @@ heartbeat
|
|||||||
|
|
||||||
- 历史与实时使用相同 envelope。
|
- 历史与实时使用相同 envelope。
|
||||||
- 支持 `Last-Event-ID` 和 `after_seq`。
|
- 支持 `Last-Event-ID` 和 `after_seq`。
|
||||||
- 高频 token/tool 参数增量按 50~250ms 或大小阈值合帧为**已持久化** `message.delta` / `tool_call.delta` / `tool_result.delta`;不能每 token 写数据库,也不能仅 live-only 后丢失重放。
|
- token delta 可 live-only 合帧,但需周期性保存可恢复 checkpoint。
|
||||||
- 每一批增量与消息最新正文同事务写入,再向 Live Hub 发布;断线时能通过 `after_seq` 重放,快照也能读到最新 checkpoint。
|
- 不能每 token 写数据库。
|
||||||
- 状态、成果、命令和报告版本必须 durable。
|
- 状态、成果、命令和报告版本必须 durable。
|
||||||
- 读取事件暂时失败时保持连接并发送可恢复 warning。
|
- 读取事件暂时失败时保持连接并发送可恢复 warning。
|
||||||
- `session.snapshot` 可以携带完整快照,也可以携带 `run`、`nodes`、`agent_runs` 的增量集合;成员状态变化必须用此事件投影。
|
|
||||||
|
|
||||||
`message.*` / `team_message.*` 的 `data` 必须携带 LangChain 兼容的消息字段和归属,而非仅有摘要:
|
`message.*` / `team_message.*` 的 `data` 必须携带 LangChain 兼容的消息字段和归属,而非仅有摘要:
|
||||||
|
|
||||||
@@ -581,33 +466,26 @@ heartbeat
|
|||||||
|
|
||||||
工具调用先在同一条 `ai` 消息的 `tool_calls` 上创建/追加参数 delta;返回值随后以 `type: "tool"`、相同 `tool_call_id` 的独立消息传输。允许用户查看的正文、参数和返回必须保持原文以及原始增量边界;只有统一脱敏器标记的字段可替换,替换理由写入 metadata。每个成员在开始生成时先发送 `message.created`,因此即使首 token 尚未到达,前端也能立即显示该成员正在回复,而非静默等待。
|
工具调用先在同一条 `ai` 消息的 `tool_calls` 上创建/追加参数 delta;返回值随后以 `type: "tool"`、相同 `tool_call_id` 的独立消息传输。允许用户查看的正文、参数和返回必须保持原文以及原始增量边界;只有统一脱敏器标记的字段可替换,替换理由写入 metadata。每个成员在开始生成时先发送 `message.created`,因此即使首 token 尚未到达,前端也能立即显示该成员正在回复,而非静默等待。
|
||||||
|
|
||||||
`reasoning_delta` 仅可承载经策略批准的、面向用户的工作说明(如“正在比对统计口径”),不允许桥接模型隐藏推理链、系统 prompt 或内部审稿思路;如模型未提供安全的公开工作说明,省略字段,过程可见性由 TeamSay、工具步骤、来源与普通 assistant 文本承担。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 分步实施计划
|
## 8. 分步实施计划
|
||||||
|
|
||||||
工单状态见 §0。本节正文仍是验收标准,不因部分完成而删减。
|
|
||||||
|
|
||||||
### BE-0:AgentScope 兼容性尖峰
|
### BE-0:AgentScope 兼容性尖峰
|
||||||
|
|
||||||
**状态:条件完成(`RC-BE-000`)。** 事件适配与 dict/真实事件单测已有;真实双模型流式与 `uv.lock` 仍属后续 POC。20 项标注评测与 20 次固定任务统计已在 `RC-BE-018` 落地。
|
|
||||||
|
|
||||||
先写实验和测试,不接前端,不建正式业务表。
|
先写实验和测试,不接前端,不建正式业务表。
|
||||||
|
|
||||||
验证:
|
验证:
|
||||||
|
|
||||||
1. 当前 Python 3.12 与候选 `agentscope==2.0.5` 兼容;POC 通过后才将精确版本写入 lock,不能使用浮动 `main`。
|
1. 当前 Python 3.12 与稳定 AgentScope 2.x 兼容。
|
||||||
2. 验证单任务 worker 所需的 Agent、Tool、Event、Permission API;Agent Team 的自主调度与 Session SSE 不属于本功能的正确性依赖。
|
2. 固定精确版本和 commit,不使用浮动 `main`。
|
||||||
3. `uv lock` 后检查 Pydantic、SQLAlchemy、FastAPI、OpenAI SDK 冲突。
|
3. `uv lock` 后检查 Pydantic、SQLAlchemy、FastAPI、OpenAI SDK 冲突。
|
||||||
4. 使用当前至少两个模型完成流式回复。
|
4. 使用当前至少两个模型完成流式回复。
|
||||||
5. `TaskLedger` 分派 2 个并行 worker,完成可见 TeamSay、并发和结构化输出。
|
5. leader + 2 workers 完成任务分配、TeamSay、并发和结构化输出。
|
||||||
6. 主动取消、超时、工具异常和成员异常行为可控。
|
6. 主动取消、超时、工具异常和成员异常行为可控。
|
||||||
7. AgentScope Event 可稳定关联 agent、task、message、tool call,并能一对一写出 LangChain 兼容的 assistant/tool 消息。
|
7. AgentScope Event 可稳定关联 agent、task、message、tool call,并能一对一写出 LangChain 兼容的 assistant/tool 消息。
|
||||||
8. 内存状态丢失后可由 DeerFlow task ledger/artifact 重建。
|
8. 内存状态丢失后可由 DeerFlow task ledger/artifact 重建。
|
||||||
9. 连续运行 20 次固定任务,统计早停、空输出、非法 JSON、事件缺失。
|
9. 连续运行 20 次固定任务,统计早停、空输出、非法 JSON、事件缺失。
|
||||||
10. 从首个 `REPLY_START`、TeamSay、tool-call start、tool result 到 token delta 的首个可见事件均在指定时限内到达 adapter;并行两个成员交错输出时不串 message ID 或 tool call ID,并能按前端既定 `phase_id` / `node_run_id` 聚焦。
|
10. 从首个 `REPLY_START`、TeamSay、tool-call start、tool result 到 token delta 的首个可见事件均在指定时限内到达 adapter;并行两个成员交错输出时不串 message ID 或 tool call ID。
|
||||||
11. 人为注入流式中断、未闭合 tool call、等待用户输入与恢复场景;adapter 必须补发终态 tool result,不能留下永久 loading 的工具步骤。
|
|
||||||
|
|
||||||
交付:
|
交付:
|
||||||
|
|
||||||
@@ -624,8 +502,6 @@ Go 条件:
|
|||||||
|
|
||||||
### BE-1:配置、依赖和总开关
|
### BE-1:配置、依赖和总开关
|
||||||
|
|
||||||
**状态:完成(`RC-BE-002`)。** `enabled` / `worker_enabled` 默认 `false`。
|
|
||||||
|
|
||||||
新增 `config.yaml -> report_collaboration`:
|
新增 `config.yaml -> report_collaboration`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -662,8 +538,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-2:领域合同和状态机
|
### BE-2:领域合同和状态机
|
||||||
|
|
||||||
**状态:完成(`RC-BE-001`)。**
|
|
||||||
|
|
||||||
完成 Pydantic 合同:
|
完成 Pydantic 合同:
|
||||||
|
|
||||||
- requirement。
|
- requirement。
|
||||||
@@ -686,8 +560,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-3:迁移和持久化 Store
|
### BE-3:迁移和持久化 Store
|
||||||
|
|
||||||
**状态:完成(`RC-BE-003`)。** 表前缀 `report_collaboration_*`,迁移 `20260905_01`。
|
|
||||||
|
|
||||||
任务:
|
任务:
|
||||||
|
|
||||||
1. 创建数据库迁移。
|
1. 创建数据库迁移。
|
||||||
@@ -707,19 +579,15 @@ cost_budget
|
|||||||
|
|
||||||
### BE-4:会话、消息和计划 API 骨架
|
### BE-4:会话、消息和计划 API 骨架
|
||||||
|
|
||||||
**状态:骨架完成(`RC-BE-004`)+ SSE 已挂载(`RC-BE-011`)+ 改写/恢复已挂载(`RC-BE-015`)。** REST 会话/消息/方案/run 已挂载;`GET /runs/{id}/stream` 为 durable SSE。
|
|
||||||
|
|
||||||
任务:
|
任务:
|
||||||
|
|
||||||
- session CRUD 和用户隔离。
|
- session CRUD 和用户隔离。
|
||||||
- `GET /sessions/{id}` 一次返回前端 `SessionSnapshot` 的全部字段;run 前的澄清/规划阶段由该快照在 2.5 秒轮询内可见。
|
|
||||||
- message 持久化:完整保存用户可见的 assistant、TeamSay、`tool_calls` 与 `tool` 返回,并保留 `agent_run_id`/`node_run_id`/`phase_id`;不只保存阶段摘要。
|
- message 持久化:完整保存用户可见的 assistant、TeamSay、`tool_calls` 与 `tool` 返回,并保留 `agent_run_id`/`node_run_id`/`phase_id`;不只保存阶段摘要。
|
||||||
- plan request 入队。
|
- plan request 入队。
|
||||||
- plan list/select。
|
- plan list/select。
|
||||||
- run create/query。
|
- run create/query。
|
||||||
- OpenAPI 响应模型。
|
- OpenAPI 响应模型。
|
||||||
- 路由注册顺序和统一错误模型。
|
- 路由注册顺序和统一错误模型。
|
||||||
- 按 `frontend-web/src/report-collaboration/api/types.ts` 建立契约测试:字段名保持 snake_case,枚举、事件类型、命令确认/取消端点和 revision 头不可漂移。
|
|
||||||
|
|
||||||
安全:
|
安全:
|
||||||
|
|
||||||
@@ -729,17 +597,14 @@ cost_budget
|
|||||||
|
|
||||||
### BE-5:需求解析和澄清
|
### BE-5:需求解析和澄清
|
||||||
|
|
||||||
**状态:完成(`RC-BE-005`)。** 规则路径;尚未接规划用 LLM。澄清完成不会自动 `plan-requests`。
|
`RequirementResolver`:
|
||||||
|
|
||||||
`IntentRouterAgent`(规划前模式)与 `RequirementResolver`:
|
|
||||||
|
|
||||||
1. 读取会话消息和最新 requirement snapshot。
|
1. 读取会话消息和最新 requirement snapshot。
|
||||||
2. `IntentRouterAgent` 输出结构化需求提取、澄清判定或“可直接规划”判定;不得自行建图或启动任务。
|
2. 提取主题、目标、受众、范围、角度、模板和来源限制。
|
||||||
3. `RequirementResolver` 校验并写入主题、目标、受众、范围、角度、模板和来源限制。
|
3. 判断是否缺少影响方案选择的必要信息。
|
||||||
4. 判断是否缺少真正影响方案选择的必要信息。
|
4. 必要时产生一个或多个独立 clarification。
|
||||||
5. 必要时产生一个或多个独立 clarification。
|
5. 用户回答后创建新 requirement revision。
|
||||||
6. 用户回答后创建新 requirement revision。
|
6. 达到澄清上限后使用明确假设继续,不能无限追问。
|
||||||
7. 达到澄清上限后使用明确假设继续,不能无限追问。
|
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
|
|
||||||
@@ -749,8 +614,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-6:智能体目录、筛选和候选计划
|
### BE-6:智能体目录、筛选和候选计划
|
||||||
|
|
||||||
**状态:完成(`RC-BE-006`)。** 选择器为规则重排,未接规划模型。`POST /plan-requests` 在 `worker_enabled=false` 时于请求内同步生成方案(非 LLM)。
|
|
||||||
|
|
||||||
五阶段:
|
五阶段:
|
||||||
|
|
||||||
1. `AgentCatalogProjection`:读取用户可见且启用的 agent、技能和工具元数据。
|
1. `AgentCatalogProjection`:读取用户可见且启用的 agent、技能和工具元数据。
|
||||||
@@ -775,8 +638,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-7:AgentScope 模型适配
|
### BE-7:AgentScope 模型适配
|
||||||
|
|
||||||
**状态:完成(`RC-BE-007`)。** 适配器鸭子类型对齐 `ChatModelBase.__call__`;运行前冻结快照。真实 Provider 冒烟仍属 `RC-BE-009` / `017`。
|
|
||||||
|
|
||||||
目标:让 AgentScope 使用 DeerFlow 配置的模型,不再建立第二套 Provider 配置。
|
目标:让 AgentScope 使用 DeerFlow 配置的模型,不再建立第二套 Provider 配置。
|
||||||
|
|
||||||
任务:
|
任务:
|
||||||
@@ -796,15 +657,13 @@ cost_budget
|
|||||||
|
|
||||||
### BE-8:工具与权限适配
|
### BE-8:工具与权限适配
|
||||||
|
|
||||||
**状态:完成(`RC-BE-008`)。**
|
|
||||||
|
|
||||||
第一版允许:
|
第一版允许:
|
||||||
|
|
||||||
- Web 检索和正文获取。
|
- Web 检索和正文获取。
|
||||||
- 用户有权限的知识库/Wiki 检索。
|
- 用户有权限的知识库/Wiki 检索。
|
||||||
- 明确批准的只读技能/MCP。
|
- 明确批准的只读技能/MCP。
|
||||||
- 成果物板读写工具。
|
- 成果物板读写工具。
|
||||||
- 用户可见交接(TeamSay)与受控成果物/澄清工具。
|
- AgentScope 团队通信与任务工具。
|
||||||
|
|
||||||
第一版默认禁止:
|
第一版默认禁止:
|
||||||
|
|
||||||
@@ -819,7 +678,7 @@ cost_budget
|
|||||||
- Researcher 不能发布报告。
|
- Researcher 不能发布报告。
|
||||||
- Writer 不能把未核验证据改成已核验。
|
- Writer 不能把未核验证据改成已核验。
|
||||||
- Reviewer 只能输出 ReviewDecision,不能覆盖报告。
|
- Reviewer 只能输出 ReviewDecision,不能覆盖报告。
|
||||||
- Coordinator 只能提出受控的计划、澄清与修复建议;实际创建任务、分派、等待、返工和完成由 `TaskLedger` 服务执行。
|
- Coordinator 只能通过受控工具创建任务、分派、等待、返工和建议完成。
|
||||||
- 所有成员获得本次 run 的最小权限快照。
|
- 所有成员获得本次 run 的最小权限快照。
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
@@ -829,9 +688,7 @@ cost_budget
|
|||||||
- 网页 prompt injection 不能提升权限。
|
- 网页 prompt injection 不能提升权限。
|
||||||
- AgentScope 工具异常被转换为 node/tool event,不泄露内部堆栈给用户。
|
- AgentScope 工具异常被转换为 node/tool event,不泄露内部堆栈给用户。
|
||||||
|
|
||||||
### BE-9:受控任务成员运行器(不采用自治 Team 调度)
|
### BE-9:AgentScope Team 运行器
|
||||||
|
|
||||||
**状态:完成(`RC-BE-009` 脚本内核 + `RC-BE-013` 质量角色内核)。** `TaskLedger` + `WaveExecutor` + `QualityRoleKernel`。真实 AgentScope `reply_stream` / web_search 仍待接。
|
|
||||||
|
|
||||||
基础角色:
|
基础角色:
|
||||||
|
|
||||||
@@ -849,21 +706,18 @@ cost_budget
|
|||||||
- 计划决定 Researcher/Analyst 数量和角度。
|
- 计划决定 Researcher/Analyst 数量和角度。
|
||||||
- 服务器限制团队人数、并发、attempt、返工和预算。
|
- 服务器限制团队人数、并发、attempt、返工和预算。
|
||||||
- 每个成员上下文只包含当前任务、需求快照和必要上游成果。
|
- 每个成员上下文只包含当前任务、需求快照和必要上游成果。
|
||||||
- `Coordinator` 只接收结构化成果摘要、质量失败与用户命令影响,输出候选计划/修复建议;它不拥有自由消息总线或数据库权限。
|
- Coordinator 不直接操作数据库,只调用 TaskLedger/ArtifactBoard 工具。
|
||||||
- `TaskLedger` 依据已验证成果、预算与并发限额逐波创建 AgentScope worker;AgentScope 不选择下一节点,也不决定任务完成。
|
|
||||||
- 每个 worker 只能调用 `submit_artifact`、`request_clarification`、`request_repair`、允许的资料工具和可见 TeamSay;所有改变运行状态的调用都由服务端重新校验。
|
|
||||||
- 任务完成由 QualityGate 判定,不接受文本声明。
|
- 任务完成由 QualityGate 判定,不接受文本声明。
|
||||||
|
|
||||||
事件映射:
|
事件映射:
|
||||||
|
|
||||||
- AgentScope `REPLY_*` → 以原始文本 chunk 创建/更新协调者或成员的稳定 assistant 消息,保留 `agentRunId`;不得等待 reply 完成后再合成为进度摘要。
|
- AgentScope `REPLY_*` → 以原始文本 chunk 创建/更新协调者或成员的稳定 assistant 消息,保留 `agentRunId`;不得等待 reply 完成后再合成为进度摘要。
|
||||||
- AgentScope TeamSay → 以成员身份创建/更新普通 assistant 消息,保留完整用户可见交接内容与因果顺序;它不会改变 ledger 状态。
|
- AgentScope TeamSay → 以成员身份创建/更新普通 assistant 消息,保留完整用户可见交接内容与因果顺序。
|
||||||
- `MODEL_CALL_*` → 内部遥测,默认不下发原始 prompt。
|
- `MODEL_CALL_*` → 内部遥测,默认不下发原始 prompt。
|
||||||
- `TOOL_CALL_*` → 原生 `ai.tool_calls` 参数事件;对应工具返回 → 原生 `tool` 消息和相同 `tool_call_id`。两者在 SSE 和持久化中一一对应,供现有 `MessageGroup` 直接渲染。
|
- `TOOL_CALL_*` → 原生 `ai.tool_calls` 参数事件;对应工具返回 → 原生 `tool` 消息和相同 `tool_call_id`。两者在 SSE 和持久化中一一对应,供现有 `MessageGroup` 直接渲染。
|
||||||
- `TaskLedger` 的分派/校验/重试/取消结果 → node/task ledger 状态;AgentScope 仅上报可验证的 TaskResult。
|
- team task/update → node/task ledger 状态。
|
||||||
- 成果工具 → artifact created/validated/rejected。
|
- 成果工具 → artifact created/validated/rejected。
|
||||||
- 每个用户可见阶段开始、进展、完成、等待、返工和失败都必须同步产生稳定的消息或状态事件,供左侧 DeerFlow `MessageList` 按原始消息顺序展示;不能要求前端读取节点结果后自行拼装过程,也不能用聚合卡替换 AgentScope 的实时可见消息。
|
- 每个用户可见阶段开始、进展、完成、等待、返工和失败都必须同步产生稳定的消息或状态事件,供左侧 DeerFlow `MessageList` 按原始消息顺序展示;不能要求前端读取节点结果后自行拼装过程,也不能用聚合卡替换 AgentScope 的实时可见消息。
|
||||||
- agent run 的开始、等待输入、完成、失败和取消,紧邻节点状态事件发 `session.snapshot` 增量,至少包括相应的 `agent_runs[]` 和当前 `run.current_phase`。
|
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
|
|
||||||
@@ -873,8 +727,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-10:成果物板与质量门
|
### BE-10:成果物板与质量门
|
||||||
|
|
||||||
**状态:完成(`RC-BE-010`)。** `QualityGate` 为确定性规则(无二次模型调用)。模板冻结快照与正式 `ReportVersion` 落库已在 `RC-BE-015` 落地。
|
|
||||||
|
|
||||||
`ArtifactBoard`:
|
`ArtifactBoard`:
|
||||||
|
|
||||||
- 按 run/node/attempt/type 保存成果。
|
- 按 run/node/attempt/type 保存成果。
|
||||||
@@ -884,26 +736,21 @@ cost_budget
|
|||||||
|
|
||||||
`QualityGate`:
|
`QualityGate`:
|
||||||
|
|
||||||
- `CoverageMatrix`:把需求中的每个必选角度/关键问题映射到证据目标、分析节点和模板章节;计划和最终版本均不可漏项。
|
- EvidenceBundle:来源数量、日期、URL/文档 ID、摘录和去重。
|
||||||
- `EvidenceBundle`:按**主张级**保存 `claim_id`、来源 URL/文档 ID、发布/获取时间、原文摘录、事实或推断标签、适用范围、数值单位/口径、可信度与去重结果。
|
- VerificationResult:支持与反对证据、冲突、时效。
|
||||||
- `VerificationResult`:对关键主张给出支持与反对证据、冲突、时效、来源独立性和可用结论;数字及重要事实必须经此门。
|
- AngleAnalysis:结论必须引用验证过的 evidence ID。
|
||||||
- `AngleAnalysis`:结论只能引用验证过的 evidence ID,必须分别标示事实、推断、假设、局限和资料缺口。
|
- ReportSectionDraft:关键事实和数字必须有引用。
|
||||||
- `ReportSectionDraft`:关键事实和数字必须有 evidence ID;章节的主张与引用一一可追溯。
|
- ReviewDecision:问题必须有目标、严重度和修复要求。
|
||||||
- `CitationTemplateLinter`:校验模板章节、必答问题、来源格式、数字/日期/实体一致性、不可用或过期来源和未引用的关键事实。
|
- ReportVersion:模板覆盖、引用完整、章节一致、正式版本已持久化。
|
||||||
- `ReviewDecision`:独立于 Writer 上下文运行,问题必须有目标、严重度、证据和修复要求;不能笼统地要求“整体再优化”。
|
|
||||||
- `ReportVersion`:覆盖矩阵、引用、模板、章节衔接、结论一致性和正式版本持久化全部通过才可创建。
|
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
|
|
||||||
- 无成果、空成果和不合 Schema 的成果不能完成节点。
|
- 无成果、空成果和不合 Schema 的成果不能完成节点。
|
||||||
- 未验证来源不能直接进入最终事实陈述。
|
- 未验证来源不能直接进入最终事实陈述。
|
||||||
- Reviewer 的返工目标可映射到具体节点和章节。
|
- Reviewer 的返工目标可映射到具体节点和章节。
|
||||||
- 同一事实出现冲突或同一数字缺少口径时,QualityGate 必须拒绝或要求定向补证,不能由 Writer 擅自选择一个答案。
|
|
||||||
|
|
||||||
### BE-11:后台 Dispatcher、租约和恢复
|
### BE-11:后台 Dispatcher、租约和恢复
|
||||||
|
|
||||||
**状态:完成(`RC-BE-012`)。** `worker_enabled` 默认仍为 false:Gateway lifespan 只有 `enabled` **且** `worker_enabled` 同时为真才 `dispatcher.start()`。测试走 `dispatch_once()` / `executor.execute()`,不要依赖后台循环。
|
|
||||||
|
|
||||||
复用现有成熟模式:
|
复用现有成熟模式:
|
||||||
|
|
||||||
- active-dedupe key。
|
- active-dedupe key。
|
||||||
@@ -933,8 +780,6 @@ cost_budget
|
|||||||
|
|
||||||
### BE-12:SSE 和 Live Hub
|
### BE-12:SSE 和 Live Hub
|
||||||
|
|
||||||
**状态:完成(`RC-BE-011`)。** `ConversationReplica` 已实现协议镜像;产品 SSE 经 `execution/live_hub.py` 挂载。报告正式版本写入已在 `RC-BE-015` 落地。
|
|
||||||
|
|
||||||
任务:
|
任务:
|
||||||
|
|
||||||
- 实现 event repository。
|
- 实现 event repository。
|
||||||
@@ -945,7 +790,6 @@ cost_budget
|
|||||||
- 将高频 token delta 合帧。
|
- 将高频 token delta 合帧。
|
||||||
- 建立 `ConversationReplica`:将 AgentScope 用户可见消息、TeamSay、assistant delta、`tool_calls`、`tool` 返回、协助请求、报告文件和领域状态写成可重放的统一消息事件。它是协议镜像而不是摘要生成器。
|
- 建立 `ConversationReplica`:将 AgentScope 用户可见消息、TeamSay、assistant delta、`tool_calls`、`tool` 返回、协助请求、报告文件和领域状态写成可重放的统一消息事件。它是协议镜像而不是摘要生成器。
|
||||||
- 对话镜像完整保留用户有权查看的 TeamSay、原始工具参数和返回;不包含 chain-of-thought、系统/内部 prompt、密钥、权限不可见内容和异常堆栈。脱敏在入库/下发前一次执行,不能由前端自行推断。
|
- 对话镜像完整保留用户有权查看的 TeamSay、原始工具参数和返回;不包含 chain-of-thought、系统/内部 prompt、密钥、权限不可见内容和异常堆栈。脱敏在入库/下发前一次执行,不能由前端自行推断。
|
||||||
- 对前端已支持的 `reasoning_delta`,仅推送由本系统生成或模型明确允许公开的短工作说明;它不等同于原始思维链,也不得用作质量门、调度或审计的唯一依据。
|
|
||||||
- `agentRunId` 是并行消息流的稳定归属。Live Hub 始终发布所有成员事件;前端标签切换只在本地选择消息,不启动第二条运行流,也不取消未聚焦成员的 delta。
|
- `agentRunId` 是并行消息流的稳定归属。Live Hub 始终发布所有成员事件;前端标签切换只在本地选择消息,不启动第二条运行流,也不取消未聚焦成员的 delta。
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
@@ -955,15 +799,10 @@ cost_budget
|
|||||||
- 终态前报告正式版本已写入并可读。
|
- 终态前报告正式版本已写入并可读。
|
||||||
- SSE 断开不取消后台 run。
|
- SSE 断开不取消后台 run。
|
||||||
- 仅依靠会话快照和事件重放即可重建每个成员的完整原生消息、工具过程和左侧聚焦视图,不需要调用节点结果接口。
|
- 仅依靠会话快照和事件重放即可重建每个成员的完整原生消息、工具过程和左侧聚焦视图,不需要调用节点结果接口。
|
||||||
- 在模型/工具中断、取消和接管时,所有已开始的 tool call 都有对应的 completed tool result(成功、失败或 interrupted),不会让复用的 `MessageList` 永久显示执行中。
|
|
||||||
|
|
||||||
### BE-13:全过程命令与影响分析
|
### BE-13:全过程命令与影响分析
|
||||||
|
|
||||||
**状态:完成(`RC-BE-014`)。** `app/report_collaboration/interventions/` 已实现运行期 IntentRouterAgent、ImpactAnalyzer 和 RuntimeCommandService;SQL/memory store 通过 `patch_command(..., expected_statuses=...)` 对命令执行权做 CAS。置信度阈值由 `report_collaboration.intent_confirmation_threshold` 配置(默认 `0.8`)。
|
`IntentRouter` 输入:
|
||||||
|
|
||||||
实现边界:本票负责识别、授权、影响计算、确认、定向失效、overlay 和安全重排队。`rewrite_section` / `polish_report` 已能严格保留研究链并只返工写作/审校链;replacement candidate、正式版本 apply/restore 已在 `RC-BE-015` 落地。`add_angle` 优先使用用户定向的研究分支,否则按冻结计划顺序确定性选择一个研究分支,只接入其必要下游;无研究支路立即 `rejected`。`rerun_node` 没有合法目标时不得扩成整图。需求补丁只在返工成功后写入。卡住的 `executing` 可在接管或租约超时后收回。`command.cancelled` 进入 SSE。运行中 `POST /sessions/{id}/messages` 返回 409。
|
|
||||||
|
|
||||||
`IntentRouterAgent`(运行中干预模式)输入:
|
|
||||||
|
|
||||||
- 用户文本。
|
- 用户文本。
|
||||||
- session/run 状态。
|
- session/run 状态。
|
||||||
@@ -971,7 +810,7 @@ cost_budget
|
|||||||
- 当前选中节点或章节上下文。
|
- 当前选中节点或章节上下文。
|
||||||
- 后端动作白名单。
|
- 后端动作白名单。
|
||||||
|
|
||||||
先由规则层处理明确的回答、取消和节点/章节显式指令;其余由 `IntentRouterAgent` 输出:
|
输出:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
intent
|
intent
|
||||||
@@ -984,8 +823,6 @@ cost_level
|
|||||||
reason
|
reason
|
||||||
```
|
```
|
||||||
|
|
||||||
模型输出必须通过 Pydantic 校验,并经服务端 target 授权与置信度/成本闸门后才创建可执行 command;`IntentRouterAgent` 不可调用分派、取消、成果写入或报告版本工具。
|
|
||||||
|
|
||||||
命令类型:
|
命令类型:
|
||||||
|
|
||||||
- `answer_question`
|
- `answer_question`
|
||||||
@@ -1002,15 +839,11 @@ reason
|
|||||||
|
|
||||||
`ImpactAnalyzer`:
|
`ImpactAnalyzer`:
|
||||||
|
|
||||||
- `answer_question`:只解析相应的 clarification;仅解锁依赖该答案的节点,其他并行节点继续。
|
- 重新检索:失效指定 EvidenceBundle 和引用它的下游。
|
||||||
- `research_again`:为指定节点/角度追加或替换 EvidenceBundle,失效其验证、分析、综合和受影响章节,不误伤无关角度。
|
- 重新分析:保留 evidence,失效分析和对应章节。
|
||||||
- `reanalyze`:保留 validated evidence,只失效指定分析、综合、写作和审校分支。
|
- 章节重写:保留研究/分析,只生成 replacement candidate。
|
||||||
- `add_angle`:在当前冻结计划中选择一个明确目标或确定性的研究支路承接新角度,再只失效其必要下游;无可用支路时拒绝执行。运行中不暗改前端已选择的 DAG,真正新增画布节点只发生在后续重新规划产生的新 plan revision 中。
|
- 语气润色:创建新报告版本,不失效事实成果。
|
||||||
- `rerun_node`:以新 attempt 重跑指定节点及必要下游,旧结果保留为 superseded。
|
- 全局改向:新建 plan revision,确认后创建 run branch。
|
||||||
- `rewrite_section`:保留研究/分析,只生成 replacement candidate 和该章节审校;用户 apply 后才产生新版本。
|
|
||||||
- `polish_report`:不重检索;在当前正式版本上创建新版本,并再次执行模板/引用 lint。
|
|
||||||
- `update_requirements` / `replan`:均先展示整图影响与高成本确认;前者写入新的 requirement revision 后重开冻结计划,后者重开当前计划全部节点。生成新候选 plan/run branch 的交互留在后续规划版本能力,不能在旧 run 的 SSE 内偷偷切换 run。
|
|
||||||
- `ask_report`:只读取当前已验证成果或报告版本,不改变任务图。
|
|
||||||
|
|
||||||
Safe-point:
|
Safe-point:
|
||||||
|
|
||||||
@@ -1018,19 +851,15 @@ Safe-point:
|
|||||||
- 不可立即取消的 attempt 继续到边界,command 状态为 `accepted_waiting_boundary`。
|
- 不可立即取消的 attempt 继续到边界,command 状态为 `accepted_waiting_boundary`。
|
||||||
- 晚到输出可保存但标记 superseded,不能进入下游。
|
- 晚到输出可保存但标记 superseded,不能进入下游。
|
||||||
- 每条 command 有分类、影响范围、状态和结果。
|
- 每条 command 有分类、影响范围、状态和结果。
|
||||||
- command overlay 仅在模型调用之间、工具返回后或节点提交成果前生效;在不可中断工具期间不能伪称已经完成修改。
|
|
||||||
|
|
||||||
验收:
|
验收:
|
||||||
|
|
||||||
- 低置信度“改一下”必须追问。
|
- 低置信度“改一下”必须追问。
|
||||||
- 相同幂等键不重复创建返工任务。
|
- 相同幂等键不重复创建返工任务。
|
||||||
- 运行中命令不能造成 completed/cancelled 状态竞争。
|
- 运行中命令不能造成 completed/cancelled 状态竞争。
|
||||||
- 用户针对画布/成员卡的定向修改只影响解析出的节点和成果血缘,不能被协调者扩大为整场重跑。
|
|
||||||
|
|
||||||
### BE-14:报告模板、写作、审稿和版本
|
### BE-14:报告模板、写作、审稿和版本
|
||||||
|
|
||||||
**状态:完成(`RC-BE-015`)。** 改写 / apply / restore 已挂载;正式版本在质量门通过后落库。
|
|
||||||
|
|
||||||
复用 `/api/report-structures`,run 开始时冻结模板快照:
|
复用 `/api/report-structures`,run 开始时冻结模板快照:
|
||||||
|
|
||||||
- 章节层级。
|
- 章节层级。
|
||||||
@@ -1042,25 +871,25 @@ Safe-point:
|
|||||||
|
|
||||||
写作规则:
|
写作规则:
|
||||||
|
|
||||||
- Writer 只能使用冻结模板、`CoverageMatrix` 和 validated artifacts,禁止携带 Web/知识库工具或原始检索上下文进入写作提示词。
|
- Writer 只能使用 validated artifacts。
|
||||||
- 关键判断绑定 evidence ID;无法证实的内容明确标为分析、假设或局限。
|
- 关键判断绑定 evidence ID。
|
||||||
- 每章获得确定 evidence set、字数目标和必答问题,输出章节草稿及主张—引用映射。
|
- 无法证实的内容明确标为分析、假设或局限。
|
||||||
- 完整报告先执行 CitationTemplateLinter,再由独立 Reviewer 进行事实、逻辑、受众适配和结论审校。
|
- 每章获得确定 evidence set。
|
||||||
- Reviewer 输出结构化 ReviewDecision;Reviser 只修改指定问题,并由同一质量门复验。
|
- 完整报告执行引用、模板、衔接和结论一致性检查。
|
||||||
|
- Reviewer 输出结构化 ReviewDecision。
|
||||||
|
- Reviser 只修改指定问题。
|
||||||
- 达到返工上限后报告未解决问题,不无限循环。
|
- 达到返工上限后报告未解决问题,不无限循环。
|
||||||
|
|
||||||
版本规则:
|
版本规则:
|
||||||
|
|
||||||
- 流式 Markdown 是临时草稿,不覆盖当前正式版本。
|
- 流式 Markdown 是临时草稿,不覆盖当前正式版本。
|
||||||
- 正式版本须在所有质量门通过后落库,随后才发 `report.version.created` 并令 run completed。
|
- 正式版本落库并确认后 run 才能 completed。
|
||||||
- 章节改写生成 replacement candidate。
|
- 章节改写生成 replacement candidate。
|
||||||
- 用户 apply 后创建新版本。
|
- 用户 apply 后创建新版本。
|
||||||
- restore 也创建新头版本,不破坏历史。
|
- restore 也创建新头版本,不破坏历史。
|
||||||
|
|
||||||
### BE-15:安全、审计和预算
|
### BE-15:安全、审计和预算
|
||||||
|
|
||||||
**状态:完成(`RC-BE-016`)。** 快照与 SSE 走统一脱敏;资源 owner、跨会话幂等、预算上限与审计已落地。
|
|
||||||
|
|
||||||
- 所有资源校验 owner 或明确授权。
|
- 所有资源校验 owner 或明确授权。
|
||||||
- agent snapshot 移除密钥和不可见 prompt。
|
- agent snapshot 移除密钥和不可见 prompt。
|
||||||
- 工具参数/结果完整镜像到用户消息流;只有密钥、令牌、隐私字段、越权内容和明确的异常堆栈由统一脱敏器替换并记录原因。不能以大小截断或摘要替代用户有权查看的返回;超大返回以可重放 chunk 传输和按需折叠展示,正文仍可完整读取。
|
- 工具参数/结果完整镜像到用户消息流;只有密钥、令牌、隐私字段、越权内容和明确的异常堆栈由统一脱敏器替换并记录原因。不能以大小截断或摘要替代用户有权查看的返回;超大返回以可重放 chunk 传输和按需折叠展示,正文仍可完整读取。
|
||||||
@@ -1068,12 +897,10 @@ Safe-point:
|
|||||||
- 每用户 active run 上限。
|
- 每用户 active run 上限。
|
||||||
- 每 run 团队、并发、检索、来源正文、模型调用、token、费用和总时长上限。
|
- 每 run 团队、并发、检索、来源正文、模型调用、token、费用和总时长上限。
|
||||||
- 审计 agent 选择、工具调用、来源、用户命令、人工确认和报告版本。
|
- 审计 agent 选择、工具调用、来源、用户命令、人工确认和报告版本。
|
||||||
- 不持久化、不下发原始 chain-of-thought、系统/内部 prompt 或模型隐藏推理 token;前端可显示的 `reasoning_delta` 必须是经过独立策略筛选的公开工作说明。这不影响用户可见 assistant 文本、TeamSay、工具调用与工具返回的完整镜像。
|
- 不持久化、不下发原始 chain-of-thought、系统/内部 prompt 或模型推理 token;这不影响用户可见 assistant 文本、TeamSay、工具调用与工具返回的完整镜像。
|
||||||
|
|
||||||
### BE-16:测试与质量评测
|
### BE-16:测试与质量评测
|
||||||
|
|
||||||
**状态:完成(`RC-BE-018`)。** 合同 / 适配器 / 规划 / 需求 / SSE / lease / 20 项标注评测集已有。打分器不调用模型;金标必须过门,会商风失败实例必须可复现。
|
|
||||||
|
|
||||||
单元测试:
|
单元测试:
|
||||||
|
|
||||||
- Pydantic 合同。
|
- Pydantic 合同。
|
||||||
@@ -1085,8 +912,6 @@ Safe-point:
|
|||||||
- AgentScope event adapter。
|
- AgentScope event adapter。
|
||||||
- 同一 `message_id` 的 token delta、同一 `tool_call_id` 的参数/结果 delta 以及脱敏字段替换;断线重放后消息正文不得被摘要化或重复。
|
- 同一 `message_id` 的 token delta、同一 `tool_call_id` 的参数/结果 delta 以及脱敏字段替换;断线重放后消息正文不得被摘要化或重复。
|
||||||
- 两个 `agentRunId` 并行交错输出、其中一员等待用户协助时,消息归属、工具配对、状态和命令目标均正确。
|
- 两个 `agentRunId` 并行交错输出、其中一员等待用户协助时,消息归属、工具配对、状态和命令目标均正确。
|
||||||
- `session.snapshot` 增量 `agent_runs[]` 能驱动前端阶段卡从 pending → running → waiting_input → terminal;`node_run_id` 与画布节点前缀匹配。
|
|
||||||
- 流式中断后每个已创建 tool call 都有终态 tool result,`MessageList` 不留下旋转步骤。
|
|
||||||
|
|
||||||
集成测试:
|
集成测试:
|
||||||
|
|
||||||
@@ -1100,7 +925,7 @@ Safe-point:
|
|||||||
真实 AgentScope 冒烟:
|
真实 AgentScope 冒烟:
|
||||||
|
|
||||||
- 至少两个已配置模型。
|
- 至少两个已配置模型。
|
||||||
- ledger 驱动的多 worker(不依赖 AgentScope Team 自动分派)。
|
- leader + 多 worker。
|
||||||
- 并发工具和 TeamSay。
|
- 并发工具和 TeamSay。
|
||||||
- 取消、超时、异常成员和结果修复。
|
- 取消、超时、异常成员和结果修复。
|
||||||
|
|
||||||
@@ -1109,24 +934,19 @@ Safe-point:
|
|||||||
- 不少于 20 个真实任务。
|
- 不少于 20 个真实任务。
|
||||||
- 覆盖政策影响、市场趋势、企业研究、事件研判、对比分析。
|
- 覆盖政策影响、市场趋势、企业研究、事件研判、对比分析。
|
||||||
- 每个任务记录必选角度、关键事实、禁止编造项、模板和评审规则。
|
- 每个任务记录必选角度、关键事实、禁止编造项、模板和评审规则。
|
||||||
- 与现有多智能体会商进行盲评,并保留每项失败的可复现实例。
|
- 与现有多智能体会商进行盲评。
|
||||||
- 指标:事实准确、来源质量、关键主张引用覆盖、角度覆盖、逻辑一致、模板遵循、定向修改正确率、耗时和成本。
|
- 指标:事实准确、来源质量、角度覆盖、逻辑一致、格式遵循、可修改性、耗时和成本。
|
||||||
- 上线前为每个任务标注关键主张/数字、可接受来源、必选角度和模板约束;不能只以主观“文章更像报告”判定成功。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. 首个纵向 POC
|
## 9. 首个纵向 POC
|
||||||
|
|
||||||
POC-1 的规则路径已通(三策略差异方案、select ≠ start)。**POC-2 脚本闭环已通**(研究员 → 撰写员,协调者建议不调度)。**POC-3 质量角色已通**(核验 / 分析 / 审稿定向返工,确定性内核)。真实检索与 AgentScope 成员仍待接。**POC-4 未开始**。
|
|
||||||
|
|
||||||
固定任务:
|
固定任务:
|
||||||
|
|
||||||
> 撰写一份新能源汽车主要品牌市场趋势分析报告,覆盖 2024 年至今的市场份额、价格策略、技术路线、海外进展、政策环境和未来 12~24 个月风险与机会,面向企业战略决策人员,要求所有关键数字给出来源。
|
> 撰写一份新能源汽车主要品牌市场趋势分析报告,覆盖 2024 年至今的市场份额、价格策略、技术路线、海外进展、政策环境和未来 12~24 个月风险与机会,面向企业战略决策人员,要求所有关键数字给出来源。
|
||||||
|
|
||||||
### POC-1:候选计划
|
### POC-1:候选计划
|
||||||
|
|
||||||
**状态:规则路径已通(`RC-BE-006`)。** 三策略差异方案、select ≠ start;尚未接规划 LLM。
|
|
||||||
|
|
||||||
- 生成 3 种真实有差异的方案。
|
- 生成 3 种真实有差异的方案。
|
||||||
- 每个方案只引用有效 agent/tool/template。
|
- 每个方案只引用有效 agent/tool/template。
|
||||||
- 每个节点有职责、输入、输出和验收条件。
|
- 每个节点有职责、输入、输出和验收条件。
|
||||||
@@ -1134,8 +954,6 @@ POC-1 的规则路径已通(三策略差异方案、select ≠ start)。**PO
|
|||||||
|
|
||||||
### POC-2:三角色闭环
|
### POC-2:三角色闭环
|
||||||
|
|
||||||
**状态:脚本路径已通(`RC-BE-009`),SSE 重放已挂载(`RC-BE-011`)。** 真实 Web 检索仍待工具实装接入运行器。
|
|
||||||
|
|
||||||
角色:Coordinator、Researcher、Writer。
|
角色:Coordinator、Researcher、Writer。
|
||||||
|
|
||||||
- 完成真实 Web 检索。
|
- 完成真实 Web 检索。
|
||||||
@@ -1146,8 +964,6 @@ POC-1 的规则路径已通(三策略差异方案、select ≠ start)。**PO
|
|||||||
|
|
||||||
### POC-3:质量角色
|
### POC-3:质量角色
|
||||||
|
|
||||||
**状态:完成(`RC-BE-010` 质检 + `RC-BE-013` 受控成员 + `RC-BE-017` 真实内核)。** `QualityRoleKernel` 跑通 Verifier / Analyst / Writer / Reviewer / Reviser;过期与跨包冲突写入 `VerificationResult`;缺失角度只重开检索链;审稿失败只重开目标写作节点。生产 worker 使用 `AgentScopeMemberKernel`。
|
|
||||||
|
|
||||||
加入 Verifier、Analyst、Reviewer、Reviser。
|
加入 Verifier、Analyst、Reviewer、Reviser。
|
||||||
|
|
||||||
- 过期资料被识别。
|
- 过期资料被识别。
|
||||||
@@ -1157,8 +973,6 @@ POC-1 的规则路径已通(三策略差异方案、select ≠ start)。**PO
|
|||||||
|
|
||||||
### POC-4:全过程干预
|
### POC-4:全过程干预
|
||||||
|
|
||||||
**状态:命令与 safe-point 路径完成(`RC-BE-014`)。** 增加角度、指定角度重检索、章节重写/润色的定向影响已覆盖;运行中旧 attempt 在边界后 supersede,晚到成果不能进入下游。正式报告 replacement/version 展示已在 `RC-BE-015` 落地。
|
|
||||||
|
|
||||||
必须演示:
|
必须演示:
|
||||||
|
|
||||||
1. “增加供应链与原材料风险角度”。
|
1. “增加供应链与原材料风险角度”。
|
||||||
@@ -1172,36 +986,28 @@ POC-1 的规则路径已通(三策略差异方案、select ≠ start)。**PO
|
|||||||
|
|
||||||
## 10. 后端开发顺序
|
## 10. 后端开发顺序
|
||||||
|
|
||||||
已完成:
|
1. `RC-BE-000`:AgentScope 版本与兼容性尖峰。
|
||||||
|
|
||||||
1. `RC-BE-000`:AgentScope 版本与兼容性尖峰(条件 Go;未写入 `uv.lock`)。
|
|
||||||
2. `RC-BE-001`:领域合同和状态机。
|
2. `RC-BE-001`:领域合同和状态机。
|
||||||
3. `RC-BE-002`:配置、依赖和 feature flag。
|
3. `RC-BE-002`:配置、依赖和 feature flag。
|
||||||
4. `RC-BE-003`:数据库迁移和 stores。
|
4. `RC-BE-003`:数据库迁移和 stores。
|
||||||
5. `RC-BE-004`:session/message/plan API(改写 / 恢复见 `015`)。
|
5. `RC-BE-004`:session/message/plan API。
|
||||||
6. `RC-BE-005`:需求解析与澄清。
|
6. `RC-BE-005`:需求解析与澄清。
|
||||||
7. `RC-BE-006`:agent catalog、selector、plan assembler/validator。
|
7. `RC-BE-006`:agent catalog、selector、plan assembler/validator。
|
||||||
8. `RC-BE-007`:AgentScope model adapter。
|
8. `RC-BE-007`:AgentScope model adapter。
|
||||||
9. `RC-BE-008`:tool/permission adapter。
|
9. `RC-BE-008`:tool/permission adapter。
|
||||||
10. `RC-BE-009`:三角色 team POC(脚本内核 + TaskLedger 波次)。
|
10. `RC-BE-009`:三角色 team POC。
|
||||||
11. `RC-BE-010`:artifact board 主张级质量门(Coverage / 核验 / 审稿)。
|
11. `RC-BE-010`:task ledger、artifact board、quality gate。
|
||||||
12. `RC-BE-011`:durable event、原生消息镜像、SSE 和 live hub。
|
12. `RC-BE-011`:durable event、原生消息镜像、SSE 和 live hub。
|
||||||
13. `RC-BE-012`:dispatcher、executor、lease、cancel、recovery。
|
13. `RC-BE-012`:dispatcher、executor、lease、cancel、recovery。
|
||||||
14. `RC-BE-013`:Verifier/Analyst/Reviewer/Reviser。
|
14. `RC-BE-013`:Verifier/Analyst/Reviewer/Reviser。
|
||||||
|
15. `RC-BE-014`:intent router、impact analyzer、safe-point commands。
|
||||||
15. `RC-BE-014`:intent router、impact analyzer、safe-point commands(已完成)。
|
16. `RC-BE-015`:report structures、report versions、rewrite candidates。
|
||||||
16. `RC-BE-015`:report structures、report versions、rewrite candidates(已完成)。
|
17. `RC-BE-016`:安全、审计、预算、监控。
|
||||||
17. `RC-BE-016`:安全、审计、预算、监控(已完成)。
|
18. `RC-BE-017`:并发、故障恢复和真实 AgentScope 测试。
|
||||||
18. `RC-BE-017`:并发、故障恢复和真实 AgentScope 测试(已完成)。
|
19. `RC-BE-018`:20 项质量评测和参数调优。
|
||||||
19. `RC-BE-018`:20 项质量评测和参数调优(已完成)。
|
|
||||||
|
|
||||||
下一票起:
|
|
||||||
|
|
||||||
20. `RC-BE-019`:灰度与离线发布接入。
|
20. `RC-BE-019`:灰度与离线发布接入。
|
||||||
|
|
||||||
编号说明:§8 的 `BE-n` 与上表 `RC-BE-00n` 在 **008 之后不完全一一对应**(例如 §8 `BE-11` dispatcher ≈ `RC-BE-012`,`BE-12` SSE ≈ `RC-BE-011`)。排期与代码以 `RC-BE-*` 为准。
|
前后端共同依赖:在 `RC-BE-001` 后冻结 OpenAPI、event envelope 和共享枚举,前端再开始真实 API 层开发。
|
||||||
|
|
||||||
前后端共同依赖:OpenAPI / event envelope / 共享枚举已按 `frontend-web/src/report-collaboration/api/types.ts` 冻结;前端页面与 mock 已完成。真实 SSE 可联调 `GET /runs/{id}/stream`(`RC-BE-011`);领取/恢复见 `RC-BE-012`;质量角色见 `RC-BE-013`;运行中自然语言命令见 `RC-BE-014`。正式报告改写候选与版本 apply/restore 已在 `RC-BE-015` 挂载。生产默认仍不挂后台循环(`worker_enabled=false`)。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1276,7 +1082,7 @@ TeamSay 不作为事实来源;报告事实只能引用 validated EvidenceBundl
|
|||||||
|
|
||||||
后端只有同时满足以下条件才算完成:
|
后端只有同时满足以下条件才算完成:
|
||||||
|
|
||||||
1. 使用 AgentScope 2.0 Core 执行真实的受控任务成员,且由 DeerFlow `TaskLedger` 而非自治 Team 调度协作。
|
1. 使用 AgentScope 2.0 Core 组织真实 leader-worker 团队。
|
||||||
2. 不调用旧 Workflow Studio DAG scheduler。
|
2. 不调用旧 Workflow Studio DAG scheduler。
|
||||||
3. 不改变现有多智能体会商行为。
|
3. 不改变现有多智能体会商行为。
|
||||||
4. DeerFlow 数据库是状态唯一事实源。
|
4. DeerFlow 数据库是状态唯一事实源。
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ VITE_MOCK_COP_TASK_DETAIL=true
|
|||||||
VITE_BACKEND_BASE_URL=http://127.0.0.1:8001
|
VITE_BACKEND_BASE_URL=http://127.0.0.1:8001
|
||||||
VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api
|
VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api
|
||||||
|
|
||||||
# 开发环境普通登录后进入 Vite 内隔离的官方风格页面;/page/* 仍访问当前定制版。
|
|
||||||
VITE_USE_OFFICIAL_DEERFLOW_ENTRY=true
|
|
||||||
|
|
||||||
# 轻应用「新建笔记」系列接口
|
# 轻应用「新建笔记」系列接口
|
||||||
VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000
|
VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000
|
||||||
# CMZS后端接口
|
# CMZS后端接口
|
||||||
|
|||||||
@@ -60,9 +60,3 @@
|
|||||||
# 不填或留空时按类型 A,继续使用原来的简介菜单数据。
|
# 不填或留空时按类型 A,继续使用原来的简介菜单数据。
|
||||||
# VITE_COMPACT_MENU_TYPE=A
|
# VITE_COMPACT_MENU_TYPE=A
|
||||||
# VITE_GITHUB_OAUTH_TOKEN=
|
# VITE_GITHUB_OAUTH_TOKEN=
|
||||||
|
|
||||||
# --- 官方 DeerFlow 风格页面入口(仅 Vite,不引入 Next.js)---
|
|
||||||
# true 时,普通登录后进入同一 Vite 应用中隔离的 /official/*。
|
|
||||||
# 已指定 redirect 或 taskId/goPath 的登录仍保持原来的优先级。
|
|
||||||
# 仅 .env.development 默认 true;其余环境保持 false。
|
|
||||||
# VITE_USE_OFFICIAL_DEERFLOW_ENTRY=false
|
|
||||||
@@ -17,9 +17,6 @@ VITE_MOCK_COP_TASK_DETAIL=false
|
|||||||
VITE_BACKEND_BASE_URL=http://10.19.200.5:2026
|
VITE_BACKEND_BASE_URL=http://10.19.200.5:2026
|
||||||
VITE_LANGGRAPH_BASE_URL=http://10.19.200.5:2026/api
|
VITE_LANGGRAPH_BASE_URL=http://10.19.200.5:2026/api
|
||||||
|
|
||||||
# 内网环境保持当前定制版默认入口。
|
|
||||||
VITE_USE_OFFICIAL_DEERFLOW_ENTRY=false
|
|
||||||
|
|
||||||
# 轻应用「新建笔记」系列接口
|
# 轻应用「新建笔记」系列接口
|
||||||
VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000
|
VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000
|
||||||
# CMZS后端接口
|
# CMZS后端接口
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ VITE_MOCK_SQ_REPORT=false
|
|||||||
VITE_BACKEND_BASE_URL=https://ch1.b.uat.4.cn/deerflow
|
VITE_BACKEND_BASE_URL=https://ch1.b.uat.4.cn/deerflow
|
||||||
VITE_LANGGRAPH_BASE_URL=https://ch1.b.uat.4.cn/deerflow/api
|
VITE_LANGGRAPH_BASE_URL=https://ch1.b.uat.4.cn/deerflow/api
|
||||||
|
|
||||||
# 线上保持当前定制版默认入口。
|
|
||||||
VITE_USE_OFFICIAL_DEERFLOW_ENTRY=false
|
|
||||||
|
|
||||||
# 轻应用「新建笔记」系列接口
|
# 轻应用「新建笔记」系列接口
|
||||||
VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api
|
VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api
|
||||||
# CMZS后端接口
|
# CMZS后端接口
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ VITE_MOCK_COP_TASK_DETAIL=false
|
|||||||
VITE_BACKEND_BASE_URL=https://ch1.b.sit.4.cn/deerflow
|
VITE_BACKEND_BASE_URL=https://ch1.b.sit.4.cn/deerflow
|
||||||
VITE_LANGGRAPH_BASE_URL=https://ch1.b.sit.4.cn/deerflow/api
|
VITE_LANGGRAPH_BASE_URL=https://ch1.b.sit.4.cn/deerflow/api
|
||||||
|
|
||||||
# SIT 环境保持当前定制版默认入口。
|
|
||||||
VITE_USE_OFFICIAL_DEERFLOW_ENTRY=false
|
|
||||||
|
|
||||||
# 轻应用「新建笔记」系列接口
|
# 轻应用「新建笔记」系列接口
|
||||||
VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api
|
VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api
|
||||||
# CMZS后端接口
|
# CMZS后端接口
|
||||||
|
|||||||
+20
-20
@@ -1,25 +1,25 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-US" suppressHydrationWarning>
|
<html lang="en-US" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
try {
|
try {
|
||||||
var t = localStorage.getItem("strategy-theme");
|
var t = localStorage.getItem("strategy-theme");
|
||||||
var root = document.documentElement;
|
var root = document.documentElement;
|
||||||
root.classList.remove("dark", "light");
|
root.classList.remove("dark", "light");
|
||||||
if (t === "light") return;
|
if (t === "light") return;
|
||||||
root.classList.add("dark");
|
root.classList.add("dark");
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>CM 助手</title>
|
<title>CM 助手</title>
|
||||||
<script src="/runtime-config.js" data-runtime-config></script>
|
<script src="/runtime-config.js" data-runtime-config></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
"build:network": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode network",
|
"build:network": "node --max-old-space-size=8192 ./node_modules/vite/bin/vite.js build --mode network",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test:report-collaboration": "npx esbuild src/report-collaboration/tests/event-reducer.test.ts --bundle --platform=node --format=esm --outfile=.runtime/unit-tests/event-reducer.test.mjs && npx esbuild src/report-collaboration/tests/message-adapter.test.ts --bundle --platform=node --format=esm --outfile=.runtime/unit-tests/message-adapter.test.mjs && node --test .runtime/unit-tests/event-reducer.test.mjs .runtime/unit-tests/message-adapter.test.mjs",
|
|
||||||
"check:chrome109-css": "node scripts/check-chrome109-css.mjs",
|
"check:chrome109-css": "node scripts/check-chrome109-css.mjs",
|
||||||
"check:legacy-chrome-css": "node scripts/check-chrome109-css.mjs"
|
"check:legacy-chrome-css": "node scripts/check-chrome109-css.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import {
|
|||||||
} from "@/position-roundtable/demo/position-roundtable-demo-config";
|
} from "@/position-roundtable/demo/position-roundtable-demo-config";
|
||||||
import { ChatRuntime } from "./pages/WorkspaceRoutes";
|
import { ChatRuntime } from "./pages/WorkspaceRoutes";
|
||||||
import { NotFoundPage } from "./pages/NotFoundPage";
|
import { NotFoundPage } from "./pages/NotFoundPage";
|
||||||
import OfficialDeerFlowEntry from "./pages/OfficialDeerFlowEntry";
|
|
||||||
|
|
||||||
const PUBLIC_EMBED_PREFIXES = [
|
const PUBLIC_EMBED_PREFIXES = [
|
||||||
"/share/",
|
"/share/",
|
||||||
@@ -204,7 +203,6 @@ export function App() {
|
|||||||
path="/roundtable-dashboard"
|
path="/roundtable-dashboard"
|
||||||
element={<DashboardStandalonePage />}
|
element={<DashboardStandalonePage />}
|
||||||
/>
|
/>
|
||||||
<Route path="/official/*" element={<OfficialDeerFlowEntry />} />
|
|
||||||
<Route path="/page/*" element={pageElement} />
|
<Route path="/page/*" element={pageElement} />
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2064"
|
|
||||||
height="2591" viewBox="0 0 2064 2591">
|
|
||||||
<path fill-rule="nonzero"
|
|
||||||
d="M 68.582031 0 C 66.890625 0 65.300781 1 64.617188 2.640625 C 49.46875 38.800781 33.71875 76.199219 33.628906 106.539062 C 33.550781 134.230469 46.445312 155.730469 59.949219 175.71875 L 59.949219 175.730469 C 52.414062 175.671875 44.992188 175.371094 37.472656 174.171875 C 27.210938 172.53125 16.585938 169.199219 5.585938 165.738281 C 5.15625 165.609375 4.722656 165.539062 4.296875 165.539062 C 2.824219 165.539062 1.417969 166.300781 0.625 167.609375 C -0.402344 169.300781 -0.140625 171.46875 1.257812 172.871094 L 1.261719 172.878906 C 11.230469 182.871094 21.8125 193.53125 36.980469 200.050781 C 51.5625 206.320312 70.167969 208.628906 88.085938 210.78125 L 88.027344 210.78125 C 127.152344 253.410156 166.617188 296.300781 215.761719 334.941406 C 230.558594 346.578125 246.292969 357.878906 261.785156 369 C 277.171875 380.050781 292.441406 391.011719 306.660156 402.191406 C 326.132812 417.511719 343.5625 433.179688 356.773438 450.019531 C 377.808594 476.828125 388.449219 506.960938 395.4375 538.628906 L 395.4375 538.640625 C 372.683594 510.710938 349.328125 483.929688 324.855469 469.339844 C 295.957031 452.121094 265.800781 452 236.960938 451.808594 L 236.933594 451.820312 C 235.792969 451.808594 234.707031 452.261719 233.898438 453.070312 C 233.085938 453.871094 232.632812 454.96875 232.636719 456.121094 C 232.722656 494.011719 232.699219 533.539062 256.304688 567.480469 C 279.351562 600.621094 324.738281 628.019531 369.191406 654.679688 L 369.132812 654.691406 C 371.476562 683.539062 373.40625 710.691406 367.109375 728.03125 C 365.828125 731.558594 364.191406 734.71875 362.058594 738.820312 C 358.910156 744.871094 354.964844 752.441406 350.417969 764.960938 C 342.890625 785.699219 333.769531 819.839844 323.648438 883.109375 C 303.347656 1010.019531 278.722656 1255.949219 308.882812 1409.269531 C 326.265625 1497.660156 361.828125 1555.210938 393.511719 1606.519531 C 417.042969 1644.632812 438.207031 1678.960938 448.835938 1720.09375 C 466.921875 1790.089844 454.648438 1880.246094 444.5 1954.386719 C 440.613281 1982.78125 437.042969 2008.839844 435.726562 2030.261719 C 433.410156 2067.929688 438.066406 2091.179688 442.304688 2112.347656 L 442.304688 2112.355469 C 445.910156 2130.347656 449.152344 2146.589844 447.757812 2169.28125 C 447.429688 2174.613281 446.835938 2180.359375 445.933594 2186.535156 L 445.933594 2186.542969 C 436.582031 2250.328125 393.613281 2362.808594 362.105469 2416.636719 C 339.761719 2454.804688 323.546875 2462.851562 309.597656 2469.925781 C 303.980469 2472.777344 298.261719 2475.660156 292.691406 2480.238281 C 273.378906 2496.113281 256.457031 2532.078125 240.183594 2566.417969 C 239.578125 2567.703125 239.640625 2569.203125 240.363281 2570.429688 C 241.082031 2571.65625 242.359375 2572.449219 243.777344 2572.542969 L 243.789062 2572.542969 L 243.882812 2572.550781 L 243.882812 2572.554688 C 261.582031 2573.746094 279.460938 2574.957031 296.386719 2574.957031 C 323.671875 2574.957031 349.332031 2571.84375 368.355469 2559.785156 C 392.640625 2544.394531 405.109375 2514.960938 414.488281 2493.074219 C 417.011719 2487.183594 419.289062 2481.871094 421.546875 2477.308594 C 432.054688 2456.039062 441.753906 2451.664062 451.855469 2446.960938 C 453.054688 2446.402344 453.929688 2445.320312 454.226562 2444.042969 L 454.238281 2443.996094 L 454.238281 2443.988281 C 476.210938 2348.800781 498.035156 2254.347656 524.300781 2196.363281 L 524.300781 2196.367188 C 536.363281 2169.730469 549.363281 2150.796875 562.695312 2131.402344 C 576.28125 2111.625 590.1875 2091.402344 603.300781 2062.445312 L 603.304688 2062.453125 C 605.183594 2058.292969 607.054688 2053.949219 608.894531 2049.421875 C 625.066406 2009.675781 639.746094 1954.289062 653.585938 1902.101562 C 663.0625 1866.367188 672.15625 1832.09375 681.085938 1805.085938 C 699.308594 1749.992188 716.863281 1725.09375 734.371094 1705.144531 L 734.421875 1705.148438 C 727.285156 1740.746094 721.597656 1776.628906 729.320312 1807.472656 C 733.351562 1823.570312 740.996094 1838.160156 749.066406 1853.613281 C 758.828125 1872.296875 769.433594 1892.636719 776.359375 1919.675781 C 783.289062 1946.71875 786.574219 1980.605469 789.527344 2011.140625 L 789.527344 2011.148438 C 791.875 2035.402344 794.023438 2057.78125 797.746094 2072.316406 L 797.746094 2072.320312 C 797.929688 2073.023438 798.128906 2073.769531 798.316406 2074.441406 L 798.320312 2074.441406 C 802.132812 2087.941406 807.617188 2093.914062 813.515625 2100.324219 C 821.132812 2108.597656 830.351562 2118.441406 839.742188 2151.671875 L 839.738281 2151.675781 C 840.105469 2152.96875 840.402344 2154.039062 840.765625 2155.398438 C 857.484375 2217.601562 874.097656 2354.589844 872.078125 2423.390625 C 870.472656 2478.15625 857.140625 2488.390625 845.339844 2498.136719 C 842.320312 2500.632812 839.085938 2503.296875 836.09375 2506.566406 C 820.847656 2523.230469 812.652344 2554.886719 804.800781 2584.753906 C 804.457031 2586.0625 804.75 2587.453125 805.589844 2588.515625 C 806.40625 Line truncated
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 19 KiB |
@@ -1,158 +1,158 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { type LucideIcon, XIcon } from "lucide-react";
|
import { type LucideIcon, XIcon } from "lucide-react";
|
||||||
import { forwardRef, type ComponentProps, type HTMLAttributes } from "react";
|
import { forwardRef, type ComponentProps, type HTMLAttributes } from "react";
|
||||||
|
|
||||||
export type ArtifactProps = HTMLAttributes<HTMLDivElement>;
|
export type ArtifactProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const Artifact = ({ className, ...props }: ArtifactProps) => (
|
export const Artifact = ({ className, ...props }: ArtifactProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col overflow-hidden rounded-[1.35rem] border border-border bg-background shadow-[0_24px_60px_-28px_rgba(15,23,42,0.16)] ring-1 ring-black/3",
|
"flex flex-col overflow-hidden rounded-[1.35rem] border border-border bg-background shadow-[0_24px_60px_-28px_rgba(15,23,42,0.16)] ring-1 ring-black/3",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactHeaderProps = HTMLAttributes<HTMLDivElement>;
|
export type ArtifactHeaderProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const ArtifactHeader = ({
|
export const ArtifactHeader = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ArtifactHeaderProps) => (
|
}: ArtifactHeaderProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-between border-b border-border bg-background px-4 py-3",
|
"flex items-center justify-between border-b border-border bg-background px-4 py-3",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactCloseProps = ComponentProps<typeof Button>;
|
export type ArtifactCloseProps = ComponentProps<typeof Button>;
|
||||||
|
|
||||||
export const ArtifactClose = ({
|
export const ArtifactClose = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
size = "sm",
|
size = "sm",
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
...props
|
...props
|
||||||
}: ArtifactCloseProps) => (
|
}: ArtifactCloseProps) => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground size-8 p-0",
|
"text-muted-foreground hover:text-foreground size-8 p-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
size={size}
|
size={size}
|
||||||
type="button"
|
type="button"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <XIcon className="size-4" />}
|
{children ?? <XIcon className="size-4" />}
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
|
export type ArtifactTitleProps = HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|
||||||
export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
|
export const ArtifactTitle = ({ className, ...props }: ArtifactTitleProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground text-sm font-semibold tracking-tight",
|
"text-foreground text-sm font-semibold tracking-tight",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
|
export type ArtifactDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
|
||||||
|
|
||||||
export const ArtifactDescription = ({
|
export const ArtifactDescription = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ArtifactDescriptionProps) => (
|
}: ArtifactDescriptionProps) => (
|
||||||
<p className={cn("text-muted-foreground text-sm", className)} {...props} />
|
<p className={cn("text-muted-foreground text-sm", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactActionsProps = HTMLAttributes<HTMLDivElement>;
|
export type ArtifactActionsProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const ArtifactActions = ({
|
export const ArtifactActions = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ArtifactActionsProps) => (
|
}: ArtifactActionsProps) => (
|
||||||
<div className={cn("flex items-center gap-1.5", className)} {...props} />
|
<div className={cn("flex items-center gap-1.5", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ArtifactActionProps = ComponentProps<typeof Button> & {
|
export type ArtifactActionProps = ComponentProps<typeof Button> & {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
icon?: LucideIcon;
|
icon?: LucideIcon;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArtifactAction = ({
|
export const ArtifactAction = ({
|
||||||
tooltip,
|
tooltip,
|
||||||
label,
|
label,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
size = "sm",
|
size = "sm",
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
...props
|
...props
|
||||||
}: ArtifactActionProps) => {
|
}: ArtifactActionProps) => {
|
||||||
const button = (
|
const button = (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground hover:bg-accent/70 size-8 rounded-full p-0 transition-all duration-200 hover:scale-[1.03]",
|
"text-muted-foreground hover:text-foreground hover:bg-accent/70 size-8 rounded-full p-0 transition-all duration-200 hover:scale-[1.03]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
size={size}
|
size={size}
|
||||||
type="button"
|
type="button"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Icon ? <Icon className="size-4" /> : children}
|
{Icon ? <Icon className="size-4" /> : children}
|
||||||
<span className="sr-only">{label || tooltip}</span>
|
<span className="sr-only">{label || tooltip}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{tooltip}</p>
|
<p>{tooltip}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ArtifactContentProps = HTMLAttributes<HTMLDivElement>;
|
export type ArtifactContentProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const ArtifactContent = forwardRef<HTMLDivElement, ArtifactContentProps>(
|
export const ArtifactContent = forwardRef<HTMLDivElement, ArtifactContentProps>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-h-0 flex-1 overflow-auto bg-background p-4",
|
"min-h-0 flex-1 overflow-auto bg-background p-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
ArtifactContent.displayName = "ArtifactContent";
|
ArtifactContent.displayName = "ArtifactContent";
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import { Background, ReactFlow, type ReactFlowProps } from "@xyflow/react";
|
import { Background, ReactFlow, type ReactFlowProps } from "@xyflow/react";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import "@xyflow/react/dist/style.css";
|
import "@xyflow/react/dist/style.css";
|
||||||
|
|
||||||
type CanvasProps = ReactFlowProps & {
|
type CanvasProps = ReactFlowProps & {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Canvas = ({ children, ...props }: CanvasProps) => (
|
export const Canvas = ({ children, ...props }: CanvasProps) => (
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
deleteKeyCode={["Backspace", "Delete"]}
|
deleteKeyCode={["Backspace", "Delete"]}
|
||||||
fitView
|
fitView
|
||||||
panOnDrag={false}
|
panOnDrag={false}
|
||||||
panOnScroll
|
panOnScroll
|
||||||
selectionOnDrag={true}
|
selectionOnDrag={true}
|
||||||
zoomOnDoubleClick={false}
|
zoomOnDoubleClick={false}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<Background bgColor="var(--sidebar)" />
|
<Background bgColor="var(--sidebar)" />
|
||||||
{children}
|
{children}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
@@ -1,239 +1,239 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
BrainIcon,
|
BrainIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
DotIcon,
|
DotIcon,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { ComponentProps, ReactNode } from "react";
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
memo,
|
memo,
|
||||||
useContext,
|
useContext,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
type ChainOfThoughtContextValue = {
|
type ChainOfThoughtContextValue = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: (open: boolean) => void;
|
setIsOpen: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(
|
const ChainOfThoughtContext = createContext<ChainOfThoughtContextValue | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const useChainOfThought = () => {
|
const useChainOfThought = () => {
|
||||||
const context = useContext(ChainOfThoughtContext);
|
const context = useContext(ChainOfThoughtContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"ChainOfThought components must be used within ChainOfThought",
|
"ChainOfThought components must be used within ChainOfThought",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChainOfThoughtProps = ComponentProps<"div"> & {
|
export type ChainOfThoughtProps = ComponentProps<"div"> & {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChainOfThought = memo(
|
export const ChainOfThought = memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
open,
|
open,
|
||||||
defaultOpen = false,
|
defaultOpen = false,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ChainOfThoughtProps) => {
|
}: ChainOfThoughtProps) => {
|
||||||
const [isOpen, setIsOpen] = useControllableState({
|
const [isOpen, setIsOpen] = useControllableState({
|
||||||
prop: open,
|
prop: open,
|
||||||
defaultProp: defaultOpen,
|
defaultProp: defaultOpen,
|
||||||
onChange: onOpenChange,
|
onChange: onOpenChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
const chainOfThoughtContext = useMemo(
|
const chainOfThoughtContext = useMemo(
|
||||||
() => ({ isOpen, setIsOpen }),
|
() => ({ isOpen, setIsOpen }),
|
||||||
[isOpen, setIsOpen],
|
[isOpen, setIsOpen],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChainOfThoughtContext.Provider value={chainOfThoughtContext}>
|
<ChainOfThoughtContext.Provider value={chainOfThoughtContext}>
|
||||||
<div className={cn("not-prose", className)} {...props}>
|
<div className={cn("not-prose", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</ChainOfThoughtContext.Provider>
|
</ChainOfThoughtContext.Provider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtHeaderProps = ComponentProps<
|
export type ChainOfThoughtHeaderProps = ComponentProps<
|
||||||
typeof CollapsibleTrigger
|
typeof CollapsibleTrigger
|
||||||
> & {
|
> & {
|
||||||
icon?: React.ReactElement;
|
icon?: React.ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChainOfThoughtHeader = memo(
|
export const ChainOfThoughtHeader = memo(
|
||||||
({ className, children, icon, ...props }: ChainOfThoughtHeaderProps) => {
|
({ className, children, icon, ...props }: ChainOfThoughtHeaderProps) => {
|
||||||
const { isOpen, setIsOpen } = useChainOfThought();
|
const { isOpen, setIsOpen } = useChainOfThought();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible onOpenChange={setIsOpen} open={isOpen}>
|
<Collapsible onOpenChange={setIsOpen} open={isOpen}>
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground flex w-full items-center gap-2 text-sm transition-colors",
|
"text-muted-foreground hover:text-foreground flex w-full items-center gap-2 text-sm transition-colors",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{icon ?? <BrainIcon className="size-4" />}
|
{icon ?? <BrainIcon className="size-4" />}
|
||||||
<span className="flex-1 text-left">
|
<span className="flex-1 text-left">
|
||||||
{children ?? "Chain of Thought"}
|
{children ?? "Chain of Thought"}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-4 transition-transform",
|
"size-4 transition-transform",
|
||||||
isOpen ? "rotate-180" : "rotate-0",
|
isOpen ? "rotate-180" : "rotate-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtStepProps = ComponentProps<"div"> & {
|
export type ChainOfThoughtStepProps = ComponentProps<"div"> & {
|
||||||
icon?: LucideIcon | React.ReactElement;
|
icon?: LucideIcon | React.ReactElement;
|
||||||
label: ReactNode;
|
label: ReactNode;
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
status?: "complete" | "active" | "pending";
|
status?: "complete" | "active" | "pending";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChainOfThoughtStep = memo(
|
export const ChainOfThoughtStep = memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
icon: Icon = DotIcon,
|
icon: Icon = DotIcon,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
status = "complete",
|
status = "complete",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ChainOfThoughtStepProps) => {
|
}: ChainOfThoughtStepProps) => {
|
||||||
const statusStyles = {
|
const statusStyles = {
|
||||||
complete: "text-muted-foreground",
|
complete: "text-muted-foreground",
|
||||||
active: "text-foreground",
|
active: "text-foreground",
|
||||||
pending: "text-muted-foreground/50",
|
pending: "text-muted-foreground/50",
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-2 text-sm",
|
"flex gap-2 text-sm",
|
||||||
statusStyles[status],
|
statusStyles[status],
|
||||||
"fade-in-0 slide-in-from-top-2 animate-in",
|
"fade-in-0 slide-in-from-top-2 animate-in",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="relative mt-0.5">
|
<div className="relative mt-0.5">
|
||||||
{isValidElement(Icon) ? Icon : <Icon className="size-4" />}
|
{isValidElement(Icon) ? Icon : <Icon className="size-4" />}
|
||||||
<div className="bg-border absolute top-7 bottom-0 left-1/2 -mx-px w-px" />
|
<div className="bg-border absolute top-7 bottom-0 left-1/2 -mx-px w-px" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 space-y-2 overflow-hidden">
|
<div className="flex-1 space-y-2 overflow-hidden">
|
||||||
<div>{label}</div>
|
<div>{label}</div>
|
||||||
{description && (
|
{description && (
|
||||||
<div className="text-muted-foreground text-xs">{description}</div>
|
<div className="text-muted-foreground text-xs">{description}</div>
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;
|
export type ChainOfThoughtSearchResultsProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ChainOfThoughtSearchResults = memo(
|
export const ChainOfThoughtSearchResults = memo(
|
||||||
({ className, ...props }: ChainOfThoughtSearchResultsProps) => (
|
({ className, ...props }: ChainOfThoughtSearchResultsProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-wrap items-center gap-2 overflow-x-hidden",
|
"flex flex-wrap items-center gap-2 overflow-x-hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
|
export type ChainOfThoughtSearchResultProps = ComponentProps<typeof Badge>;
|
||||||
|
|
||||||
export const ChainOfThoughtSearchResult = memo(
|
export const ChainOfThoughtSearchResult = memo(
|
||||||
({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (
|
({ className, children, ...props }: ChainOfThoughtSearchResultProps) => (
|
||||||
<Badge
|
<Badge
|
||||||
className={cn("gap-1 px-2 py-0.5 text-xs font-normal", className)}
|
className={cn("gap-1 px-2 py-0.5 text-xs font-normal", className)}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtContentProps = ComponentProps<
|
export type ChainOfThoughtContentProps = ComponentProps<
|
||||||
typeof CollapsibleContent
|
typeof CollapsibleContent
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ChainOfThoughtContent = memo(
|
export const ChainOfThoughtContent = memo(
|
||||||
({ className, children, ...props }: ChainOfThoughtContentProps) => {
|
({ className, children, ...props }: ChainOfThoughtContentProps) => {
|
||||||
const { isOpen } = useChainOfThought();
|
const { isOpen } = useChainOfThought();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={isOpen}>
|
<Collapsible open={isOpen}>
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 space-y-3",
|
"mt-2 space-y-3",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ChainOfThoughtImageProps = ComponentProps<"div"> & {
|
export type ChainOfThoughtImageProps = ComponentProps<"div"> & {
|
||||||
caption?: string;
|
caption?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChainOfThoughtImage = memo(
|
export const ChainOfThoughtImage = memo(
|
||||||
({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (
|
({ className, children, caption, ...props }: ChainOfThoughtImageProps) => (
|
||||||
<div className={cn("mt-2 space-y-2", className)} {...props}>
|
<div className={cn("mt-2 space-y-2", className)} {...props}>
|
||||||
<div className="bg-muted relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg p-3">
|
<div className="bg-muted relative flex max-h-[22rem] items-center justify-center overflow-hidden rounded-lg p-3">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
{caption && <p className="text-muted-foreground text-xs">{caption}</p>}
|
{caption && <p className="text-muted-foreground text-xs">{caption}</p>}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
ChainOfThought.displayName = "ChainOfThought";
|
ChainOfThought.displayName = "ChainOfThought";
|
||||||
ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";
|
ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";
|
||||||
ChainOfThoughtStep.displayName = "ChainOfThoughtStep";
|
ChainOfThoughtStep.displayName = "ChainOfThoughtStep";
|
||||||
ChainOfThoughtSearchResults.displayName = "ChainOfThoughtSearchResults";
|
ChainOfThoughtSearchResults.displayName = "ChainOfThoughtSearchResults";
|
||||||
ChainOfThoughtSearchResult.displayName = "ChainOfThoughtSearchResult";
|
ChainOfThoughtSearchResult.displayName = "ChainOfThoughtSearchResult";
|
||||||
ChainOfThoughtContent.displayName = "ChainOfThoughtContent";
|
ChainOfThoughtContent.displayName = "ChainOfThoughtContent";
|
||||||
ChainOfThoughtImage.displayName = "ChainOfThoughtImage";
|
ChainOfThoughtImage.displayName = "ChainOfThoughtImage";
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { BookmarkIcon, type LucideProps } from "lucide-react";
|
import { BookmarkIcon, type LucideProps } from "lucide-react";
|
||||||
import type { ComponentProps, HTMLAttributes } from "react";
|
import type { ComponentProps, HTMLAttributes } from "react";
|
||||||
|
|
||||||
export type CheckpointProps = HTMLAttributes<HTMLDivElement>;
|
export type CheckpointProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const Checkpoint = ({
|
export const Checkpoint = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: CheckpointProps) => (
|
}: CheckpointProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex items-center gap-0.5 overflow-hidden",
|
"text-muted-foreground flex items-center gap-0.5 overflow-hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type CheckpointIconProps = LucideProps;
|
export type CheckpointIconProps = LucideProps;
|
||||||
|
|
||||||
export const CheckpointIcon = ({
|
export const CheckpointIcon = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: CheckpointIconProps) =>
|
}: CheckpointIconProps) =>
|
||||||
children ?? (
|
children ?? (
|
||||||
<BookmarkIcon className={cn("size-4 shrink-0", className)} {...props} />
|
<BookmarkIcon className={cn("size-4 shrink-0", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type CheckpointTriggerProps = ComponentProps<typeof Button> & {
|
export type CheckpointTriggerProps = ComponentProps<typeof Button> & {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CheckpointTrigger = ({
|
export const CheckpointTrigger = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
size = "sm",
|
size = "sm",
|
||||||
tooltip,
|
tooltip,
|
||||||
...props
|
...props
|
||||||
}: CheckpointTriggerProps) =>
|
}: CheckpointTriggerProps) =>
|
||||||
tooltip ? (
|
tooltip ? (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button size={size} type="button" variant={variant} {...props}>
|
<Button size={size} type="button" variant={variant} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent align="start" side="bottom">
|
<TooltipContent align="start" side="bottom">
|
||||||
{tooltip}
|
{tooltip}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Button size={size} type="button" variant={variant} {...props}>
|
<Button size={size} type="button" variant={variant} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -1,150 +1,150 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { copyTextToClipboard } from "@/core/clipboard";
|
import { copyTextToClipboard } from "@/core/clipboard";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
type ComponentProps,
|
type ComponentProps,
|
||||||
createContext,
|
createContext,
|
||||||
type HTMLAttributes,
|
type HTMLAttributes,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { type BundledLanguage, codeToHtml, type ShikiTransformer } from "shiki";
|
import { type BundledLanguage, codeToHtml, type ShikiTransformer } from "shiki";
|
||||||
|
|
||||||
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
|
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
code: string;
|
code: string;
|
||||||
language: BundledLanguage;
|
language: BundledLanguage;
|
||||||
showLineNumbers?: boolean;
|
showLineNumbers?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CodeBlockContextType = {
|
type CodeBlockContextType = {
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CodeBlockContext = createContext<CodeBlockContextType>({
|
const CodeBlockContext = createContext<CodeBlockContextType>({
|
||||||
code: "",
|
code: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const lineNumberTransformer: ShikiTransformer = {
|
const lineNumberTransformer: ShikiTransformer = {
|
||||||
name: "line-numbers",
|
name: "line-numbers",
|
||||||
line(node, line) {
|
line(node, line) {
|
||||||
node.children.unshift({
|
node.children.unshift({
|
||||||
type: "element",
|
type: "element",
|
||||||
tagName: "span",
|
tagName: "span",
|
||||||
properties: {
|
properties: {
|
||||||
className: [
|
className: [
|
||||||
"inline-block",
|
"inline-block",
|
||||||
"min-w-10",
|
"min-w-10",
|
||||||
"mr-4",
|
"mr-4",
|
||||||
"text-right",
|
"text-right",
|
||||||
"select-none",
|
"select-none",
|
||||||
"text-muted-foreground",
|
"text-muted-foreground",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [{ type: "text", value: String(line) }],
|
children: [{ type: "text", value: String(line) }],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function highlightCode(
|
export async function highlightCode(
|
||||||
code: string,
|
code: string,
|
||||||
language: BundledLanguage,
|
language: BundledLanguage,
|
||||||
showLineNumbers = false,
|
showLineNumbers = false,
|
||||||
) {
|
) {
|
||||||
const transformers: ShikiTransformer[] = showLineNumbers
|
const transformers: ShikiTransformer[] = showLineNumbers
|
||||||
? [lineNumberTransformer]
|
? [lineNumberTransformer]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return await Promise.all([
|
return await Promise.all([
|
||||||
codeToHtml(code, {
|
codeToHtml(code, {
|
||||||
lang: language,
|
lang: language,
|
||||||
theme: "one-light",
|
theme: "one-light",
|
||||||
transformers,
|
transformers,
|
||||||
}),
|
}),
|
||||||
codeToHtml(code, {
|
codeToHtml(code, {
|
||||||
lang: language,
|
lang: language,
|
||||||
theme: "one-dark-pro",
|
theme: "one-dark-pro",
|
||||||
transformers,
|
transformers,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeBlock = ({
|
export const CodeBlock = ({
|
||||||
code,
|
code,
|
||||||
language,
|
language,
|
||||||
showLineNumbers = false,
|
showLineNumbers = false,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: CodeBlockProps) => {
|
}: CodeBlockProps) => {
|
||||||
const [html, setHtml] = useState<string>("");
|
const [html, setHtml] = useState<string>("");
|
||||||
const [darkHtml, setDarkHtml] = useState<string>("");
|
const [darkHtml, setDarkHtml] = useState<string>("");
|
||||||
const mounted = useRef(false);
|
const mounted = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
highlightCode(code, language, showLineNumbers).then(([light, dark]) => {
|
highlightCode(code, language, showLineNumbers).then(([light, dark]) => {
|
||||||
if (!mounted.current) {
|
if (!mounted.current) {
|
||||||
setHtml(light);
|
setHtml(light);
|
||||||
setDarkHtml(dark);
|
setDarkHtml(dark);
|
||||||
mounted.current = true;
|
mounted.current = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mounted.current = false;
|
mounted.current = false;
|
||||||
};
|
};
|
||||||
}, [code, language, showLineNumbers]);
|
}, [code, language, showLineNumbers]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeBlockContext.Provider value={{ code }}>
|
<CodeBlockContext.Provider value={{ code }}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"group bg-background text-foreground relative size-full overflow-hidden rounded-md border",
|
"group bg-background text-foreground relative size-full overflow-hidden rounded-md border",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="relative size-full">
|
<div className="relative size-full">
|
||||||
<div
|
<div
|
||||||
className="[&>pre]:bg-background! [&>pre]:text-foreground! size-full overflow-auto dark:hidden [&_code]:font-mono [&_code]:text-sm [&>pre]:m-0 [&>pre]:text-sm [&>pre]:whitespace-pre-wrap"
|
className="[&>pre]:bg-background! [&>pre]:text-foreground! size-full overflow-auto dark:hidden [&_code]:font-mono [&_code]:text-sm [&>pre]:m-0 [&>pre]:text-sm [&>pre]:whitespace-pre-wrap"
|
||||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="[&>pre]:bg-background! [&>pre]:text-foreground! hidden size-full overflow-auto dark:block [&_code]:font-mono [&_code]:text-sm [&>pre]:m-0 [&>pre]:text-sm [&>pre]:whitespace-pre-wrap"
|
className="[&>pre]:bg-background! [&>pre]:text-foreground! hidden size-full overflow-auto dark:block [&_code]:font-mono [&_code]:text-sm [&>pre]:m-0 [&>pre]:text-sm [&>pre]:whitespace-pre-wrap"
|
||||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
|
||||||
dangerouslySetInnerHTML={{ __html: darkHtml }}
|
dangerouslySetInnerHTML={{ __html: darkHtml }}
|
||||||
/>
|
/>
|
||||||
{children && (
|
{children && (
|
||||||
<div className="absolute top-2 right-2 flex items-center gap-2">
|
<div className="absolute top-2 right-2 flex items-center gap-2">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CodeBlockContext.Provider>
|
</CodeBlockContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
|
export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
|
||||||
onCopy?: () => void;
|
onCopy?: () => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CodeBlockCopyButton = ({
|
export const CodeBlockCopyButton = ({
|
||||||
onCopy,
|
onCopy,
|
||||||
onError,
|
onError,
|
||||||
timeout = 2000,
|
timeout = 2000,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: CodeBlockCopyButtonProps) => {
|
}: CodeBlockCopyButtonProps) => {
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
const { code } = useContext(CodeBlockContext);
|
const { code } = useContext(CodeBlockContext);
|
||||||
|
|
||||||
const copyToClipboard = async () => {
|
const copyToClipboard = async () => {
|
||||||
@@ -152,23 +152,23 @@ export const CodeBlockCopyButton = ({
|
|||||||
await copyTextToClipboard(code);
|
await copyTextToClipboard(code);
|
||||||
setIsCopied(true);
|
setIsCopied(true);
|
||||||
onCopy?.();
|
onCopy?.();
|
||||||
setTimeout(() => setIsCopied(false), timeout);
|
setTimeout(() => setIsCopied(false), timeout);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError?.(error as Error);
|
onError?.(error as Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Icon = isCopied ? CheckIcon : CopyIcon;
|
const Icon = isCopied ? CheckIcon : CopyIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={cn("shrink-0", className)}
|
className={cn("shrink-0", className)}
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <Icon size={14} />}
|
{children ?? <Icon size={14} />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
import type { ConnectionLineComponent } from "@xyflow/react";
|
import type { ConnectionLineComponent } from "@xyflow/react";
|
||||||
|
|
||||||
const HALF = 0.5;
|
const HALF = 0.5;
|
||||||
|
|
||||||
export const Connection: ConnectionLineComponent = ({
|
export const Connection: ConnectionLineComponent = ({
|
||||||
fromX,
|
fromX,
|
||||||
fromY,
|
fromY,
|
||||||
toX,
|
toX,
|
||||||
toY,
|
toY,
|
||||||
}) => (
|
}) => (
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
className="animated"
|
className="animated"
|
||||||
d={`M${fromX},${fromY} C ${fromX + (toX - fromX) * HALF},${fromY} ${fromX + (toX - fromX) * HALF},${toY} ${toX},${toY}`}
|
d={`M${fromX},${fromY} C ${fromX + (toX - fromX) * HALF},${fromY} ${fromX + (toX - fromX) * HALF},${toY} ${toX},${toY}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="var(--color-ring)"
|
stroke="var(--color-ring)"
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={toX}
|
cx={toX}
|
||||||
cy={toY}
|
cy={toY}
|
||||||
fill="#fff"
|
fill="#fff"
|
||||||
r={3}
|
r={3}
|
||||||
stroke="var(--color-ring)"
|
stroke="var(--color-ring)"
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
@@ -1,408 +1,408 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from "@/components/ui/hover-card";
|
} from "@/components/ui/hover-card";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { LanguageModelUsage } from "ai";
|
import type { LanguageModelUsage } from "ai";
|
||||||
import { type ComponentProps, createContext, useContext } from "react";
|
import { type ComponentProps, createContext, useContext } from "react";
|
||||||
import { getUsage } from "tokenlens";
|
import { getUsage } from "tokenlens";
|
||||||
|
|
||||||
const PERCENT_MAX = 100;
|
const PERCENT_MAX = 100;
|
||||||
const ICON_RADIUS = 10;
|
const ICON_RADIUS = 10;
|
||||||
const ICON_VIEWBOX = 24;
|
const ICON_VIEWBOX = 24;
|
||||||
const ICON_CENTER = 12;
|
const ICON_CENTER = 12;
|
||||||
const ICON_STROKE_WIDTH = 2;
|
const ICON_STROKE_WIDTH = 2;
|
||||||
|
|
||||||
type ModelId = string;
|
type ModelId = string;
|
||||||
|
|
||||||
type ContextSchema = {
|
type ContextSchema = {
|
||||||
usedTokens: number;
|
usedTokens: number;
|
||||||
maxTokens: number;
|
maxTokens: number;
|
||||||
usage?: LanguageModelUsage;
|
usage?: LanguageModelUsage;
|
||||||
modelId?: ModelId;
|
modelId?: ModelId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextContext = createContext<ContextSchema | null>(null);
|
const ContextContext = createContext<ContextSchema | null>(null);
|
||||||
|
|
||||||
const useContextValue = () => {
|
const useContextValue = () => {
|
||||||
const context = useContext(ContextContext);
|
const context = useContext(ContextContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context components must be used within Context");
|
throw new Error("Context components must be used within Context");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextProps = ComponentProps<typeof HoverCard> & ContextSchema;
|
export type ContextProps = ComponentProps<typeof HoverCard> & ContextSchema;
|
||||||
|
|
||||||
export const Context = ({
|
export const Context = ({
|
||||||
usedTokens,
|
usedTokens,
|
||||||
maxTokens,
|
maxTokens,
|
||||||
usage,
|
usage,
|
||||||
modelId,
|
modelId,
|
||||||
...props
|
...props
|
||||||
}: ContextProps) => (
|
}: ContextProps) => (
|
||||||
<ContextContext.Provider
|
<ContextContext.Provider
|
||||||
value={{
|
value={{
|
||||||
usedTokens,
|
usedTokens,
|
||||||
maxTokens,
|
maxTokens,
|
||||||
usage,
|
usage,
|
||||||
modelId,
|
modelId,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HoverCard closeDelay={0} openDelay={0} {...props} />
|
<HoverCard closeDelay={0} openDelay={0} {...props} />
|
||||||
</ContextContext.Provider>
|
</ContextContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ContextIcon = () => {
|
const ContextIcon = () => {
|
||||||
const { usedTokens, maxTokens } = useContextValue();
|
const { usedTokens, maxTokens } = useContextValue();
|
||||||
const circumference = 2 * Math.PI * ICON_RADIUS;
|
const circumference = 2 * Math.PI * ICON_RADIUS;
|
||||||
const usedPercent = usedTokens / maxTokens;
|
const usedPercent = usedTokens / maxTokens;
|
||||||
const dashOffset = circumference * (1 - usedPercent);
|
const dashOffset = circumference * (1 - usedPercent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-label="Model context usage"
|
aria-label="Model context usage"
|
||||||
height="20"
|
height="20"
|
||||||
role="img"
|
role="img"
|
||||||
style={{ color: "currentcolor" }}
|
style={{ color: "currentcolor" }}
|
||||||
viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`}
|
viewBox={`0 0 ${ICON_VIEWBOX} ${ICON_VIEWBOX}`}
|
||||||
width="20"
|
width="20"
|
||||||
>
|
>
|
||||||
<circle
|
<circle
|
||||||
cx={ICON_CENTER}
|
cx={ICON_CENTER}
|
||||||
cy={ICON_CENTER}
|
cy={ICON_CENTER}
|
||||||
fill="none"
|
fill="none"
|
||||||
opacity="0.25"
|
opacity="0.25"
|
||||||
r={ICON_RADIUS}
|
r={ICON_RADIUS}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={ICON_STROKE_WIDTH}
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
/>
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={ICON_CENTER}
|
cx={ICON_CENTER}
|
||||||
cy={ICON_CENTER}
|
cy={ICON_CENTER}
|
||||||
fill="none"
|
fill="none"
|
||||||
opacity="0.7"
|
opacity="0.7"
|
||||||
r={ICON_RADIUS}
|
r={ICON_RADIUS}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeDasharray={`${circumference} ${circumference}`}
|
strokeDasharray={`${circumference} ${circumference}`}
|
||||||
strokeDashoffset={dashOffset}
|
strokeDashoffset={dashOffset}
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeWidth={ICON_STROKE_WIDTH}
|
strokeWidth={ICON_STROKE_WIDTH}
|
||||||
style={{ transformOrigin: "center", transform: "rotate(-90deg)" }}
|
style={{ transformOrigin: "center", transform: "rotate(-90deg)" }}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextTriggerProps = ComponentProps<typeof Button>;
|
export type ContextTriggerProps = ComponentProps<typeof Button>;
|
||||||
|
|
||||||
export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => {
|
export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => {
|
||||||
const { usedTokens, maxTokens } = useContextValue();
|
const { usedTokens, maxTokens } = useContextValue();
|
||||||
const usedPercent = usedTokens / maxTokens;
|
const usedPercent = usedTokens / maxTokens;
|
||||||
const renderedPercent = new Intl.NumberFormat("en-US", {
|
const renderedPercent = new Intl.NumberFormat("en-US", {
|
||||||
style: "percent",
|
style: "percent",
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
}).format(usedPercent);
|
}).format(usedPercent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<Button type="button" variant="ghost" {...props}>
|
<Button type="button" variant="ghost" {...props}>
|
||||||
<span className="text-muted-foreground font-medium">
|
<span className="text-muted-foreground font-medium">
|
||||||
{renderedPercent}
|
{renderedPercent}
|
||||||
</span>
|
</span>
|
||||||
<ContextIcon />
|
<ContextIcon />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextContentProps = ComponentProps<typeof HoverCardContent>;
|
export type ContextContentProps = ComponentProps<typeof HoverCardContent>;
|
||||||
|
|
||||||
export const ContextContent = ({
|
export const ContextContent = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ContextContentProps) => (
|
}: ContextContentProps) => (
|
||||||
<HoverCardContent
|
<HoverCardContent
|
||||||
className={cn("min-w-60 divide-y overflow-hidden p-0", className)}
|
className={cn("min-w-60 divide-y overflow-hidden p-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ContextContentHeaderProps = ComponentProps<"div">;
|
export type ContextContentHeaderProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextContentHeader = ({
|
export const ContextContentHeader = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ContextContentHeaderProps) => {
|
}: ContextContentHeaderProps) => {
|
||||||
const { usedTokens, maxTokens } = useContextValue();
|
const { usedTokens, maxTokens } = useContextValue();
|
||||||
const usedPercent = usedTokens / maxTokens;
|
const usedPercent = usedTokens / maxTokens;
|
||||||
const displayPct = new Intl.NumberFormat("en-US", {
|
const displayPct = new Intl.NumberFormat("en-US", {
|
||||||
style: "percent",
|
style: "percent",
|
||||||
maximumFractionDigits: 1,
|
maximumFractionDigits: 1,
|
||||||
}).format(usedPercent);
|
}).format(usedPercent);
|
||||||
const used = new Intl.NumberFormat("en-US", {
|
const used = new Intl.NumberFormat("en-US", {
|
||||||
notation: "compact",
|
notation: "compact",
|
||||||
}).format(usedTokens);
|
}).format(usedTokens);
|
||||||
const total = new Intl.NumberFormat("en-US", {
|
const total = new Intl.NumberFormat("en-US", {
|
||||||
notation: "compact",
|
notation: "compact",
|
||||||
}).format(maxTokens);
|
}).format(maxTokens);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("w-full space-y-2 p-3", className)} {...props}>
|
<div className={cn("w-full space-y-2 p-3", className)} {...props}>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between gap-3 text-xs">
|
<div className="flex items-center justify-between gap-3 text-xs">
|
||||||
<p>{displayPct}</p>
|
<p>{displayPct}</p>
|
||||||
<p className="text-muted-foreground font-mono">
|
<p className="text-muted-foreground font-mono">
|
||||||
{used} / {total}
|
{used} / {total}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Progress className="bg-muted" value={usedPercent * PERCENT_MAX} />
|
<Progress className="bg-muted" value={usedPercent * PERCENT_MAX} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextContentBodyProps = ComponentProps<"div">;
|
export type ContextContentBodyProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextContentBody = ({
|
export const ContextContentBody = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ContextContentBodyProps) => (
|
}: ContextContentBodyProps) => (
|
||||||
<div className={cn("w-full p-3", className)} {...props}>
|
<div className={cn("w-full p-3", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ContextContentFooterProps = ComponentProps<"div">;
|
export type ContextContentFooterProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextContentFooter = ({
|
export const ContextContentFooter = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ContextContentFooterProps) => {
|
}: ContextContentFooterProps) => {
|
||||||
const { modelId, usage } = useContextValue();
|
const { modelId, usage } = useContextValue();
|
||||||
const costUSD = modelId
|
const costUSD = modelId
|
||||||
? getUsage({
|
? getUsage({
|
||||||
modelId,
|
modelId,
|
||||||
usage: {
|
usage: {
|
||||||
input: usage?.inputTokens ?? 0,
|
input: usage?.inputTokens ?? 0,
|
||||||
output: usage?.outputTokens ?? 0,
|
output: usage?.outputTokens ?? 0,
|
||||||
},
|
},
|
||||||
}).costUSD?.totalUSD
|
}).costUSD?.totalUSD
|
||||||
: undefined;
|
: undefined;
|
||||||
const totalCost = new Intl.NumberFormat("en-US", {
|
const totalCost = new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}).format(costUSD ?? 0);
|
}).format(costUSD ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-secondary flex w-full items-center justify-between gap-3 p-3 text-xs",
|
"bg-secondary flex w-full items-center justify-between gap-3 p-3 text-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground">Total cost</span>
|
<span className="text-muted-foreground">Total cost</span>
|
||||||
<span>{totalCost}</span>
|
<span>{totalCost}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextInputUsageProps = ComponentProps<"div">;
|
export type ContextInputUsageProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextInputUsage = ({
|
export const ContextInputUsage = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ContextInputUsageProps) => {
|
}: ContextInputUsageProps) => {
|
||||||
const { usage, modelId } = useContextValue();
|
const { usage, modelId } = useContextValue();
|
||||||
const inputTokens = usage?.inputTokens ?? 0;
|
const inputTokens = usage?.inputTokens ?? 0;
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inputTokens) {
|
if (!inputTokens) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputCost = modelId
|
const inputCost = modelId
|
||||||
? getUsage({
|
? getUsage({
|
||||||
modelId,
|
modelId,
|
||||||
usage: { input: inputTokens, output: 0 },
|
usage: { input: inputTokens, output: 0 },
|
||||||
}).costUSD?.totalUSD
|
}).costUSD?.totalUSD
|
||||||
: undefined;
|
: undefined;
|
||||||
const inputCostText = new Intl.NumberFormat("en-US", {
|
const inputCostText = new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}).format(inputCost ?? 0);
|
}).format(inputCost ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center justify-between text-xs", className)}
|
className={cn("flex items-center justify-between text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground">Input</span>
|
<span className="text-muted-foreground">Input</span>
|
||||||
<TokensWithCost costText={inputCostText} tokens={inputTokens} />
|
<TokensWithCost costText={inputCostText} tokens={inputTokens} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextOutputUsageProps = ComponentProps<"div">;
|
export type ContextOutputUsageProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextOutputUsage = ({
|
export const ContextOutputUsage = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ContextOutputUsageProps) => {
|
}: ContextOutputUsageProps) => {
|
||||||
const { usage, modelId } = useContextValue();
|
const { usage, modelId } = useContextValue();
|
||||||
const outputTokens = usage?.outputTokens ?? 0;
|
const outputTokens = usage?.outputTokens ?? 0;
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!outputTokens) {
|
if (!outputTokens) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputCost = modelId
|
const outputCost = modelId
|
||||||
? getUsage({
|
? getUsage({
|
||||||
modelId,
|
modelId,
|
||||||
usage: { input: 0, output: outputTokens },
|
usage: { input: 0, output: outputTokens },
|
||||||
}).costUSD?.totalUSD
|
}).costUSD?.totalUSD
|
||||||
: undefined;
|
: undefined;
|
||||||
const outputCostText = new Intl.NumberFormat("en-US", {
|
const outputCostText = new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}).format(outputCost ?? 0);
|
}).format(outputCost ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center justify-between text-xs", className)}
|
className={cn("flex items-center justify-between text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground">Output</span>
|
<span className="text-muted-foreground">Output</span>
|
||||||
<TokensWithCost costText={outputCostText} tokens={outputTokens} />
|
<TokensWithCost costText={outputCostText} tokens={outputTokens} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextReasoningUsageProps = ComponentProps<"div">;
|
export type ContextReasoningUsageProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextReasoningUsage = ({
|
export const ContextReasoningUsage = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ContextReasoningUsageProps) => {
|
}: ContextReasoningUsageProps) => {
|
||||||
const { usage, modelId } = useContextValue();
|
const { usage, modelId } = useContextValue();
|
||||||
const reasoningTokens = usage?.reasoningTokens ?? 0;
|
const reasoningTokens = usage?.reasoningTokens ?? 0;
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reasoningTokens) {
|
if (!reasoningTokens) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reasoningCost = modelId
|
const reasoningCost = modelId
|
||||||
? getUsage({
|
? getUsage({
|
||||||
modelId,
|
modelId,
|
||||||
usage: { reasoningTokens },
|
usage: { reasoningTokens },
|
||||||
}).costUSD?.totalUSD
|
}).costUSD?.totalUSD
|
||||||
: undefined;
|
: undefined;
|
||||||
const reasoningCostText = new Intl.NumberFormat("en-US", {
|
const reasoningCostText = new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}).format(reasoningCost ?? 0);
|
}).format(reasoningCost ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center justify-between text-xs", className)}
|
className={cn("flex items-center justify-between text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground">Reasoning</span>
|
<span className="text-muted-foreground">Reasoning</span>
|
||||||
<TokensWithCost costText={reasoningCostText} tokens={reasoningTokens} />
|
<TokensWithCost costText={reasoningCostText} tokens={reasoningTokens} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextCacheUsageProps = ComponentProps<"div">;
|
export type ContextCacheUsageProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ContextCacheUsage = ({
|
export const ContextCacheUsage = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ContextCacheUsageProps) => {
|
}: ContextCacheUsageProps) => {
|
||||||
const { usage, modelId } = useContextValue();
|
const { usage, modelId } = useContextValue();
|
||||||
const cacheTokens = usage?.cachedInputTokens ?? 0;
|
const cacheTokens = usage?.cachedInputTokens ?? 0;
|
||||||
|
|
||||||
if (children) {
|
if (children) {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cacheTokens) {
|
if (!cacheTokens) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheCost = modelId
|
const cacheCost = modelId
|
||||||
? getUsage({
|
? getUsage({
|
||||||
modelId,
|
modelId,
|
||||||
usage: { cacheReads: cacheTokens, input: 0, output: 0 },
|
usage: { cacheReads: cacheTokens, input: 0, output: 0 },
|
||||||
}).costUSD?.totalUSD
|
}).costUSD?.totalUSD
|
||||||
: undefined;
|
: undefined;
|
||||||
const cacheCostText = new Intl.NumberFormat("en-US", {
|
const cacheCostText = new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
}).format(cacheCost ?? 0);
|
}).format(cacheCost ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center justify-between text-xs", className)}
|
className={cn("flex items-center justify-between text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground">Cache</span>
|
<span className="text-muted-foreground">Cache</span>
|
||||||
<TokensWithCost costText={cacheCostText} tokens={cacheTokens} />
|
<TokensWithCost costText={cacheCostText} tokens={cacheTokens} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const TokensWithCost = ({
|
const TokensWithCost = ({
|
||||||
tokens,
|
tokens,
|
||||||
costText,
|
costText,
|
||||||
}: {
|
}: {
|
||||||
tokens?: number;
|
tokens?: number;
|
||||||
costText?: string;
|
costText?: string;
|
||||||
}) => (
|
}) => (
|
||||||
<span>
|
<span>
|
||||||
{tokens === undefined
|
{tokens === undefined
|
||||||
? "—"
|
? "—"
|
||||||
: new Intl.NumberFormat("en-US", {
|
: new Intl.NumberFormat("en-US", {
|
||||||
notation: "compact",
|
notation: "compact",
|
||||||
}).format(tokens)}
|
}).format(tokens)}
|
||||||
{costText ? (
|
{costText ? (
|
||||||
<span className="text-muted-foreground ml-2">• {costText}</span>
|
<span className="text-muted-foreground ml-2">• {costText}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Controls as ControlsPrimitive } from "@xyflow/react";
|
import { Controls as ControlsPrimitive } from "@xyflow/react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type ControlsProps = ComponentProps<typeof ControlsPrimitive>;
|
export type ControlsProps = ComponentProps<typeof ControlsPrimitive>;
|
||||||
|
|
||||||
export const Controls = ({ className, ...props }: ControlsProps) => (
|
export const Controls = ({ className, ...props }: ControlsProps) => (
|
||||||
<ControlsPrimitive
|
<ControlsPrimitive
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card gap-px overflow-hidden rounded-md border p-1 shadow-none!",
|
"bg-card gap-px overflow-hidden rounded-md border p-1 shadow-none!",
|
||||||
"[&>button]:hover:bg-secondary! [&>button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent!",
|
"[&>button]:hover:bg-secondary! [&>button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent!",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,100 +1,100 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ArrowDownIcon } from "lucide-react";
|
import { ArrowDownIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
|
||||||
|
|
||||||
export type ConversationProps = ComponentProps<typeof StickToBottom>;
|
export type ConversationProps = ComponentProps<typeof StickToBottom>;
|
||||||
|
|
||||||
export const Conversation = ({ className, ...props }: ConversationProps) => (
|
export const Conversation = ({ className, ...props }: ConversationProps) => (
|
||||||
<StickToBottom
|
<StickToBottom
|
||||||
className={cn("relative flex-1 overflow-y-hidden", className)}
|
className={cn("relative flex-1 overflow-y-hidden", className)}
|
||||||
initial="smooth"
|
initial="smooth"
|
||||||
resize="smooth"
|
resize="smooth"
|
||||||
role="log"
|
role="log"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ConversationContentProps = ComponentProps<
|
export type ConversationContentProps = ComponentProps<
|
||||||
typeof StickToBottom.Content
|
typeof StickToBottom.Content
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ConversationContent = ({
|
export const ConversationContent = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ConversationContentProps) => (
|
}: ConversationContentProps) => (
|
||||||
<StickToBottom.Content
|
<StickToBottom.Content
|
||||||
className={cn("flex flex-col gap-8 p-4", className)}
|
className={cn("flex flex-col gap-8 p-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ConversationEmptyStateProps = ComponentProps<"div"> & {
|
export type ConversationEmptyStateProps = ComponentProps<"div"> & {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ConversationEmptyState = ({
|
export const ConversationEmptyState = ({
|
||||||
className,
|
className,
|
||||||
title = "No messages yet",
|
title = "No messages yet",
|
||||||
description = "Start a conversation to see messages here",
|
description = "Start a conversation to see messages here",
|
||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ConversationEmptyStateProps) => (
|
}: ConversationEmptyStateProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex size-full flex-col items-center justify-center gap-3 p-8 text-center",
|
"flex size-full flex-col items-center justify-center gap-3 p-8 text-center",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
{icon && <div className="text-muted-foreground">{icon}</div>}
|
{icon && <div className="text-muted-foreground">{icon}</div>}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-sm font-medium">{title}</h3>
|
<h3 className="text-sm font-medium">{title}</h3>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-muted-foreground text-sm">{description}</p>
|
<p className="text-muted-foreground text-sm">{description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ConversationScrollButtonProps = ComponentProps<typeof Button>;
|
export type ConversationScrollButtonProps = ComponentProps<typeof Button>;
|
||||||
|
|
||||||
export const ConversationScrollButton = ({
|
export const ConversationScrollButton = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ConversationScrollButtonProps) => {
|
}: ConversationScrollButtonProps) => {
|
||||||
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
||||||
|
|
||||||
const handleScrollToBottom = useCallback(() => {
|
const handleScrollToBottom = useCallback(() => {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [scrollToBottom]);
|
}, [scrollToBottom]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!isAtBottom && (
|
!isAtBottom && (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
|
"absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onClick={handleScrollToBottom}
|
onClick={handleScrollToBottom}
|
||||||
size="icon"
|
size="icon"
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowDownIcon className="size-4" />
|
<ArrowDownIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,140 +1,140 @@
|
|||||||
import {
|
import {
|
||||||
BaseEdge,
|
BaseEdge,
|
||||||
type EdgeProps,
|
type EdgeProps,
|
||||||
getBezierPath,
|
getBezierPath,
|
||||||
getSimpleBezierPath,
|
getSimpleBezierPath,
|
||||||
type InternalNode,
|
type InternalNode,
|
||||||
type Node,
|
type Node,
|
||||||
Position,
|
Position,
|
||||||
useInternalNode,
|
useInternalNode,
|
||||||
} from "@xyflow/react";
|
} from "@xyflow/react";
|
||||||
|
|
||||||
const Temporary = ({
|
const Temporary = ({
|
||||||
id,
|
id,
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
}: EdgeProps) => {
|
}: EdgeProps) => {
|
||||||
const [edgePath] = getSimpleBezierPath({
|
const [edgePath] = getSimpleBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
sourceY,
|
sourceY,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseEdge
|
<BaseEdge
|
||||||
className="stroke-ring stroke-1"
|
className="stroke-ring stroke-1"
|
||||||
id={id}
|
id={id}
|
||||||
path={edgePath}
|
path={edgePath}
|
||||||
style={{
|
style={{
|
||||||
strokeDasharray: "5, 5",
|
strokeDasharray: "5, 5",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getHandleCoordsByPosition = (
|
const getHandleCoordsByPosition = (
|
||||||
node: InternalNode<Node>,
|
node: InternalNode<Node>,
|
||||||
handlePosition: Position,
|
handlePosition: Position,
|
||||||
) => {
|
) => {
|
||||||
// Choose the handle type based on position - Left is for target, Right is for source
|
// Choose the handle type based on position - Left is for target, Right is for source
|
||||||
const handleType = handlePosition === Position.Left ? "target" : "source";
|
const handleType = handlePosition === Position.Left ? "target" : "source";
|
||||||
|
|
||||||
const handle = node.internals.handleBounds?.[handleType]?.find(
|
const handle = node.internals.handleBounds?.[handleType]?.find(
|
||||||
(h) => h.position === handlePosition,
|
(h) => h.position === handlePosition,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
return [0, 0] as const;
|
return [0, 0] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
let offsetX = handle.width / 2;
|
let offsetX = handle.width / 2;
|
||||||
let offsetY = handle.height / 2;
|
let offsetY = handle.height / 2;
|
||||||
|
|
||||||
// this is a tiny detail to make the markerEnd of an edge visible.
|
// this is a tiny detail to make the markerEnd of an edge visible.
|
||||||
// The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
|
// The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset
|
||||||
// when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position
|
// when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position
|
||||||
switch (handlePosition) {
|
switch (handlePosition) {
|
||||||
case Position.Left:
|
case Position.Left:
|
||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
break;
|
break;
|
||||||
case Position.Right:
|
case Position.Right:
|
||||||
offsetX = handle.width;
|
offsetX = handle.width;
|
||||||
break;
|
break;
|
||||||
case Position.Top:
|
case Position.Top:
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
break;
|
break;
|
||||||
case Position.Bottom:
|
case Position.Bottom:
|
||||||
offsetY = handle.height;
|
offsetY = handle.height;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Invalid handle position: ${handlePosition}`);
|
throw new Error(`Invalid handle position: ${handlePosition}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = node.internals.positionAbsolute.x + handle.x + offsetX;
|
const x = node.internals.positionAbsolute.x + handle.x + offsetX;
|
||||||
const y = node.internals.positionAbsolute.y + handle.y + offsetY;
|
const y = node.internals.positionAbsolute.y + handle.y + offsetY;
|
||||||
|
|
||||||
return [x, y] as const;
|
return [x, y] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEdgeParams = (
|
const getEdgeParams = (
|
||||||
source: InternalNode<Node>,
|
source: InternalNode<Node>,
|
||||||
target: InternalNode<Node>,
|
target: InternalNode<Node>,
|
||||||
) => {
|
) => {
|
||||||
const sourcePos = Position.Right;
|
const sourcePos = Position.Right;
|
||||||
const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
|
const [sx, sy] = getHandleCoordsByPosition(source, sourcePos);
|
||||||
const targetPos = Position.Left;
|
const targetPos = Position.Left;
|
||||||
const [tx, ty] = getHandleCoordsByPosition(target, targetPos);
|
const [tx, ty] = getHandleCoordsByPosition(target, targetPos);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sx,
|
sx,
|
||||||
sy,
|
sy,
|
||||||
tx,
|
tx,
|
||||||
ty,
|
ty,
|
||||||
sourcePos,
|
sourcePos,
|
||||||
targetPos,
|
targetPos,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
|
const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => {
|
||||||
const sourceNode = useInternalNode(source);
|
const sourceNode = useInternalNode(source);
|
||||||
const targetNode = useInternalNode(target);
|
const targetNode = useInternalNode(target);
|
||||||
|
|
||||||
if (!(sourceNode && targetNode)) {
|
if (!(sourceNode && targetNode)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
|
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(
|
||||||
sourceNode,
|
sourceNode,
|
||||||
targetNode,
|
targetNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [edgePath] = getBezierPath({
|
const [edgePath] = getBezierPath({
|
||||||
sourceX: sx,
|
sourceX: sx,
|
||||||
sourceY: sy,
|
sourceY: sy,
|
||||||
sourcePosition: sourcePos,
|
sourcePosition: sourcePos,
|
||||||
targetX: tx,
|
targetX: tx,
|
||||||
targetY: ty,
|
targetY: ty,
|
||||||
targetPosition: targetPos,
|
targetPosition: targetPos,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
|
<BaseEdge id={id} markerEnd={markerEnd} path={edgePath} style={style} />
|
||||||
<circle fill="var(--primary)" r="4">
|
<circle fill="var(--primary)" r="4">
|
||||||
<animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
|
<animateMotion dur="2s" path={edgePath} repeatCount="indefinite" />
|
||||||
</circle>
|
</circle>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Edge = {
|
export const Edge = {
|
||||||
Temporary,
|
Temporary,
|
||||||
Animated,
|
Animated,
|
||||||
};
|
};
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { Experimental_GeneratedImage } from "ai";
|
import type { Experimental_GeneratedImage } from "ai";
|
||||||
|
|
||||||
export type ImageProps = Experimental_GeneratedImage & {
|
export type ImageProps = Experimental_GeneratedImage & {
|
||||||
className?: string;
|
className?: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Image = ({
|
export const Image = ({
|
||||||
base64,
|
base64,
|
||||||
uint8Array,
|
uint8Array,
|
||||||
mediaType,
|
mediaType,
|
||||||
...props
|
...props
|
||||||
}: ImageProps) => (
|
}: ImageProps) => (
|
||||||
<img
|
<img
|
||||||
{...props}
|
{...props}
|
||||||
alt={props.alt}
|
alt={props.alt}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-auto max-w-full overflow-hidden rounded-md",
|
"h-auto max-w-full overflow-hidden rounded-md",
|
||||||
props.className,
|
props.className,
|
||||||
)}
|
)}
|
||||||
src={`data:${mediaType};base64,${base64}`}
|
src={`data:${mediaType};base64,${base64}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,96 +1,96 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
|
|
||||||
type LoaderIconProps = {
|
type LoaderIconProps = {
|
||||||
size?: number;
|
size?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
|
const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
|
||||||
<svg
|
<svg
|
||||||
height={size}
|
height={size}
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
style={{ color: "currentcolor" }}
|
style={{ color: "currentcolor" }}
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
width={size}
|
width={size}
|
||||||
>
|
>
|
||||||
<title>Loader</title>
|
<title>Loader</title>
|
||||||
<g clipPath="url(#clip0_2393_1490)">
|
<g clipPath="url(#clip0_2393_1490)">
|
||||||
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
|
<path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
|
||||||
<path
|
<path
|
||||||
d="M8 16V12"
|
d="M8 16V12"
|
||||||
opacity="0.5"
|
opacity="0.5"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M3.29773 1.52783L5.64887 4.7639"
|
d="M3.29773 1.52783L5.64887 4.7639"
|
||||||
opacity="0.9"
|
opacity="0.9"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M12.7023 1.52783L10.3511 4.7639"
|
d="M12.7023 1.52783L10.3511 4.7639"
|
||||||
opacity="0.1"
|
opacity="0.1"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M12.7023 14.472L10.3511 11.236"
|
d="M12.7023 14.472L10.3511 11.236"
|
||||||
opacity="0.4"
|
opacity="0.4"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M3.29773 14.472L5.64887 11.236"
|
d="M3.29773 14.472L5.64887 11.236"
|
||||||
opacity="0.6"
|
opacity="0.6"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M15.6085 5.52783L11.8043 6.7639"
|
d="M15.6085 5.52783L11.8043 6.7639"
|
||||||
opacity="0.2"
|
opacity="0.2"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M0.391602 10.472L4.19583 9.23598"
|
d="M0.391602 10.472L4.19583 9.23598"
|
||||||
opacity="0.7"
|
opacity="0.7"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M15.6085 10.4722L11.8043 9.2361"
|
d="M15.6085 10.4722L11.8043 9.2361"
|
||||||
opacity="0.3"
|
opacity="0.3"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M0.391602 5.52783L4.19583 6.7639"
|
d="M0.391602 5.52783L4.19583 6.7639"
|
||||||
opacity="0.8"
|
opacity="0.8"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_2393_1490">
|
<clipPath id="clip0_2393_1490">
|
||||||
<rect fill="white" height="16" width="16" />
|
<rect fill="white" height="16" width="16" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
|
export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
size?: number;
|
size?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
|
export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex animate-spin items-center justify-center",
|
"inline-flex animate-spin items-center justify-center",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<LoaderIcon size={size} />
|
<LoaderIcon size={size} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1,446 +1,446 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
|
import { ButtonGroup, ButtonGroupText } from "@/components/ui/button-group";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { FileUIPart, UIMessage } from "ai";
|
import type { FileUIPart, UIMessage } from "ai";
|
||||||
import {
|
import {
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
PaperclipIcon,
|
PaperclipIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
|
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
|
||||||
import { createContext, memo, useContext, useEffect, useState } from "react";
|
import { createContext, memo, useContext, useEffect, useState } from "react";
|
||||||
import { Streamdown } from "streamdown";
|
import { Streamdown } from "streamdown";
|
||||||
|
|
||||||
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
from: UIMessage["role"];
|
from: UIMessage["role"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Message = ({ className, from, ...props }: MessageProps) => (
|
export const Message = ({ className, from, ...props }: MessageProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex w-full flex-col gap-2",
|
"group flex w-full flex-col gap-2",
|
||||||
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
|
export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const MessageContent = ({
|
export const MessageContent = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageContentProps) => (
|
}: MessageContentProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-visible",
|
"is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-visible",
|
||||||
"group-[.is-user]:overflow-hidden",
|
"group-[.is-user]:overflow-hidden",
|
||||||
"group-[.is-user]:bg-secondary group-[.is-user]:text-foreground group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:px-4 group-[.is-user]:py-3",
|
"group-[.is-user]:bg-secondary group-[.is-user]:text-foreground group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:px-4 group-[.is-user]:py-3",
|
||||||
"group-[.is-assistant]:text-foreground",
|
"group-[.is-assistant]:text-foreground",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type MessageActionsProps = ComponentProps<"div">;
|
export type MessageActionsProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const MessageActions = ({
|
export const MessageActions = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageActionsProps) => (
|
}: MessageActionsProps) => (
|
||||||
<div className={cn("flex items-center gap-1", className)} {...props}>
|
<div className={cn("flex items-center gap-1", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type MessageActionProps = ComponentProps<typeof Button> & {
|
export type MessageActionProps = ComponentProps<typeof Button> & {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageAction = ({
|
export const MessageAction = ({
|
||||||
tooltip,
|
tooltip,
|
||||||
children,
|
children,
|
||||||
label,
|
label,
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
size = "icon-sm",
|
size = "icon-sm",
|
||||||
...props
|
...props
|
||||||
}: MessageActionProps) => {
|
}: MessageActionProps) => {
|
||||||
const button = (
|
const button = (
|
||||||
<Button size={size} type="button" variant={variant} {...props}>
|
<Button size={size} type="button" variant={variant} {...props}>
|
||||||
{children}
|
{children}
|
||||||
<span className="sr-only">{label || tooltip}</span>
|
<span className="sr-only">{label || tooltip}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{tooltip}</p>
|
<p>{tooltip}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MessageBranchContextType = {
|
type MessageBranchContextType = {
|
||||||
currentBranch: number;
|
currentBranch: number;
|
||||||
totalBranches: number;
|
totalBranches: number;
|
||||||
goToPrevious: () => void;
|
goToPrevious: () => void;
|
||||||
goToNext: () => void;
|
goToNext: () => void;
|
||||||
branches: ReactElement[];
|
branches: ReactElement[];
|
||||||
setBranches: (branches: ReactElement[]) => void;
|
setBranches: (branches: ReactElement[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageBranchContext = createContext<MessageBranchContextType | null>(
|
const MessageBranchContext = createContext<MessageBranchContextType | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const useMessageBranch = () => {
|
const useMessageBranch = () => {
|
||||||
const context = useContext(MessageBranchContext);
|
const context = useContext(MessageBranchContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"MessageBranch components must be used within MessageBranch",
|
"MessageBranch components must be used within MessageBranch",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
|
export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
defaultBranch?: number;
|
defaultBranch?: number;
|
||||||
onBranchChange?: (branchIndex: number) => void;
|
onBranchChange?: (branchIndex: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageBranch = ({
|
export const MessageBranch = ({
|
||||||
defaultBranch = 0,
|
defaultBranch = 0,
|
||||||
onBranchChange,
|
onBranchChange,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchProps) => {
|
}: MessageBranchProps) => {
|
||||||
const [currentBranch, setCurrentBranch] = useState(defaultBranch);
|
const [currentBranch, setCurrentBranch] = useState(defaultBranch);
|
||||||
const [branches, setBranches] = useState<ReactElement[]>([]);
|
const [branches, setBranches] = useState<ReactElement[]>([]);
|
||||||
|
|
||||||
const handleBranchChange = (newBranch: number) => {
|
const handleBranchChange = (newBranch: number) => {
|
||||||
setCurrentBranch(newBranch);
|
setCurrentBranch(newBranch);
|
||||||
onBranchChange?.(newBranch);
|
onBranchChange?.(newBranch);
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToPrevious = () => {
|
const goToPrevious = () => {
|
||||||
const newBranch =
|
const newBranch =
|
||||||
currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
||||||
handleBranchChange(newBranch);
|
handleBranchChange(newBranch);
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToNext = () => {
|
const goToNext = () => {
|
||||||
const newBranch =
|
const newBranch =
|
||||||
currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
||||||
handleBranchChange(newBranch);
|
handleBranchChange(newBranch);
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextValue: MessageBranchContextType = {
|
const contextValue: MessageBranchContextType = {
|
||||||
currentBranch,
|
currentBranch,
|
||||||
totalBranches: branches.length,
|
totalBranches: branches.length,
|
||||||
goToPrevious,
|
goToPrevious,
|
||||||
goToNext,
|
goToNext,
|
||||||
branches,
|
branches,
|
||||||
setBranches,
|
setBranches,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MessageBranchContext.Provider value={contextValue}>
|
<MessageBranchContext.Provider value={contextValue}>
|
||||||
<div
|
<div
|
||||||
className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
|
className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</MessageBranchContext.Provider>
|
</MessageBranchContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
|
export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const MessageBranchContent = ({
|
export const MessageBranchContent = ({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchContentProps) => {
|
}: MessageBranchContentProps) => {
|
||||||
const { currentBranch, setBranches, branches } = useMessageBranch();
|
const { currentBranch, setBranches, branches } = useMessageBranch();
|
||||||
const childrenArray = Array.isArray(children) ? children : [children];
|
const childrenArray = Array.isArray(children) ? children : [children];
|
||||||
|
|
||||||
// Use useEffect to update branches when they change
|
// Use useEffect to update branches when they change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (branches.length !== childrenArray.length) {
|
if (branches.length !== childrenArray.length) {
|
||||||
setBranches(childrenArray);
|
setBranches(childrenArray);
|
||||||
}
|
}
|
||||||
}, [childrenArray, branches, setBranches]);
|
}, [childrenArray, branches, setBranches]);
|
||||||
|
|
||||||
return childrenArray.map((branch, index) => (
|
return childrenArray.map((branch, index) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
||||||
index === currentBranch ? "block" : "hidden",
|
index === currentBranch ? "block" : "hidden",
|
||||||
)}
|
)}
|
||||||
key={branch.key}
|
key={branch.key}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{branch}
|
{branch}
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
|
export type MessageBranchSelectorProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
from: UIMessage["role"];
|
from: UIMessage["role"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessageBranchSelector = ({
|
export const MessageBranchSelector = ({
|
||||||
className,
|
className,
|
||||||
from,
|
from,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchSelectorProps) => {
|
}: MessageBranchSelectorProps) => {
|
||||||
const { totalBranches } = useMessageBranch();
|
const { totalBranches } = useMessageBranch();
|
||||||
|
|
||||||
// Don't render if there's only one branch
|
// Don't render if there's only one branch
|
||||||
if (totalBranches <= 1) {
|
if (totalBranches <= 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
className="[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md"
|
className="[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
|
export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
|
||||||
|
|
||||||
export const MessageBranchPrevious = ({
|
export const MessageBranchPrevious = ({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchPreviousProps) => {
|
}: MessageBranchPreviousProps) => {
|
||||||
const { goToPrevious, totalBranches } = useMessageBranch();
|
const { goToPrevious, totalBranches } = useMessageBranch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Previous branch"
|
aria-label="Previous branch"
|
||||||
disabled={totalBranches <= 1}
|
disabled={totalBranches <= 1}
|
||||||
onClick={goToPrevious}
|
onClick={goToPrevious}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <ChevronLeftIcon size={14} />}
|
{children ?? <ChevronLeftIcon size={14} />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchNextProps = ComponentProps<typeof Button>;
|
export type MessageBranchNextProps = ComponentProps<typeof Button>;
|
||||||
|
|
||||||
export const MessageBranchNext = ({
|
export const MessageBranchNext = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchNextProps) => {
|
}: MessageBranchNextProps) => {
|
||||||
const { goToNext, totalBranches } = useMessageBranch();
|
const { goToNext, totalBranches } = useMessageBranch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Next branch"
|
aria-label="Next branch"
|
||||||
disabled={totalBranches <= 1}
|
disabled={totalBranches <= 1}
|
||||||
onClick={goToNext}
|
onClick={goToNext}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <ChevronRightIcon size={14} />}
|
{children ?? <ChevronRightIcon size={14} />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
|
export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
|
||||||
|
|
||||||
export const MessageBranchPage = ({
|
export const MessageBranchPage = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageBranchPageProps) => {
|
}: MessageBranchPageProps) => {
|
||||||
const { currentBranch, totalBranches } = useMessageBranch();
|
const { currentBranch, totalBranches } = useMessageBranch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroupText
|
<ButtonGroupText
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground border-none bg-transparent shadow-none",
|
"text-muted-foreground border-none bg-transparent shadow-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{currentBranch + 1} of {totalBranches}
|
{currentBranch + 1} of {totalBranches}
|
||||||
</ButtonGroupText>
|
</ButtonGroupText>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageResponseProps = ComponentProps<typeof Streamdown>;
|
export type MessageResponseProps = ComponentProps<typeof Streamdown>;
|
||||||
|
|
||||||
export const MessageResponse = memo(
|
export const MessageResponse = memo(
|
||||||
({ className, ...props }: MessageResponseProps) => (
|
({ className, ...props }: MessageResponseProps) => (
|
||||||
<Streamdown
|
<Streamdown
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
(prevProps, nextProps) => prevProps.children === nextProps.children,
|
(prevProps, nextProps) => prevProps.children === nextProps.children,
|
||||||
);
|
);
|
||||||
|
|
||||||
MessageResponse.displayName = "MessageResponse";
|
MessageResponse.displayName = "MessageResponse";
|
||||||
|
|
||||||
export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
|
export type MessageAttachmentProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
data: FileUIPart;
|
data: FileUIPart;
|
||||||
className?: string;
|
className?: string;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MessageAttachment({
|
export function MessageAttachment({
|
||||||
data,
|
data,
|
||||||
className,
|
className,
|
||||||
onRemove,
|
onRemove,
|
||||||
...props
|
...props
|
||||||
}: MessageAttachmentProps) {
|
}: MessageAttachmentProps) {
|
||||||
const filename = data.filename || "";
|
const filename = data.filename || "";
|
||||||
const mediaType =
|
const mediaType =
|
||||||
data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
|
data.mediaType?.startsWith("image/") && data.url ? "image" : "file";
|
||||||
const isImage = mediaType === "image";
|
const isImage = mediaType === "image";
|
||||||
const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
|
const attachmentLabel = filename || (isImage ? "Image" : "Attachment");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative size-24 overflow-hidden rounded-lg",
|
"group relative size-24 overflow-hidden rounded-lg",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isImage ? (
|
{isImage ? (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
alt={filename || "attachment"}
|
alt={filename || "attachment"}
|
||||||
className="size-full object-cover"
|
className="size-full object-cover"
|
||||||
height={100}
|
height={100}
|
||||||
src={data.url}
|
src={data.url}
|
||||||
width={100}
|
width={100}
|
||||||
/>
|
/>
|
||||||
{onRemove && (
|
{onRemove && (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Remove attachment"
|
aria-label="Remove attachment"
|
||||||
className="bg-background/80 hover:bg-background absolute top-2 right-2 size-6 rounded-full p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
className="bg-background/80 hover:bg-background absolute top-2 right-2 size-6 rounded-full p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onRemove();
|
onRemove();
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
<span className="sr-only">Remove</span>
|
<span className="sr-only">Remove</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="bg-muted text-muted-foreground flex size-full shrink-0 items-center justify-center rounded-lg">
|
<div className="bg-muted text-muted-foreground flex size-full shrink-0 items-center justify-center rounded-lg">
|
||||||
<PaperclipIcon className="size-4" />
|
<PaperclipIcon className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{attachmentLabel}</p>
|
<p>{attachmentLabel}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{onRemove && (
|
{onRemove && (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Remove attachment"
|
aria-label="Remove attachment"
|
||||||
className="hover:bg-accent size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
className="hover:bg-accent size-6 shrink-0 rounded-full p-0 opacity-0 transition-opacity group-hover:opacity-100 [&>svg]:size-3"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onRemove();
|
onRemove();
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
<span className="sr-only">Remove</span>
|
<span className="sr-only">Remove</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageAttachmentsProps = ComponentProps<"div">;
|
export type MessageAttachmentsProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export function MessageAttachments({
|
export function MessageAttachments({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MessageAttachmentsProps) {
|
}: MessageAttachmentsProps) {
|
||||||
if (!children) {
|
if (!children) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto flex w-fit flex-wrap items-start gap-2",
|
"ml-auto flex w-fit flex-wrap items-start gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageToolbarProps = ComponentProps<"div">;
|
export type MessageToolbarProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const MessageToolbar = ({
|
export const MessageToolbar = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: MessageToolbarProps) => (
|
}: MessageToolbarProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-4 flex w-full items-center justify-between gap-4",
|
"mt-4 flex w-full items-center justify-between gap-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1,208 +1,208 @@
|
|||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { ComponentProps, ReactNode } from "react";
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
|
|
||||||
export type ModelSelectorProps = ComponentProps<typeof Dialog>;
|
export type ModelSelectorProps = ComponentProps<typeof Dialog>;
|
||||||
|
|
||||||
export const ModelSelector = (props: ModelSelectorProps) => (
|
export const ModelSelector = (props: ModelSelectorProps) => (
|
||||||
<Dialog {...props} />
|
<Dialog {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
|
export type ModelSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;
|
||||||
|
|
||||||
export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
|
export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => (
|
||||||
<DialogTrigger {...props} />
|
<DialogTrigger {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
|
export type ModelSelectorContentProps = ComponentProps<typeof DialogContent> & {
|
||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModelSelectorContent = ({
|
export const ModelSelectorContent = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
title = "Model Selector",
|
title = "Model Selector",
|
||||||
...props
|
...props
|
||||||
}: ModelSelectorContentProps) => (
|
}: ModelSelectorContentProps) => (
|
||||||
<DialogContent className={cn("p-0", className)} {...props}>
|
<DialogContent className={cn("p-0", className)} {...props}>
|
||||||
<DialogTitle className="sr-only">{title}</DialogTitle>
|
<DialogTitle className="sr-only">{title}</DialogTitle>
|
||||||
<Command className="**:data-[slot=command-input-wrapper]:h-auto">
|
<Command className="**:data-[slot=command-input-wrapper]:h-auto">
|
||||||
{children}
|
{children}
|
||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
|
export type ModelSelectorDialogProps = ComponentProps<typeof CommandDialog>;
|
||||||
|
|
||||||
export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
|
export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => (
|
||||||
<CommandDialog {...props} />
|
<CommandDialog {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
|
export type ModelSelectorInputProps = ComponentProps<typeof CommandInput>;
|
||||||
|
|
||||||
export const ModelSelectorInput = ({
|
export const ModelSelectorInput = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ModelSelectorInputProps) => (
|
}: ModelSelectorInputProps) => (
|
||||||
<CommandInput className={cn("h-auto py-3.5", className)} {...props} />
|
<CommandInput className={cn("h-auto py-3.5", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
|
export type ModelSelectorListProps = ComponentProps<typeof CommandList>;
|
||||||
|
|
||||||
export const ModelSelectorList = (props: ModelSelectorListProps) => (
|
export const ModelSelectorList = (props: ModelSelectorListProps) => (
|
||||||
<CommandList {...props} />
|
<CommandList {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
|
export type ModelSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;
|
||||||
|
|
||||||
export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
|
export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => (
|
||||||
<CommandEmpty {...props} />
|
<CommandEmpty {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
|
export type ModelSelectorGroupProps = ComponentProps<typeof CommandGroup>;
|
||||||
|
|
||||||
export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
|
export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => (
|
||||||
<CommandGroup {...props} />
|
<CommandGroup {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
|
export type ModelSelectorItemProps = ComponentProps<typeof CommandItem>;
|
||||||
|
|
||||||
export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
|
export const ModelSelectorItem = (props: ModelSelectorItemProps) => (
|
||||||
<CommandItem {...props} />
|
<CommandItem {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
|
export type ModelSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;
|
||||||
|
|
||||||
export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
|
export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => (
|
||||||
<CommandShortcut {...props} />
|
<CommandShortcut {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorSeparatorProps = ComponentProps<
|
export type ModelSelectorSeparatorProps = ComponentProps<
|
||||||
typeof CommandSeparator
|
typeof CommandSeparator
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
|
export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => (
|
||||||
<CommandSeparator {...props} />
|
<CommandSeparator {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorLogoProps = Omit<
|
export type ModelSelectorLogoProps = Omit<
|
||||||
ComponentProps<"img">,
|
ComponentProps<"img">,
|
||||||
"src" | "alt"
|
"src" | "alt"
|
||||||
> & {
|
> & {
|
||||||
provider:
|
provider:
|
||||||
| "moonshotai-cn"
|
| "moonshotai-cn"
|
||||||
| "lucidquery"
|
| "lucidquery"
|
||||||
| "moonshotai"
|
| "moonshotai"
|
||||||
| "zai-coding-plan"
|
| "zai-coding-plan"
|
||||||
| "alibaba"
|
| "alibaba"
|
||||||
| "xai"
|
| "xai"
|
||||||
| "vultr"
|
| "vultr"
|
||||||
| "nvidia"
|
| "nvidia"
|
||||||
| "upstage"
|
| "upstage"
|
||||||
| "groq"
|
| "groq"
|
||||||
| "github-copilot"
|
| "github-copilot"
|
||||||
| "mistral"
|
| "mistral"
|
||||||
| "vercel"
|
| "vercel"
|
||||||
| "nebius"
|
| "nebius"
|
||||||
| "deepseek"
|
| "deepseek"
|
||||||
| "alibaba-cn"
|
| "alibaba-cn"
|
||||||
| "google-vertex-anthropic"
|
| "google-vertex-anthropic"
|
||||||
| "venice"
|
| "venice"
|
||||||
| "chutes"
|
| "chutes"
|
||||||
| "cortecs"
|
| "cortecs"
|
||||||
| "github-models"
|
| "github-models"
|
||||||
| "togetherai"
|
| "togetherai"
|
||||||
| "azure"
|
| "azure"
|
||||||
| "baseten"
|
| "baseten"
|
||||||
| "huggingface"
|
| "huggingface"
|
||||||
| "opencode"
|
| "opencode"
|
||||||
| "fastrouter"
|
| "fastrouter"
|
||||||
| "google"
|
| "google"
|
||||||
| "google-vertex"
|
| "google-vertex"
|
||||||
| "cloudflare-workers-ai"
|
| "cloudflare-workers-ai"
|
||||||
| "inception"
|
| "inception"
|
||||||
| "wandb"
|
| "wandb"
|
||||||
| "openai"
|
| "openai"
|
||||||
| "zhipuai-coding-plan"
|
| "zhipuai-coding-plan"
|
||||||
| "perplexity"
|
| "perplexity"
|
||||||
| "openrouter"
|
| "openrouter"
|
||||||
| "zenmux"
|
| "zenmux"
|
||||||
| "v0"
|
| "v0"
|
||||||
| "iflowcn"
|
| "iflowcn"
|
||||||
| "synthetic"
|
| "synthetic"
|
||||||
| "deepinfra"
|
| "deepinfra"
|
||||||
| "zhipuai"
|
| "zhipuai"
|
||||||
| "submodel"
|
| "submodel"
|
||||||
| "zai"
|
| "zai"
|
||||||
| "inference"
|
| "inference"
|
||||||
| "requesty"
|
| "requesty"
|
||||||
| "morph"
|
| "morph"
|
||||||
| "lmstudio"
|
| "lmstudio"
|
||||||
| "anthropic"
|
| "anthropic"
|
||||||
| "aihubmix"
|
| "aihubmix"
|
||||||
| "fireworks-ai"
|
| "fireworks-ai"
|
||||||
| "modelscope"
|
| "modelscope"
|
||||||
| "llama"
|
| "llama"
|
||||||
| "scaleway"
|
| "scaleway"
|
||||||
| "amazon-bedrock"
|
| "amazon-bedrock"
|
||||||
| "cerebras"
|
| "cerebras"
|
||||||
| (string & {});
|
| (string & {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModelSelectorLogo = ({
|
export const ModelSelectorLogo = ({
|
||||||
provider,
|
provider,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ModelSelectorLogoProps) => (
|
}: ModelSelectorLogoProps) => (
|
||||||
<img
|
<img
|
||||||
{...props}
|
{...props}
|
||||||
alt={`${provider} logo`}
|
alt={`${provider} logo`}
|
||||||
className={cn("size-3 dark:invert", className)}
|
className={cn("size-3 dark:invert", className)}
|
||||||
height={12}
|
height={12}
|
||||||
src={`https://models.dev/logos/${provider}.svg`}
|
src={`https://models.dev/logos/${provider}.svg`}
|
||||||
width={12}
|
width={12}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
|
export type ModelSelectorLogoGroupProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const ModelSelectorLogoGroup = ({
|
export const ModelSelectorLogoGroup = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ModelSelectorLogoGroupProps) => (
|
}: ModelSelectorLogoGroupProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"[&>img]:bg-background dark:[&>img]:bg-foreground flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:p-px [&>img]:ring-1",
|
"[&>img]:bg-background dark:[&>img]:bg-foreground flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:p-px [&>img]:ring-1",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ModelSelectorNameProps = ComponentProps<"span">;
|
export type ModelSelectorNameProps = ComponentProps<"span">;
|
||||||
|
|
||||||
export const ModelSelectorName = ({
|
export const ModelSelectorName = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ModelSelectorNameProps) => (
|
}: ModelSelectorNameProps) => (
|
||||||
<span
|
<span
|
||||||
className={cn("flex-1 truncate text-left text-xs", className)}
|
className={cn("flex-1 truncate text-left text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,71 +1,71 @@
|
|||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardAction,
|
CardAction,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Handle, Position } from "@xyflow/react";
|
import { Handle, Position } from "@xyflow/react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type NodeProps = ComponentProps<typeof Card> & {
|
export type NodeProps = ComponentProps<typeof Card> & {
|
||||||
handles: {
|
handles: {
|
||||||
target: boolean;
|
target: boolean;
|
||||||
source: boolean;
|
source: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Node = ({ handles, className, ...props }: NodeProps) => (
|
export const Node = ({ handles, className, ...props }: NodeProps) => (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
"node-container relative size-full h-auto w-sm gap-0 rounded-md p-0",
|
"node-container relative size-full h-auto w-sm gap-0 rounded-md p-0",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{handles.target && <Handle position={Position.Left} type="target" />}
|
{handles.target && <Handle position={Position.Left} type="target" />}
|
||||||
{handles.source && <Handle position={Position.Right} type="source" />}
|
{handles.source && <Handle position={Position.Right} type="source" />}
|
||||||
{props.children}
|
{props.children}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type NodeHeaderProps = ComponentProps<typeof CardHeader>;
|
export type NodeHeaderProps = ComponentProps<typeof CardHeader>;
|
||||||
|
|
||||||
export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => (
|
export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => (
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className={cn("bg-secondary gap-0.5 rounded-t-md border-b p-3!", className)}
|
className={cn("bg-secondary gap-0.5 rounded-t-md border-b p-3!", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type NodeTitleProps = ComponentProps<typeof CardTitle>;
|
export type NodeTitleProps = ComponentProps<typeof CardTitle>;
|
||||||
|
|
||||||
export const NodeTitle = (props: NodeTitleProps) => <CardTitle {...props} />;
|
export const NodeTitle = (props: NodeTitleProps) => <CardTitle {...props} />;
|
||||||
|
|
||||||
export type NodeDescriptionProps = ComponentProps<typeof CardDescription>;
|
export type NodeDescriptionProps = ComponentProps<typeof CardDescription>;
|
||||||
|
|
||||||
export const NodeDescription = (props: NodeDescriptionProps) => (
|
export const NodeDescription = (props: NodeDescriptionProps) => (
|
||||||
<CardDescription {...props} />
|
<CardDescription {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type NodeActionProps = ComponentProps<typeof CardAction>;
|
export type NodeActionProps = ComponentProps<typeof CardAction>;
|
||||||
|
|
||||||
export const NodeAction = (props: NodeActionProps) => <CardAction {...props} />;
|
export const NodeAction = (props: NodeActionProps) => <CardAction {...props} />;
|
||||||
|
|
||||||
export type NodeContentProps = ComponentProps<typeof CardContent>;
|
export type NodeContentProps = ComponentProps<typeof CardContent>;
|
||||||
|
|
||||||
export const NodeContent = ({ className, ...props }: NodeContentProps) => (
|
export const NodeContent = ({ className, ...props }: NodeContentProps) => (
|
||||||
<CardContent className={cn("p-3", className)} {...props} />
|
<CardContent className={cn("p-3", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type NodeFooterProps = ComponentProps<typeof CardFooter>;
|
export type NodeFooterProps = ComponentProps<typeof CardFooter>;
|
||||||
|
|
||||||
export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
|
export const NodeFooter = ({ className, ...props }: NodeFooterProps) => (
|
||||||
<CardFooter
|
<CardFooter
|
||||||
className={cn("bg-secondary rounded-b-md border-t p-3!", className)}
|
className={cn("bg-secondary rounded-b-md border-t p-3!", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,365 +1,365 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { type ComponentProps, createContext, useContext } from "react";
|
import { type ComponentProps, createContext, useContext } from "react";
|
||||||
|
|
||||||
const providers = {
|
const providers = {
|
||||||
github: {
|
github: {
|
||||||
title: "Open in GitHub",
|
title: "Open in GitHub",
|
||||||
createUrl: (url: string) => url,
|
createUrl: (url: string) => url,
|
||||||
icon: (
|
icon: (
|
||||||
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
<svg fill="currentColor" role="img" viewBox="0 0 24 24">
|
||||||
<title>GitHub</title>
|
<title>GitHub</title>
|
||||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
scira: {
|
scira: {
|
||||||
title: "Open in Scira",
|
title: "Open in Scira",
|
||||||
createUrl: (q: string) =>
|
createUrl: (q: string) =>
|
||||||
`https://scira.ai/?${new URLSearchParams({
|
`https://scira.ai/?${new URLSearchParams({
|
||||||
q,
|
q,
|
||||||
})}`,
|
})}`,
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
fill="none"
|
fill="none"
|
||||||
height="934"
|
height="934"
|
||||||
viewBox="0 0 910 934"
|
viewBox="0 0 910 934"
|
||||||
width="910"
|
width="910"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<title>Scira AI</title>
|
<title>Scira AI</title>
|
||||||
<path
|
<path
|
||||||
d="M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z"
|
d="M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="8"
|
strokeWidth="8"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M516.774 304.217C510.299 275.491 498.208 252.087 480.335 234.214C462.462 216.341 439.058 204.251 410.333 197.775C439.059 191.3 462.462 179.209 480.335 161.336C498.208 143.463 510.299 120.06 516.774 91.334C523.25 120.059 535.34 143.463 553.213 161.336C571.086 179.209 594.49 191.3 623.216 197.775C594.49 204.251 571.086 216.341 553.213 234.214C535.34 252.087 523.25 275.491 516.774 304.217Z"
|
d="M516.774 304.217C510.299 275.491 498.208 252.087 480.335 234.214C462.462 216.341 439.058 204.251 410.333 197.775C439.059 191.3 462.462 179.209 480.335 161.336C498.208 143.463 510.299 120.06 516.774 91.334C523.25 120.059 535.34 143.463 553.213 161.336C571.086 179.209 594.49 191.3 623.216 197.775C594.49 204.251 571.086 216.341 553.213 234.214C535.34 252.087 523.25 275.491 516.774 304.217Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="8"
|
strokeWidth="8"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M857.5 508.116C763.259 497.644 710.903 445.288 700.432 351.047C689.961 445.288 637.605 497.644 543.364 508.116C637.605 518.587 689.961 570.943 700.432 665.184C710.903 570.943 763.259 518.587 857.5 508.116Z"
|
d="M857.5 508.116C763.259 497.644 710.903 445.288 700.432 351.047C689.961 445.288 637.605 497.644 543.364 508.116C637.605 518.587 689.961 570.943 700.432 665.184C710.903 570.943 763.259 518.587 857.5 508.116Z"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="20"
|
strokeWidth="20"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M700.432 615.957C691.848 589.05 678.575 566.357 660.383 548.165C642.191 529.973 619.499 516.7 592.593 508.116C619.499 499.533 642.191 486.258 660.383 468.066C678.575 449.874 691.848 427.181 700.432 400.274C709.015 427.181 722.289 449.874 740.481 468.066C758.673 486.258 781.365 499.533 808.271 508.116C781.365 516.7 758.673 529.973 740.481 548.165C722.289 566.357 709.015 589.05 700.432 615.957Z"
|
d="M700.432 615.957C691.848 589.05 678.575 566.357 660.383 548.165C642.191 529.973 619.499 516.7 592.593 508.116C619.499 499.533 642.191 486.258 660.383 468.066C678.575 449.874 691.848 427.181 700.432 400.274C709.015 427.181 722.289 449.874 740.481 468.066C758.673 486.258 781.365 499.533 808.271 508.116C781.365 516.7 758.673 529.973 740.481 548.165C722.289 566.357 709.015 589.05 700.432 615.957Z"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="20"
|
strokeWidth="20"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M889.949 121.237C831.049 114.692 798.326 81.9698 791.782 23.0692C785.237 81.9698 752.515 114.692 693.614 121.237C752.515 127.781 785.237 160.504 791.782 219.404C798.326 160.504 831.049 127.781 889.949 121.237Z"
|
d="M889.949 121.237C831.049 114.692 798.326 81.9698 791.782 23.0692C785.237 81.9698 752.515 114.692 693.614 121.237C752.515 127.781 785.237 160.504 791.782 219.404C798.326 160.504 831.049 127.781 889.949 121.237Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="8"
|
strokeWidth="8"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M791.782 196.795C786.697 176.937 777.869 160.567 765.16 147.858C752.452 135.15 736.082 126.322 716.226 121.237C736.082 116.152 752.452 107.324 765.16 94.6152C777.869 81.9065 786.697 65.5368 791.782 45.6797C796.867 65.5367 805.695 81.9066 818.403 94.6152C831.112 107.324 847.481 116.152 867.338 121.237C847.481 126.322 831.112 135.15 818.403 147.858C805.694 160.567 796.867 176.937 791.782 196.795Z"
|
d="M791.782 196.795C786.697 176.937 777.869 160.567 765.16 147.858C752.452 135.15 736.082 126.322 716.226 121.237C736.082 116.152 752.452 107.324 765.16 94.6152C777.869 81.9065 786.697 65.5368 791.782 45.6797C796.867 65.5367 805.695 81.9066 818.403 94.6152C831.112 107.324 847.481 116.152 867.338 121.237C847.481 126.322 831.112 135.15 818.403 147.858C805.694 160.567 796.867 176.937 791.782 196.795Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="8"
|
strokeWidth="8"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M760.632 764.337C720.719 814.616 669.835 855.1 611.872 882.692C553.91 910.285 490.404 924.255 426.213 923.533C362.022 922.812 298.846 907.419 241.518 878.531C184.19 849.643 134.228 808.026 95.4548 756.863C56.6815 705.7 30.1238 646.346 17.8129 583.343C5.50207 520.339 7.76433 455.354 24.4266 393.359C41.089 331.364 71.7099 274.001 113.947 225.658C156.184 177.315 208.919 139.273 268.117 114.442"
|
d="M760.632 764.337C720.719 814.616 669.835 855.1 611.872 882.692C553.91 910.285 490.404 924.255 426.213 923.533C362.022 922.812 298.846 907.419 241.518 878.531C184.19 849.643 134.228 808.026 95.4548 756.863C56.6815 705.7 30.1238 646.346 17.8129 583.343C5.50207 520.339 7.76433 455.354 24.4266 393.359C41.089 331.364 71.7099 274.001 113.947 225.658C156.184 177.315 208.919 139.273 268.117 114.442"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="30"
|
strokeWidth="30"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
chatgpt: {
|
chatgpt: {
|
||||||
title: "Open in ChatGPT",
|
title: "Open in ChatGPT",
|
||||||
createUrl: (prompt: string) =>
|
createUrl: (prompt: string) =>
|
||||||
`https://chatgpt.com/?${new URLSearchParams({
|
`https://chatgpt.com/?${new URLSearchParams({
|
||||||
hints: "search",
|
hints: "search",
|
||||||
prompt,
|
prompt,
|
||||||
})}`,
|
})}`,
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
role="img"
|
role="img"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<title>OpenAI</title>
|
<title>OpenAI</title>
|
||||||
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
claude: {
|
claude: {
|
||||||
title: "Open in Claude",
|
title: "Open in Claude",
|
||||||
createUrl: (q: string) =>
|
createUrl: (q: string) =>
|
||||||
`https://claude.ai/new?${new URLSearchParams({
|
`https://claude.ai/new?${new URLSearchParams({
|
||||||
q,
|
q,
|
||||||
})}`,
|
})}`,
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
role="img"
|
role="img"
|
||||||
viewBox="0 0 12 12"
|
viewBox="0 0 12 12"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<title>Claude</title>
|
<title>Claude</title>
|
||||||
<path
|
<path
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M2.3545 7.9775L4.7145 6.654L4.7545 6.539L4.7145 6.475H4.6L4.205 6.451L2.856 6.4145L1.6865 6.366L0.5535 6.305L0.268 6.2445L0 5.892L0.0275 5.716L0.2675 5.5555L0.6105 5.5855L1.3705 5.637L2.5095 5.716L3.3355 5.7645L4.56 5.892H4.7545L4.782 5.8135L4.715 5.7645L4.6635 5.716L3.4845 4.918L2.2085 4.074L1.5405 3.588L1.1785 3.3425L0.9965 3.1115L0.9175 2.6075L1.2455 2.2465L1.686 2.2765L1.7985 2.307L2.245 2.65L3.199 3.388L4.4445 4.3045L4.627 4.4565L4.6995 4.405L4.709 4.3685L4.627 4.2315L3.9495 3.0085L3.2265 1.7635L2.9045 1.2475L2.8195 0.938C2.78711 0.819128 2.76965 0.696687 2.7675 0.5735L3.1415 0.067L3.348 0L3.846 0.067L4.056 0.249L4.366 0.956L4.867 2.0705L5.6445 3.5855L5.8725 4.0345L5.994 4.4505L6.0395 4.578H6.1185V4.505L6.1825 3.652L6.301 2.6045L6.416 1.257L6.456 0.877L6.644 0.422L7.0175 0.176L7.3095 0.316L7.5495 0.6585L7.516 0.8805L7.373 1.806L7.0935 3.2575L6.9115 4.2285H7.0175L7.139 4.1075L7.6315 3.4545L8.4575 2.4225L8.8225 2.0125L9.2475 1.5605L9.521 1.345H10.0375L10.4175 1.9095L10.2475 2.4925L9.7155 3.166L9.275 3.737L8.643 4.587L8.248 5.267L8.2845 5.322L8.3785 5.312L9.8065 5.009L10.578 4.869L11.4985 4.7115L11.915 4.9055L11.9605 5.103L11.7965 5.5065L10.812 5.7495L9.6575 5.9805L7.938 6.387L7.917 6.402L7.9415 6.4325L8.716 6.5055L9.047 6.5235H9.858L11.368 6.636L11.763 6.897L12 7.216L11.9605 7.4585L11.353 7.7685L10.533 7.574L8.6185 7.119L7.9625 6.9545H7.8715V7.0095L8.418 7.5435L9.421 8.4485L10.6755 9.6135L10.739 9.9025L10.578 10.13L10.408 10.1055L9.3055 9.277L8.88 8.9035L7.917 8.0935H7.853V8.1785L8.075 8.503L9.2475 10.2635L9.3085 10.8035L9.2235 10.98L8.9195 11.0865L8.5855 11.0255L7.8985 10.063L7.191 8.9795L6.6195 8.008L6.5495 8.048L6.2125 11.675L6.0545 11.86L5.69 12L5.3865 11.7695L5.2255 11.396L5.3865 10.658L5.581 9.696L5.7385 8.931L5.8815 7.981L5.9665 7.665L5.9605 7.644L5.8905 7.653L5.1735 8.6365L4.0835 10.109L3.2205 11.0315L3.0135 11.1135L2.655 10.9285L2.6885 10.5975L2.889 10.303L4.083 8.785L4.803 7.844L5.268 7.301L5.265 7.222H5.2375L2.066 9.28L1.501 9.353L1.2575 9.125L1.288 8.752L1.4035 8.6305L2.3575 7.9745L2.3545 7.9775Z"
|
d="M2.3545 7.9775L4.7145 6.654L4.7545 6.539L4.7145 6.475H4.6L4.205 6.451L2.856 6.4145L1.6865 6.366L0.5535 6.305L0.268 6.2445L0 5.892L0.0275 5.716L0.2675 5.5555L0.6105 5.5855L1.3705 5.637L2.5095 5.716L3.3355 5.7645L4.56 5.892H4.7545L4.782 5.8135L4.715 5.7645L4.6635 5.716L3.4845 4.918L2.2085 4.074L1.5405 3.588L1.1785 3.3425L0.9965 3.1115L0.9175 2.6075L1.2455 2.2465L1.686 2.2765L1.7985 2.307L2.245 2.65L3.199 3.388L4.4445 4.3045L4.627 4.4565L4.6995 4.405L4.709 4.3685L4.627 4.2315L3.9495 3.0085L3.2265 1.7635L2.9045 1.2475L2.8195 0.938C2.78711 0.819128 2.76965 0.696687 2.7675 0.5735L3.1415 0.067L3.348 0L3.846 0.067L4.056 0.249L4.366 0.956L4.867 2.0705L5.6445 3.5855L5.8725 4.0345L5.994 4.4505L6.0395 4.578H6.1185V4.505L6.1825 3.652L6.301 2.6045L6.416 1.257L6.456 0.877L6.644 0.422L7.0175 0.176L7.3095 0.316L7.5495 0.6585L7.516 0.8805L7.373 1.806L7.0935 3.2575L6.9115 4.2285H7.0175L7.139 4.1075L7.6315 3.4545L8.4575 2.4225L8.8225 2.0125L9.2475 1.5605L9.521 1.345H10.0375L10.4175 1.9095L10.2475 2.4925L9.7155 3.166L9.275 3.737L8.643 4.587L8.248 5.267L8.2845 5.322L8.3785 5.312L9.8065 5.009L10.578 4.869L11.4985 4.7115L11.915 4.9055L11.9605 5.103L11.7965 5.5065L10.812 5.7495L9.6575 5.9805L7.938 6.387L7.917 6.402L7.9415 6.4325L8.716 6.5055L9.047 6.5235H9.858L11.368 6.636L11.763 6.897L12 7.216L11.9605 7.4585L11.353 7.7685L10.533 7.574L8.6185 7.119L7.9625 6.9545H7.8715V7.0095L8.418 7.5435L9.421 8.4485L10.6755 9.6135L10.739 9.9025L10.578 10.13L10.408 10.1055L9.3055 9.277L8.88 8.9035L7.917 8.0935H7.853V8.1785L8.075 8.503L9.2475 10.2635L9.3085 10.8035L9.2235 10.98L8.9195 11.0865L8.5855 11.0255L7.8985 10.063L7.191 8.9795L6.6195 8.008L6.5495 8.048L6.2125 11.675L6.0545 11.86L5.69 12L5.3865 11.7695L5.2255 11.396L5.3865 10.658L5.581 9.696L5.7385 8.931L5.8815 7.981L5.9665 7.665L5.9605 7.644L5.8905 7.653L5.1735 8.6365L4.0835 10.109L3.2205 11.0315L3.0135 11.1135L2.655 10.9285L2.6885 10.5975L2.889 10.303L4.083 8.785L4.803 7.844L5.268 7.301L5.265 7.222H5.2375L2.066 9.28L1.501 9.353L1.2575 9.125L1.288 8.752L1.4035 8.6305L2.3575 7.9745L2.3545 7.9775Z"
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
t3: {
|
t3: {
|
||||||
title: "Open in T3 Chat",
|
title: "Open in T3 Chat",
|
||||||
createUrl: (q: string) =>
|
createUrl: (q: string) =>
|
||||||
`https://t3.chat/new?${new URLSearchParams({
|
`https://t3.chat/new?${new URLSearchParams({
|
||||||
q,
|
q,
|
||||||
})}`,
|
})}`,
|
||||||
icon: <MessageCircleIcon />,
|
icon: <MessageCircleIcon />,
|
||||||
},
|
},
|
||||||
v0: {
|
v0: {
|
||||||
title: "Open in v0",
|
title: "Open in v0",
|
||||||
createUrl: (q: string) =>
|
createUrl: (q: string) =>
|
||||||
`https://v0.app?${new URLSearchParams({
|
`https://v0.app?${new URLSearchParams({
|
||||||
q,
|
q,
|
||||||
})}`,
|
})}`,
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
viewBox="0 0 147 70"
|
viewBox="0 0 147 70"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<title>v0</title>
|
<title>v0</title>
|
||||||
<path d="M56 50.2031V14H70V60.1562C70 65.5928 65.5928 70 60.1562 70C57.5605 70 54.9982 68.9992 53.1562 67.1573L0 14H19.7969L56 50.2031Z" />
|
<path d="M56 50.2031V14H70V60.1562C70 65.5928 65.5928 70 60.1562 70C57.5605 70 54.9982 68.9992 53.1562 67.1573L0 14H19.7969L56 50.2031Z" />
|
||||||
<path d="M147 56H133V23.9531L100.953 56H133V70H96.6875C85.8144 70 77 61.1856 77 50.3125V14H91V46.1562L123.156 14H91V0H127.312C138.186 0 147 8.81439 147 19.6875V56Z" />
|
<path d="M147 56H133V23.9531L100.953 56H133V70H96.6875C85.8144 70 77 61.1856 77 50.3125V14H91V46.1562L123.156 14H91V0H127.312C138.186 0 147 8.81439 147 19.6875V56Z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
cursor: {
|
cursor: {
|
||||||
title: "Open in Cursor",
|
title: "Open in Cursor",
|
||||||
createUrl: (text: string) => {
|
createUrl: (text: string) => {
|
||||||
const url = new URL("https://cursor.com/link/prompt");
|
const url = new URL("https://cursor.com/link/prompt");
|
||||||
url.searchParams.set("text", text);
|
url.searchParams.set("text", text);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
},
|
},
|
||||||
icon: (
|
icon: (
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
viewBox="0 0 466.73 532.09"
|
viewBox="0 0 466.73 532.09"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<title>Cursor</title>
|
<title>Cursor</title>
|
||||||
<path
|
<path
|
||||||
d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"
|
d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const OpenInContext = createContext<{ query: string } | undefined>(undefined);
|
const OpenInContext = createContext<{ query: string } | undefined>(undefined);
|
||||||
|
|
||||||
const useOpenInContext = () => {
|
const useOpenInContext = () => {
|
||||||
const context = useContext(OpenInContext);
|
const context = useContext(OpenInContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("OpenIn components must be used within an OpenIn provider");
|
throw new Error("OpenIn components must be used within an OpenIn provider");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInProps = ComponentProps<typeof DropdownMenu> & {
|
export type OpenInProps = ComponentProps<typeof DropdownMenu> & {
|
||||||
query: string;
|
query: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OpenIn = ({ query, ...props }: OpenInProps) => (
|
export const OpenIn = ({ query, ...props }: OpenInProps) => (
|
||||||
<OpenInContext.Provider value={{ query }}>
|
<OpenInContext.Provider value={{ query }}>
|
||||||
<DropdownMenu {...props} />
|
<DropdownMenu {...props} />
|
||||||
</OpenInContext.Provider>
|
</OpenInContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>;
|
export type OpenInContentProps = ComponentProps<typeof DropdownMenuContent>;
|
||||||
|
|
||||||
export const OpenInContent = ({ className, ...props }: OpenInContentProps) => (
|
export const OpenInContent = ({ className, ...props }: OpenInContentProps) => (
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="start"
|
align="start"
|
||||||
className={cn("w-[240px]", className)}
|
className={cn("w-[240px]", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInItemProps = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInItemProps = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInItem = (props: OpenInItemProps) => (
|
export const OpenInItem = (props: OpenInItemProps) => (
|
||||||
<DropdownMenuItem {...props} />
|
<DropdownMenuItem {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInLabelProps = ComponentProps<typeof DropdownMenuLabel>;
|
export type OpenInLabelProps = ComponentProps<typeof DropdownMenuLabel>;
|
||||||
|
|
||||||
export const OpenInLabel = (props: OpenInLabelProps) => (
|
export const OpenInLabel = (props: OpenInLabelProps) => (
|
||||||
<DropdownMenuLabel {...props} />
|
<DropdownMenuLabel {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInSeparatorProps = ComponentProps<typeof DropdownMenuSeparator>;
|
export type OpenInSeparatorProps = ComponentProps<typeof DropdownMenuSeparator>;
|
||||||
|
|
||||||
export const OpenInSeparator = (props: OpenInSeparatorProps) => (
|
export const OpenInSeparator = (props: OpenInSeparatorProps) => (
|
||||||
<DropdownMenuSeparator {...props} />
|
<DropdownMenuSeparator {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;
|
export type OpenInTriggerProps = ComponentProps<typeof DropdownMenuTrigger>;
|
||||||
|
|
||||||
export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => (
|
export const OpenInTrigger = ({ children, ...props }: OpenInTriggerProps) => (
|
||||||
<DropdownMenuTrigger {...props} asChild>
|
<DropdownMenuTrigger {...props} asChild>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<Button type="button" variant="outline">
|
<Button type="button" variant="outline">
|
||||||
Open in chat
|
Open in chat
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OpenInChatGPTProps = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInChatGPTProps = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
|
export const OpenInChatGPT = (props: OpenInChatGPTProps) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.chatgpt.createUrl(query)}
|
href={providers.chatgpt.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.chatgpt.icon}</span>
|
<span className="shrink-0">{providers.chatgpt.icon}</span>
|
||||||
<span className="flex-1">{providers.chatgpt.title}</span>
|
<span className="flex-1">{providers.chatgpt.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInClaudeProps = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInClaudeProps = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInClaude = (props: OpenInClaudeProps) => {
|
export const OpenInClaude = (props: OpenInClaudeProps) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.claude.createUrl(query)}
|
href={providers.claude.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.claude.icon}</span>
|
<span className="shrink-0">{providers.claude.icon}</span>
|
||||||
<span className="flex-1">{providers.claude.title}</span>
|
<span className="flex-1">{providers.claude.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInT3Props = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInT3Props = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInT3 = (props: OpenInT3Props) => {
|
export const OpenInT3 = (props: OpenInT3Props) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.t3.createUrl(query)}
|
href={providers.t3.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.t3.icon}</span>
|
<span className="shrink-0">{providers.t3.icon}</span>
|
||||||
<span className="flex-1">{providers.t3.title}</span>
|
<span className="flex-1">{providers.t3.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInSciraProps = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInSciraProps = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInScira = (props: OpenInSciraProps) => {
|
export const OpenInScira = (props: OpenInSciraProps) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.scira.createUrl(query)}
|
href={providers.scira.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.scira.icon}</span>
|
<span className="shrink-0">{providers.scira.icon}</span>
|
||||||
<span className="flex-1">{providers.scira.title}</span>
|
<span className="flex-1">{providers.scira.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInv0Props = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInv0Props = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInv0 = (props: OpenInv0Props) => {
|
export const OpenInv0 = (props: OpenInv0Props) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.v0.createUrl(query)}
|
href={providers.v0.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.v0.icon}</span>
|
<span className="shrink-0">{providers.v0.icon}</span>
|
||||||
<span className="flex-1">{providers.v0.title}</span>
|
<span className="flex-1">{providers.v0.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OpenInCursorProps = ComponentProps<typeof DropdownMenuItem>;
|
export type OpenInCursorProps = ComponentProps<typeof DropdownMenuItem>;
|
||||||
|
|
||||||
export const OpenInCursor = (props: OpenInCursorProps) => {
|
export const OpenInCursor = (props: OpenInCursorProps) => {
|
||||||
const { query } = useOpenInContext();
|
const { query } = useOpenInContext();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem asChild {...props}>
|
<DropdownMenuItem asChild {...props}>
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={providers.cursor.createUrl(query)}
|
href={providers.cursor.createUrl(query)}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span className="shrink-0">{providers.cursor.icon}</span>
|
<span className="shrink-0">{providers.cursor.icon}</span>
|
||||||
<span className="flex-1">{providers.cursor.title}</span>
|
<span className="flex-1">{providers.cursor.title}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Panel as PanelPrimitive } from "@xyflow/react";
|
import { Panel as PanelPrimitive } from "@xyflow/react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
type PanelProps = ComponentProps<typeof PanelPrimitive>;
|
type PanelProps = ComponentProps<typeof PanelPrimitive>;
|
||||||
|
|
||||||
export const Panel = ({ className, ...props }: PanelProps) => (
|
export const Panel = ({ className, ...props }: PanelProps) => (
|
||||||
<PanelPrimitive
|
<PanelPrimitive
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card m-4 overflow-hidden rounded-md border p-1",
|
"bg-card m-4 overflow-hidden rounded-md border p-1",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,142 +1,142 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardAction,
|
CardAction,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ChevronsUpDownIcon } from "lucide-react";
|
import { ChevronsUpDownIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
import { Shimmer } from "./shimmer";
|
import { Shimmer } from "./shimmer";
|
||||||
|
|
||||||
type PlanContextValue = {
|
type PlanContextValue = {
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PlanContext = createContext<PlanContextValue | null>(null);
|
const PlanContext = createContext<PlanContextValue | null>(null);
|
||||||
|
|
||||||
const usePlan = () => {
|
const usePlan = () => {
|
||||||
const context = useContext(PlanContext);
|
const context = useContext(PlanContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Plan components must be used within Plan");
|
throw new Error("Plan components must be used within Plan");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlanProps = ComponentProps<typeof Collapsible> & {
|
export type PlanProps = ComponentProps<typeof Collapsible> & {
|
||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Plan = ({
|
export const Plan = ({
|
||||||
className,
|
className,
|
||||||
isStreaming = false,
|
isStreaming = false,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: PlanProps) => (
|
}: PlanProps) => (
|
||||||
<PlanContext.Provider value={{ isStreaming }}>
|
<PlanContext.Provider value={{ isStreaming }}>
|
||||||
<Collapsible asChild data-slot="plan" {...props}>
|
<Collapsible asChild data-slot="plan" {...props}>
|
||||||
<Card className={cn("shadow-none", className)}>{children}</Card>
|
<Card className={cn("shadow-none", className)}>{children}</Card>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</PlanContext.Provider>
|
</PlanContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PlanHeaderProps = ComponentProps<typeof CardHeader>;
|
export type PlanHeaderProps = ComponentProps<typeof CardHeader>;
|
||||||
|
|
||||||
export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
|
export const PlanHeader = ({ className, ...props }: PlanHeaderProps) => (
|
||||||
<CardHeader
|
<CardHeader
|
||||||
className={cn("flex items-start justify-between", className)}
|
className={cn("flex items-start justify-between", className)}
|
||||||
data-slot="plan-header"
|
data-slot="plan-header"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PlanTitleProps = Omit<
|
export type PlanTitleProps = Omit<
|
||||||
ComponentProps<typeof CardTitle>,
|
ComponentProps<typeof CardTitle>,
|
||||||
"children"
|
"children"
|
||||||
> & {
|
> & {
|
||||||
children: string;
|
children: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
|
export const PlanTitle = ({ children, ...props }: PlanTitleProps) => {
|
||||||
const { isStreaming } = usePlan();
|
const { isStreaming } = usePlan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardTitle data-slot="plan-title" {...props}>
|
<CardTitle data-slot="plan-title" {...props}>
|
||||||
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlanDescriptionProps = Omit<
|
export type PlanDescriptionProps = Omit<
|
||||||
ComponentProps<typeof CardDescription>,
|
ComponentProps<typeof CardDescription>,
|
||||||
"children"
|
"children"
|
||||||
> & {
|
> & {
|
||||||
children: string;
|
children: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlanDescription = ({
|
export const PlanDescription = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: PlanDescriptionProps) => {
|
}: PlanDescriptionProps) => {
|
||||||
const { isStreaming } = usePlan();
|
const { isStreaming } = usePlan();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardDescription
|
<CardDescription
|
||||||
className={cn("text-balance", className)}
|
className={cn("text-balance", className)}
|
||||||
data-slot="plan-description"
|
data-slot="plan-description"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
{isStreaming ? <Shimmer>{children}</Shimmer> : children}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PlanActionProps = ComponentProps<typeof CardAction>;
|
export type PlanActionProps = ComponentProps<typeof CardAction>;
|
||||||
|
|
||||||
export const PlanAction = (props: PlanActionProps) => (
|
export const PlanAction = (props: PlanActionProps) => (
|
||||||
<CardAction data-slot="plan-action" {...props} />
|
<CardAction data-slot="plan-action" {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PlanContentProps = ComponentProps<typeof CardContent>;
|
export type PlanContentProps = ComponentProps<typeof CardContent>;
|
||||||
|
|
||||||
export const PlanContent = (props: PlanContentProps) => (
|
export const PlanContent = (props: PlanContentProps) => (
|
||||||
<CollapsibleContent asChild>
|
<CollapsibleContent asChild>
|
||||||
<CardContent data-slot="plan-content" {...props} />
|
<CardContent data-slot="plan-content" {...props} />
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PlanFooterProps = ComponentProps<"div">;
|
export type PlanFooterProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const PlanFooter = (props: PlanFooterProps) => (
|
export const PlanFooter = (props: PlanFooterProps) => (
|
||||||
<CardFooter data-slot="plan-footer" {...props} />
|
<CardFooter data-slot="plan-footer" {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
|
export type PlanTriggerProps = ComponentProps<typeof CollapsibleTrigger>;
|
||||||
|
|
||||||
export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
|
export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className={cn("size-8", className)}
|
className={cn("size-8", className)}
|
||||||
data-slot="plan-trigger"
|
data-slot="plan-trigger"
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronsUpDownIcon className="size-4" />
|
<ChevronsUpDownIcon className="size-4" />
|
||||||
<span className="sr-only">Toggle plan</span>
|
<span className="sr-only">Toggle plan</span>
|
||||||
</Button>
|
</Button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
);
|
);
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,274 +1,274 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ChevronDownIcon, PaperclipIcon } from "lucide-react";
|
import { ChevronDownIcon, PaperclipIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type QueueMessagePart = {
|
export type QueueMessagePart = {
|
||||||
type: string;
|
type: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
mediaType?: string;
|
mediaType?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueueMessage = {
|
export type QueueMessage = {
|
||||||
id: string;
|
id: string;
|
||||||
parts: QueueMessagePart[];
|
parts: QueueMessagePart[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueueTodo = {
|
export type QueueTodo = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
status?: "pending" | "completed";
|
status?: "pending" | "completed";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueueItemProps = ComponentProps<"li">;
|
export type QueueItemProps = ComponentProps<"li">;
|
||||||
|
|
||||||
export const QueueItem = ({ className, ...props }: QueueItemProps) => (
|
export const QueueItem = ({ className, ...props }: QueueItemProps) => (
|
||||||
<li
|
<li
|
||||||
className={cn(
|
className={cn(
|
||||||
"group hover:bg-muted flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors",
|
"group hover:bg-muted flex flex-col gap-1 rounded-md px-3 py-1 text-sm transition-colors",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemIndicatorProps = ComponentProps<"span"> & {
|
export type QueueItemIndicatorProps = ComponentProps<"span"> & {
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueueItemIndicator = ({
|
export const QueueItemIndicator = ({
|
||||||
completed = false,
|
completed = false,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemIndicatorProps) => (
|
}: QueueItemIndicatorProps) => (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-0.5 inline-block size-2.5 rounded-full border",
|
"mt-0.5 inline-block size-2.5 rounded-full border",
|
||||||
completed
|
completed
|
||||||
? "border-muted-foreground/20 bg-muted-foreground/10"
|
? "border-muted-foreground/20 bg-muted-foreground/10"
|
||||||
: "border-muted-foreground/50",
|
: "border-muted-foreground/50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemContentProps = ComponentProps<"span"> & {
|
export type QueueItemContentProps = ComponentProps<"span"> & {
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueueItemContent = ({
|
export const QueueItemContent = ({
|
||||||
completed = false,
|
completed = false,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemContentProps) => (
|
}: QueueItemContentProps) => (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"line-clamp-1 grow break-words",
|
"line-clamp-1 grow break-words",
|
||||||
completed
|
completed
|
||||||
? "text-muted-foreground/50 line-through"
|
? "text-muted-foreground/50 line-through"
|
||||||
: "text-muted-foreground",
|
: "text-muted-foreground",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemDescriptionProps = ComponentProps<"div"> & {
|
export type QueueItemDescriptionProps = ComponentProps<"div"> & {
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueueItemDescription = ({
|
export const QueueItemDescription = ({
|
||||||
completed = false,
|
completed = false,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemDescriptionProps) => (
|
}: QueueItemDescriptionProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-6 text-xs",
|
"ml-6 text-xs",
|
||||||
completed
|
completed
|
||||||
? "text-muted-foreground/40 line-through"
|
? "text-muted-foreground/40 line-through"
|
||||||
: "text-muted-foreground",
|
: "text-muted-foreground",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemActionsProps = ComponentProps<"div">;
|
export type QueueItemActionsProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const QueueItemActions = ({
|
export const QueueItemActions = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemActionsProps) => (
|
}: QueueItemActionsProps) => (
|
||||||
<div className={cn("flex gap-1", className)} {...props} />
|
<div className={cn("flex gap-1", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemActionProps = Omit<
|
export type QueueItemActionProps = Omit<
|
||||||
ComponentProps<typeof Button>,
|
ComponentProps<typeof Button>,
|
||||||
"variant" | "size"
|
"variant" | "size"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const QueueItemAction = ({
|
export const QueueItemAction = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemActionProps) => (
|
}: QueueItemActionProps) => (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground size-auto rounded p-1 opacity-0 transition-opacity group-hover:opacity-100",
|
"text-muted-foreground hover:bg-muted-foreground/10 hover:text-foreground size-auto rounded p-1 opacity-0 transition-opacity group-hover:opacity-100",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
size="icon"
|
size="icon"
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemAttachmentProps = ComponentProps<"div">;
|
export type QueueItemAttachmentProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const QueueItemAttachment = ({
|
export const QueueItemAttachment = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemAttachmentProps) => (
|
}: QueueItemAttachmentProps) => (
|
||||||
<div className={cn("mt-1 flex flex-wrap gap-2", className)} {...props} />
|
<div className={cn("mt-1 flex flex-wrap gap-2", className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemImageProps = ComponentProps<"img">;
|
export type QueueItemImageProps = ComponentProps<"img">;
|
||||||
|
|
||||||
export const QueueItemImage = ({
|
export const QueueItemImage = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemImageProps) => (
|
}: QueueItemImageProps) => (
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
className={cn("h-8 w-8 rounded border object-cover", className)}
|
className={cn("h-8 w-8 rounded border object-cover", className)}
|
||||||
height={32}
|
height={32}
|
||||||
width={32}
|
width={32}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueItemFileProps = ComponentProps<"span">;
|
export type QueueItemFileProps = ComponentProps<"span">;
|
||||||
|
|
||||||
export const QueueItemFile = ({
|
export const QueueItemFile = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueItemFileProps) => (
|
}: QueueItemFileProps) => (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted flex items-center gap-1 rounded border px-2 py-1 text-xs",
|
"bg-muted flex items-center gap-1 rounded border px-2 py-1 text-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<PaperclipIcon size={12} />
|
<PaperclipIcon size={12} />
|
||||||
<span className="max-w-[100px] truncate">{children}</span>
|
<span className="max-w-[100px] truncate">{children}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueListProps = ComponentProps<typeof ScrollArea>;
|
export type QueueListProps = ComponentProps<typeof ScrollArea>;
|
||||||
|
|
||||||
export const QueueList = ({
|
export const QueueList = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueListProps) => (
|
}: QueueListProps) => (
|
||||||
<ScrollArea className={cn("mt-2 -mb-1", className)} {...props}>
|
<ScrollArea className={cn("mt-2 -mb-1", className)} {...props}>
|
||||||
<div className="max-h-40 pr-4">
|
<div className="max-h-40 pr-4">
|
||||||
<ul>{children}</ul>
|
<ul>{children}</ul>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
|
|
||||||
// QueueSection - collapsible section container
|
// QueueSection - collapsible section container
|
||||||
export type QueueSectionProps = ComponentProps<typeof Collapsible>;
|
export type QueueSectionProps = ComponentProps<typeof Collapsible>;
|
||||||
|
|
||||||
export const QueueSection = ({
|
export const QueueSection = ({
|
||||||
className,
|
className,
|
||||||
defaultOpen = true,
|
defaultOpen = true,
|
||||||
...props
|
...props
|
||||||
}: QueueSectionProps) => (
|
}: QueueSectionProps) => (
|
||||||
<Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
|
<Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
// QueueSectionTrigger - section header/trigger
|
// QueueSectionTrigger - section header/trigger
|
||||||
export type QueueSectionTriggerProps = ComponentProps<"button">;
|
export type QueueSectionTriggerProps = ComponentProps<"button">;
|
||||||
|
|
||||||
export const QueueSectionTrigger = ({
|
export const QueueSectionTrigger = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueSectionTriggerProps) => (
|
}: QueueSectionTriggerProps) => (
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
"group bg-muted/40 text-muted-foreground hover:bg-muted flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm font-medium transition-colors",
|
"group bg-muted/40 text-muted-foreground hover:bg-muted flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm font-medium transition-colors",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
type="button"
|
type="button"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
);
|
);
|
||||||
|
|
||||||
// QueueSectionLabel - label content with icon and count
|
// QueueSectionLabel - label content with icon and count
|
||||||
export type QueueSectionLabelProps = ComponentProps<"span"> & {
|
export type QueueSectionLabelProps = ComponentProps<"span"> & {
|
||||||
count?: number;
|
count?: number;
|
||||||
label: string;
|
label: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QueueSectionLabel = ({
|
export const QueueSectionLabel = ({
|
||||||
count,
|
count,
|
||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueSectionLabelProps) => (
|
}: QueueSectionLabelProps) => (
|
||||||
<span className={cn("flex items-center gap-2", className)} {...props}>
|
<span className={cn("flex items-center gap-2", className)} {...props}>
|
||||||
<ChevronDownIcon className="size-4 transition-transform group-data-[state=closed]:-rotate-90" />
|
<ChevronDownIcon className="size-4 transition-transform group-data-[state=closed]:-rotate-90" />
|
||||||
{icon}
|
{icon}
|
||||||
<span>
|
<span>
|
||||||
{count} {label}
|
{count} {label}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
// QueueSectionContent - collapsible content area
|
// QueueSectionContent - collapsible content area
|
||||||
export type QueueSectionContentProps = ComponentProps<
|
export type QueueSectionContentProps = ComponentProps<
|
||||||
typeof CollapsibleContent
|
typeof CollapsibleContent
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const QueueSectionContent = ({
|
export const QueueSectionContent = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: QueueSectionContentProps) => (
|
}: QueueSectionContentProps) => (
|
||||||
<CollapsibleContent className={cn(className)} {...props} />
|
<CollapsibleContent className={cn(className)} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type QueueProps = ComponentProps<"div">;
|
export type QueueProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const Queue = ({ className, ...props }: QueueProps) => (
|
export const Queue = ({ className, ...props }: QueueProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-border bg-background flex flex-col gap-2 rounded-xl border px-3 pt-2 pb-2 shadow-xs",
|
"border-border bg-background flex flex-col gap-2 rounded-xl border px-3 pt-2 pb-2 shadow-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,192 +1,192 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { BrainIcon, ChevronDownIcon } from "lucide-react";
|
import { BrainIcon, ChevronDownIcon } from "lucide-react";
|
||||||
import type { ComponentProps, ReactNode } from "react";
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
import { createContext, memo, useContext, useEffect, useState } from "react";
|
import { createContext, memo, useContext, useEffect, useState } from "react";
|
||||||
import { Streamdown } from "streamdown";
|
import { Streamdown } from "streamdown";
|
||||||
import { reasoningPlugins } from "@/core/streamdown/plugins";
|
import { reasoningPlugins } from "@/core/streamdown/plugins";
|
||||||
import { Shimmer } from "./shimmer";
|
import { Shimmer } from "./shimmer";
|
||||||
|
|
||||||
type ReasoningContextValue = {
|
type ReasoningContextValue = {
|
||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: (open: boolean) => void;
|
setIsOpen: (open: boolean) => void;
|
||||||
duration: number | undefined;
|
duration: number | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReasoningContext = createContext<ReasoningContextValue | null>(null);
|
const ReasoningContext = createContext<ReasoningContextValue | null>(null);
|
||||||
|
|
||||||
export const useReasoning = () => {
|
export const useReasoning = () => {
|
||||||
const context = useContext(ReasoningContext);
|
const context = useContext(ReasoningContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Reasoning components must be used within Reasoning");
|
throw new Error("Reasoning components must be used within Reasoning");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReasoningProps = ComponentProps<typeof Collapsible> & {
|
export type ReasoningProps = ComponentProps<typeof Collapsible> & {
|
||||||
isStreaming?: boolean;
|
isStreaming?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUTO_CLOSE_DELAY = 1000;
|
const AUTO_CLOSE_DELAY = 1000;
|
||||||
const MS_IN_S = 1000;
|
const MS_IN_S = 1000;
|
||||||
|
|
||||||
export const Reasoning = memo(
|
export const Reasoning = memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
isStreaming = false,
|
isStreaming = false,
|
||||||
open,
|
open,
|
||||||
defaultOpen = true,
|
defaultOpen = true,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
duration: durationProp,
|
duration: durationProp,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ReasoningProps) => {
|
}: ReasoningProps) => {
|
||||||
const [isOpen, setIsOpen] = useControllableState({
|
const [isOpen, setIsOpen] = useControllableState({
|
||||||
prop: open,
|
prop: open,
|
||||||
defaultProp: defaultOpen,
|
defaultProp: defaultOpen,
|
||||||
onChange: onOpenChange,
|
onChange: onOpenChange,
|
||||||
});
|
});
|
||||||
const [duration, setDuration] = useControllableState({
|
const [duration, setDuration] = useControllableState({
|
||||||
prop: durationProp,
|
prop: durationProp,
|
||||||
defaultProp: undefined,
|
defaultProp: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [hasAutoClosed, setHasAutoClosed] = useState(false);
|
const [hasAutoClosed, setHasAutoClosed] = useState(false);
|
||||||
const [startTime, setStartTime] = useState<number | null>(null);
|
const [startTime, setStartTime] = useState<number | null>(null);
|
||||||
|
|
||||||
// Track duration when streaming starts and ends
|
// Track duration when streaming starts and ends
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
if (startTime === null) {
|
if (startTime === null) {
|
||||||
setStartTime(Date.now());
|
setStartTime(Date.now());
|
||||||
}
|
}
|
||||||
} else if (startTime !== null) {
|
} else if (startTime !== null) {
|
||||||
setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
|
setDuration(Math.ceil((Date.now() - startTime) / MS_IN_S));
|
||||||
setStartTime(null);
|
setStartTime(null);
|
||||||
}
|
}
|
||||||
}, [isStreaming, startTime, setDuration]);
|
}, [isStreaming, startTime, setDuration]);
|
||||||
|
|
||||||
// Auto-open when streaming starts, auto-close when streaming ends (once only)
|
// Auto-open when streaming starts, auto-close when streaming ends (once only)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
|
if (defaultOpen && !isStreaming && isOpen && !hasAutoClosed) {
|
||||||
// Add a small delay before closing to allow user to see the content
|
// Add a small delay before closing to allow user to see the content
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setHasAutoClosed(true);
|
setHasAutoClosed(true);
|
||||||
}, AUTO_CLOSE_DELAY);
|
}, AUTO_CLOSE_DELAY);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed]);
|
}, [isStreaming, isOpen, defaultOpen, setIsOpen, hasAutoClosed]);
|
||||||
|
|
||||||
const handleOpenChange = (newOpen: boolean) => {
|
const handleOpenChange = (newOpen: boolean) => {
|
||||||
setIsOpen(newOpen);
|
setIsOpen(newOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReasoningContext.Provider
|
<ReasoningContext.Provider
|
||||||
value={{ isStreaming, isOpen, setIsOpen, duration }}
|
value={{ isStreaming, isOpen, setIsOpen, duration }}
|
||||||
>
|
>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
className={cn("not-prose mb-4", className)}
|
className={cn("not-prose mb-4", className)}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</ReasoningContext.Provider>
|
</ReasoningContext.Provider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ReasoningTriggerProps = ComponentProps<
|
export type ReasoningTriggerProps = ComponentProps<
|
||||||
typeof CollapsibleTrigger
|
typeof CollapsibleTrigger
|
||||||
> & {
|
> & {
|
||||||
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
|
getThinkingMessage?: (isStreaming: boolean, duration?: number) => ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
const defaultGetThinkingMessage = (isStreaming: boolean, duration?: number) => {
|
||||||
if (isStreaming || duration === 0) {
|
if (isStreaming || duration === 0) {
|
||||||
return <Shimmer duration={1}>Thinking...</Shimmer>;
|
return <Shimmer duration={1}>Thinking...</Shimmer>;
|
||||||
}
|
}
|
||||||
if (duration === undefined) {
|
if (duration === undefined) {
|
||||||
return <span>Thought for a few seconds</span>;
|
return <span>Thought for a few seconds</span>;
|
||||||
}
|
}
|
||||||
return <span>Thought for {duration} seconds</span>;
|
return <span>Thought for {duration} seconds</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReasoningTrigger = memo(
|
export const ReasoningTrigger = memo(
|
||||||
({
|
({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
getThinkingMessage = defaultGetThinkingMessage,
|
getThinkingMessage = defaultGetThinkingMessage,
|
||||||
...props
|
...props
|
||||||
}: ReasoningTriggerProps) => {
|
}: ReasoningTriggerProps) => {
|
||||||
const { isStreaming, isOpen, duration } = useReasoning();
|
const { isStreaming, isOpen, duration } = useReasoning();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground hover:text-foreground flex w-full items-center gap-2 text-sm transition-colors",
|
"text-muted-foreground hover:text-foreground flex w-full items-center gap-2 text-sm transition-colors",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<BrainIcon className="size-4" />
|
<BrainIcon className="size-4" />
|
||||||
{getThinkingMessage(isStreaming, duration)}
|
{getThinkingMessage(isStreaming, duration)}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-4 transition-transform",
|
"size-4 transition-transform",
|
||||||
isOpen ? "rotate-180" : "rotate-0",
|
isOpen ? "rotate-180" : "rotate-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ReasoningContentProps = ComponentProps<
|
export type ReasoningContentProps = ComponentProps<
|
||||||
typeof CollapsibleContent
|
typeof CollapsibleContent
|
||||||
> & {
|
> & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReasoningContent = memo(
|
export const ReasoningContent = memo(
|
||||||
({ className, children, ...props }: ReasoningContentProps) => (
|
({ className, children, ...props }: ReasoningContentProps) => (
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-4 text-sm",
|
"mt-4 text-sm",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-muted-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{typeof children === "string" ? (
|
{typeof children === "string" ? (
|
||||||
<Streamdown {...reasoningPlugins}>{children}</Streamdown>
|
<Streamdown {...reasoningPlugins}>{children}</Streamdown>
|
||||||
) : (
|
) : (
|
||||||
children
|
children
|
||||||
)}
|
)}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Reasoning.displayName = "Reasoning";
|
Reasoning.displayName = "Reasoning";
|
||||||
ReasoningTrigger.displayName = "ReasoningTrigger";
|
ReasoningTrigger.displayName = "ReasoningTrigger";
|
||||||
ReasoningContent.displayName = "ReasoningContent";
|
ReasoningContent.displayName = "ReasoningContent";
|
||||||
@@ -1,64 +1,64 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "motion/react";
|
||||||
import {
|
import {
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
type ElementType,
|
type ElementType,
|
||||||
type JSX,
|
type JSX,
|
||||||
memo,
|
memo,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
export type TextShimmerProps = {
|
export type TextShimmerProps = {
|
||||||
children: string;
|
children: string;
|
||||||
as?: ElementType;
|
as?: ElementType;
|
||||||
className?: string;
|
className?: string;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
spread?: number;
|
spread?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ShimmerComponent = ({
|
const ShimmerComponent = ({
|
||||||
children,
|
children,
|
||||||
as: Component = "p",
|
as: Component = "p",
|
||||||
className,
|
className,
|
||||||
duration = 2,
|
duration = 2,
|
||||||
spread = 2,
|
spread = 2,
|
||||||
}: TextShimmerProps) => {
|
}: TextShimmerProps) => {
|
||||||
const MotionComponent = motion.create(
|
const MotionComponent = motion.create(
|
||||||
Component as keyof JSX.IntrinsicElements,
|
Component as keyof JSX.IntrinsicElements,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dynamicSpread = useMemo(
|
const dynamicSpread = useMemo(
|
||||||
() => (children?.length ?? 0) * spread,
|
() => (children?.length ?? 0) * spread,
|
||||||
[children, spread],
|
[children, spread],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MotionComponent
|
<MotionComponent
|
||||||
animate={{ backgroundPosition: "0% center" }}
|
animate={{ backgroundPosition: "0% center" }}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
"relative inline-block bg-[length:250%_100%,auto] bg-clip-text text-transparent",
|
||||||
"[background-repeat:no-repeat,padding-box] [--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))]",
|
"[background-repeat:no-repeat,padding-box] [--bg:linear-gradient(90deg,#0000_calc(50%-var(--spread)),var(--color-background),#0000_calc(50%+var(--spread)))]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
initial={{ backgroundPosition: "100% center" }}
|
initial={{ backgroundPosition: "100% center" }}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--spread": `${dynamicSpread}px`,
|
"--spread": `${dynamicSpread}px`,
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
|
"var(--bg), linear-gradient(var(--color-muted-foreground), var(--color-muted-foreground))",
|
||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}
|
||||||
transition={{
|
transition={{
|
||||||
repeat: Number.POSITIVE_INFINITY,
|
repeat: Number.POSITIVE_INFINITY,
|
||||||
duration,
|
duration,
|
||||||
ease: "linear",
|
ease: "linear",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</MotionComponent>
|
</MotionComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Shimmer = memo(ShimmerComponent);
|
export const Shimmer = memo(ShimmerComponent);
|
||||||
@@ -1,77 +1,77 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { BookIcon, ChevronDownIcon } from "lucide-react";
|
import { BookIcon, ChevronDownIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type SourcesProps = ComponentProps<"div">;
|
export type SourcesProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const Sources = ({ className, ...props }: SourcesProps) => (
|
export const Sources = ({ className, ...props }: SourcesProps) => (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
className={cn("not-prose text-primary mb-4 text-xs", className)}
|
className={cn("not-prose text-primary mb-4 text-xs", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
export type SourcesTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SourcesTrigger = ({
|
export const SourcesTrigger = ({
|
||||||
className,
|
className,
|
||||||
count,
|
count,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: SourcesTriggerProps) => (
|
}: SourcesTriggerProps) => (
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
className={cn("flex items-center gap-2", className)}
|
className={cn("flex items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<p className="font-medium">Used {count} sources</p>
|
<p className="font-medium">Used {count} sources</p>
|
||||||
<ChevronDownIcon className="h-4 w-4" />
|
<ChevronDownIcon className="h-4 w-4" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SourcesContentProps = ComponentProps<typeof CollapsibleContent>;
|
export type SourcesContentProps = ComponentProps<typeof CollapsibleContent>;
|
||||||
|
|
||||||
export const SourcesContent = ({
|
export const SourcesContent = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: SourcesContentProps) => (
|
}: SourcesContentProps) => (
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-3 flex w-fit flex-col gap-2",
|
"mt-3 flex w-fit flex-col gap-2",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SourceProps = ComponentProps<"a">;
|
export type SourceProps = ComponentProps<"a">;
|
||||||
|
|
||||||
export const Source = ({ href, title, children, ...props }: SourceProps) => (
|
export const Source = ({ href, title, children, ...props }: SourceProps) => (
|
||||||
<a
|
<a
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
href={href}
|
href={href}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<>
|
<>
|
||||||
<BookIcon className="h-4 w-4" />
|
<BookIcon className="h-4 w-4" />
|
||||||
<span className="block font-medium">{title}</span>
|
<span className="block font-medium">{title}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
@@ -1,80 +1,80 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
import { Children, type ComponentProps } from "react";
|
import { Children, type ComponentProps } from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const STAGGER_DELAY_MS = 60;
|
const STAGGER_DELAY_MS = 60;
|
||||||
const STAGGER_DELAY_MS_OFFSET = 250;
|
const STAGGER_DELAY_MS_OFFSET = 250;
|
||||||
|
|
||||||
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
|
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
|
||||||
|
|
||||||
export const Suggestions = ({
|
export const Suggestions = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: SuggestionsProps) => (
|
}: SuggestionsProps) => (
|
||||||
<ScrollArea className="overflow-x-auto whitespace-normal" {...props}>
|
<ScrollArea className="overflow-x-auto whitespace-normal" {...props}>
|
||||||
<div
|
<div
|
||||||
className={cn("flex w-full flex-wrap items-center gap-2", className)}
|
className={cn("flex w-full flex-wrap items-center gap-2", className)}
|
||||||
data-slot="suggestions-list"
|
data-slot="suggestions-list"
|
||||||
>
|
>
|
||||||
{Children.map(children, (child, index) =>
|
{Children.map(children, (child, index) =>
|
||||||
child != null ? (
|
child != null ? (
|
||||||
<span
|
<span
|
||||||
className="animate-fade-in-up max-w-full opacity-0"
|
className="animate-fade-in-up max-w-full opacity-0"
|
||||||
style={{
|
style={{
|
||||||
animationDelay: `${STAGGER_DELAY_MS_OFFSET + index * STAGGER_DELAY_MS}ms`,
|
animationDelay: `${STAGGER_DELAY_MS_OFFSET + index * STAGGER_DELAY_MS}ms`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{child}
|
{child}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
child
|
child
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<ScrollBar className="hidden" orientation="horizontal" />
|
<ScrollBar className="hidden" orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
|
export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
|
||||||
suggestion: React.ReactNode;
|
suggestion: React.ReactNode;
|
||||||
icon?: LucideIcon;
|
icon?: LucideIcon;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Suggestion = ({
|
export const Suggestion = ({
|
||||||
suggestion,
|
suggestion,
|
||||||
onClick,
|
onClick,
|
||||||
className,
|
className,
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "sm",
|
size = "sm",
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: SuggestionProps) => {
|
}: SuggestionProps) => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
onClick?.();
|
onClick?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground dark:bg-background h-auto max-w-full cursor-pointer rounded-full px-4 py-2 text-center text-xs font-normal whitespace-normal",
|
"text-muted-foreground dark:bg-background h-auto max-w-full cursor-pointer rounded-full px-4 py-2 text-center text-xs font-normal whitespace-normal",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
size={size}
|
size={size}
|
||||||
type="button"
|
type="button"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Icon && <Icon className="size-4" />}
|
{Icon && <Icon className="size-4" />}
|
||||||
{children ?? suggestion}
|
{children ?? suggestion}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,87 +1,87 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ChevronDownIcon, SearchIcon } from "lucide-react";
|
import { ChevronDownIcon, SearchIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
export type TaskItemFileProps = ComponentProps<"div">;
|
export type TaskItemFileProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const TaskItemFile = ({
|
export const TaskItemFile = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: TaskItemFileProps) => (
|
}: TaskItemFileProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-secondary text-foreground inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-xs",
|
"bg-secondary text-foreground inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TaskItemProps = ComponentProps<"div">;
|
export type TaskItemProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (
|
export const TaskItem = ({ children, className, ...props }: TaskItemProps) => (
|
||||||
<div className={cn("text-muted-foreground text-sm", className)} {...props}>
|
<div className={cn("text-muted-foreground text-sm", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TaskProps = ComponentProps<typeof Collapsible>;
|
export type TaskProps = ComponentProps<typeof Collapsible>;
|
||||||
|
|
||||||
export const Task = ({
|
export const Task = ({
|
||||||
defaultOpen = true,
|
defaultOpen = true,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: TaskProps) => (
|
}: TaskProps) => (
|
||||||
<Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
|
<Collapsible className={cn(className)} defaultOpen={defaultOpen} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
export type TaskTriggerProps = ComponentProps<typeof CollapsibleTrigger> & {
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TaskTrigger = ({
|
export const TaskTrigger = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
...props
|
...props
|
||||||
}: TaskTriggerProps) => (
|
}: TaskTriggerProps) => (
|
||||||
<CollapsibleTrigger asChild className={cn("group", className)} {...props}>
|
<CollapsibleTrigger asChild className={cn("group", className)} {...props}>
|
||||||
{children ?? (
|
{children ?? (
|
||||||
<div className="text-muted-foreground hover:text-foreground flex w-full cursor-pointer items-center gap-2 text-sm transition-colors">
|
<div className="text-muted-foreground hover:text-foreground flex w-full cursor-pointer items-center gap-2 text-sm transition-colors">
|
||||||
<SearchIcon className="size-4" />
|
<SearchIcon className="size-4" />
|
||||||
<p className="text-sm">{title}</p>
|
<p className="text-sm">{title}</p>
|
||||||
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TaskContentProps = ComponentProps<typeof CollapsibleContent>;
|
export type TaskContentProps = ComponentProps<typeof CollapsibleContent>;
|
||||||
|
|
||||||
export const TaskContent = ({
|
export const TaskContent = ({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: TaskContentProps) => (
|
}: TaskContentProps) => (
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2 text-popover-foreground data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="border-muted mt-4 space-y-2 border-l-2 pl-4">
|
<div className="border-muted mt-4 space-y-2 border-l-2 pl-4">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
);
|
);
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { NodeToolbar, Position } from "@xyflow/react";
|
import { NodeToolbar, Position } from "@xyflow/react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
type ToolbarProps = ComponentProps<typeof NodeToolbar>;
|
type ToolbarProps = ComponentProps<typeof NodeToolbar>;
|
||||||
|
|
||||||
export const Toolbar = ({ className, ...props }: ToolbarProps) => (
|
export const Toolbar = ({ className, ...props }: ToolbarProps) => (
|
||||||
<NodeToolbar
|
<NodeToolbar
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background flex items-center gap-1 rounded-sm border p-1.5",
|
"bg-background flex items-center gap-1 rounded-sm border p-1.5",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
position={Position.Bottom}
|
position={Position.Bottom}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -1,263 +1,263 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
CollapsibleContent,
|
CollapsibleContent,
|
||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ChevronDownIcon } from "lucide-react";
|
import { ChevronDownIcon } from "lucide-react";
|
||||||
import type { ComponentProps, ReactNode } from "react";
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
import { createContext, useContext, useEffect, useState } from "react";
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export type WebPreviewContextValue = {
|
export type WebPreviewContextValue = {
|
||||||
url: string;
|
url: string;
|
||||||
setUrl: (url: string) => void;
|
setUrl: (url: string) => void;
|
||||||
consoleOpen: boolean;
|
consoleOpen: boolean;
|
||||||
setConsoleOpen: (open: boolean) => void;
|
setConsoleOpen: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
|
const WebPreviewContext = createContext<WebPreviewContextValue | null>(null);
|
||||||
|
|
||||||
const useWebPreview = () => {
|
const useWebPreview = () => {
|
||||||
const context = useContext(WebPreviewContext);
|
const context = useContext(WebPreviewContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("WebPreview components must be used within a WebPreview");
|
throw new Error("WebPreview components must be used within a WebPreview");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebPreviewProps = ComponentProps<"div"> & {
|
export type WebPreviewProps = ComponentProps<"div"> & {
|
||||||
defaultUrl?: string;
|
defaultUrl?: string;
|
||||||
onUrlChange?: (url: string) => void;
|
onUrlChange?: (url: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebPreview = ({
|
export const WebPreview = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
defaultUrl = "",
|
defaultUrl = "",
|
||||||
onUrlChange,
|
onUrlChange,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewProps) => {
|
}: WebPreviewProps) => {
|
||||||
const [url, setUrl] = useState(defaultUrl);
|
const [url, setUrl] = useState(defaultUrl);
|
||||||
const [consoleOpen, setConsoleOpen] = useState(false);
|
const [consoleOpen, setConsoleOpen] = useState(false);
|
||||||
|
|
||||||
const handleUrlChange = (newUrl: string) => {
|
const handleUrlChange = (newUrl: string) => {
|
||||||
setUrl(newUrl);
|
setUrl(newUrl);
|
||||||
onUrlChange?.(newUrl);
|
onUrlChange?.(newUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextValue: WebPreviewContextValue = {
|
const contextValue: WebPreviewContextValue = {
|
||||||
url,
|
url,
|
||||||
setUrl: handleUrlChange,
|
setUrl: handleUrlChange,
|
||||||
consoleOpen,
|
consoleOpen,
|
||||||
setConsoleOpen,
|
setConsoleOpen,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WebPreviewContext.Provider value={contextValue}>
|
<WebPreviewContext.Provider value={contextValue}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card flex size-full flex-col rounded-lg border",
|
"bg-card flex size-full flex-col rounded-lg border",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</WebPreviewContext.Provider>
|
</WebPreviewContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebPreviewNavigationProps = ComponentProps<"div">;
|
export type WebPreviewNavigationProps = ComponentProps<"div">;
|
||||||
|
|
||||||
export const WebPreviewNavigation = ({
|
export const WebPreviewNavigation = ({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewNavigationProps) => (
|
}: WebPreviewNavigationProps) => (
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center gap-1 border-b p-2", className)}
|
className={cn("flex items-center gap-1 border-b p-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
|
export type WebPreviewNavigationButtonProps = ComponentProps<typeof Button> & {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebPreviewNavigationButton = ({
|
export const WebPreviewNavigationButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
tooltip,
|
tooltip,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewNavigationButtonProps) => (
|
}: WebPreviewNavigationButtonProps) => (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="hover:text-foreground h-8 w-8 p-0"
|
className="hover:text-foreground h-8 w-8 p-0"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{tooltip}</p>
|
<p>{tooltip}</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export type WebPreviewUrlProps = ComponentProps<typeof Input>;
|
export type WebPreviewUrlProps = ComponentProps<typeof Input>;
|
||||||
|
|
||||||
export const WebPreviewUrl = ({
|
export const WebPreviewUrl = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewUrlProps) => {
|
}: WebPreviewUrlProps) => {
|
||||||
const { url, setUrl } = useWebPreview();
|
const { url, setUrl } = useWebPreview();
|
||||||
const [inputValue, setInputValue] = useState(url);
|
const [inputValue, setInputValue] = useState(url);
|
||||||
|
|
||||||
// Sync input value with context URL when it changes externally
|
// Sync input value with context URL when it changes externally
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInputValue(url);
|
setInputValue(url);
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputValue(event.target.value);
|
setInputValue(event.target.value);
|
||||||
onChange?.(event);
|
onChange?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
const target = event.target as HTMLInputElement;
|
const target = event.target as HTMLInputElement;
|
||||||
setUrl(target.value);
|
setUrl(target.value);
|
||||||
}
|
}
|
||||||
onKeyDown?.(event);
|
onKeyDown?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
className="h-8 flex-1 text-sm"
|
className="h-8 flex-1 text-sm"
|
||||||
onChange={onChange ?? handleChange}
|
onChange={onChange ?? handleChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Enter URL..."
|
placeholder="Enter URL..."
|
||||||
value={value ?? inputValue}
|
value={value ?? inputValue}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebPreviewBodyProps = ComponentProps<"iframe"> & {
|
export type WebPreviewBodyProps = ComponentProps<"iframe"> & {
|
||||||
loading?: ReactNode;
|
loading?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebPreviewBody = ({
|
export const WebPreviewBody = ({
|
||||||
className,
|
className,
|
||||||
loading,
|
loading,
|
||||||
src,
|
src,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewBodyProps) => {
|
}: WebPreviewBodyProps) => {
|
||||||
const { url } = useWebPreview();
|
const { url } = useWebPreview();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<iframe
|
<iframe
|
||||||
className={cn("size-full", className)}
|
className={cn("size-full", className)}
|
||||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-presentation"
|
||||||
src={(src ?? url) || undefined}
|
src={(src ?? url) || undefined}
|
||||||
title="Preview"
|
title="Preview"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{loading}
|
{loading}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WebPreviewConsoleProps = ComponentProps<"div"> & {
|
export type WebPreviewConsoleProps = ComponentProps<"div"> & {
|
||||||
logs?: Array<{
|
logs?: Array<{
|
||||||
level: "log" | "warn" | "error";
|
level: "log" | "warn" | "error";
|
||||||
message: string;
|
message: string;
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WebPreviewConsole = ({
|
export const WebPreviewConsole = ({
|
||||||
className,
|
className,
|
||||||
logs = [],
|
logs = [],
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: WebPreviewConsoleProps) => {
|
}: WebPreviewConsoleProps) => {
|
||||||
const { consoleOpen, setConsoleOpen } = useWebPreview();
|
const { consoleOpen, setConsoleOpen } = useWebPreview();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
className={cn("bg-muted/50 border-t font-mono text-sm", className)}
|
className={cn("bg-muted/50 border-t font-mono text-sm", className)}
|
||||||
onOpenChange={setConsoleOpen}
|
onOpenChange={setConsoleOpen}
|
||||||
open={consoleOpen}
|
open={consoleOpen}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="hover:bg-muted/50 flex w-full items-center justify-between p-4 text-left font-medium"
|
className="hover:bg-muted/50 flex w-full items-center justify-between p-4 text-left font-medium"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
Console
|
Console
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-4 w-4 transition-transform duration-200",
|
"h-4 w-4 transition-transform duration-200",
|
||||||
consoleOpen && "rotate-180",
|
consoleOpen && "rotate-180",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-4 pb-4",
|
"px-4 pb-4",
|
||||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=open]:animate-in outline-none",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="max-h-48 space-y-1 overflow-y-auto">
|
<div className="max-h-48 space-y-1 overflow-y-auto">
|
||||||
{logs.length === 0 ? (
|
{logs.length === 0 ? (
|
||||||
<p className="text-muted-foreground">No console output</p>
|
<p className="text-muted-foreground">No console output</p>
|
||||||
) : (
|
) : (
|
||||||
logs.map((log, index) => (
|
logs.map((log, index) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xs",
|
"text-xs",
|
||||||
log.level === "error" && "text-destructive",
|
log.level === "error" && "text-destructive",
|
||||||
log.level === "warn" && "text-yellow-600",
|
log.level === "warn" && "text-yellow-600",
|
||||||
log.level === "log" && "text-foreground",
|
log.level === "log" && "text-foreground",
|
||||||
)}
|
)}
|
||||||
key={`${log.timestamp.getTime()}-${index}`}
|
key={`${log.timestamp.getTime()}-${index}`}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{log.timestamp.toLocaleTimeString()}
|
{log.timestamp.toLocaleTimeString()}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
{log.message}
|
{log.message}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
const year = useMemo(() => new Date().getFullYear(), []);
|
const year = useMemo(() => new Date().getFullYear(), []);
|
||||||
return (
|
return (
|
||||||
<footer className="container-md mx-auto mt-32 flex flex-col items-center justify-center">
|
<footer className="container-md mx-auto mt-32 flex flex-col items-center justify-center">
|
||||||
<hr className="from-border/0 to-border/0 m-0 h-px w-full border-none bg-linear-to-r via-white/20" />
|
<hr className="from-border/0 to-border/0 m-0 h-px w-full border-none bg-linear-to-r via-white/20" />
|
||||||
<div className="text-muted-foreground container flex h-20 flex-col items-center justify-center text-sm">
|
<div className="text-muted-foreground container flex h-20 flex-col items-center justify-center text-sm">
|
||||||
<p className="text-center font-serif text-lg md:text-xl">
|
<p className="text-center font-serif text-lg md:text-xl">
|
||||||
"Originated from Open Source, give back to Open Source."
|
"Originated from Open Source, give back to Open Source."
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground container mb-8 flex flex-col items-center justify-center text-xs">
|
<div className="text-muted-foreground container mb-8 flex flex-col items-center justify-center text-xs">
|
||||||
<p>Licensed under MIT License</p>
|
<p>Licensed under MIT License</p>
|
||||||
<p>© {year} CM 助手</p>
|
<p>© {year} CM 助手</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,52 +1,52 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import type { Locale } from "@/core/i18n/locale";
|
import type { Locale } from "@/core/i18n/locale";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export type HeaderProps = {
|
export type HeaderProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
homeURL?: string;
|
homeURL?: string;
|
||||||
locale?: Locale;
|
locale?: Locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Header({ className, homeURL }: HeaderProps) {
|
export function Header({ className, homeURL }: HeaderProps) {
|
||||||
const isExternalHome = !homeURL;
|
const isExternalHome = !homeURL;
|
||||||
const { locale: resolvedLocale, t } = useI18n();
|
const { locale: resolvedLocale, t } = useI18n();
|
||||||
const lang = resolvedLocale.substring(0, 2);
|
const lang = resolvedLocale.substring(0, 2);
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn(
|
className={cn(
|
||||||
"container-md fixed top-0 right-0 left-0 z-20 mx-auto flex h-16 items-center justify-between backdrop-blur-xs",
|
"container-md fixed top-0 right-0 left-0 z-20 mx-auto flex h-16 items-center justify-between backdrop-blur-xs",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<a
|
<a
|
||||||
href={homeURL ?? "/"}
|
href={homeURL ?? "/"}
|
||||||
target={isExternalHome ? "_blank" : "_self"}
|
target={isExternalHome ? "_blank" : "_self"}
|
||||||
rel={isExternalHome ? "noopener noreferrer" : undefined}
|
rel={isExternalHome ? "noopener noreferrer" : undefined}
|
||||||
>
|
>
|
||||||
<h1 className="font-serif text-xl">CM 助手</h1>
|
<h1 className="font-serif text-xl">CM 助手</h1>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<nav className="mr-8 ml-auto flex items-center gap-8 text-sm font-medium">
|
<nav className="mr-8 ml-auto flex items-center gap-8 text-sm font-medium">
|
||||||
<Link
|
<Link
|
||||||
href={`/${lang}/docs`}
|
href={`/${lang}/docs`}
|
||||||
className="text-secondary-foreground hover:text-foreground transition-colors"
|
className="text-secondary-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{t.home.docs}
|
{t.home.docs}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/blog/posts"
|
href="/blog/posts"
|
||||||
className="text-secondary-foreground hover:text-foreground transition-colors"
|
className="text-secondary-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{t.home.blog}
|
{t.home.blog}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
<hr className="from-border/0 via-border/70 to-border/0 absolute top-16 right-0 left-0 z-10 m-0 h-px w-full border-none bg-linear-to-r" />
|
<hr className="from-border/0 via-border/70 to-border/0 absolute top-16 right-0 left-0 z-10 m-0 h-px w-full border-none bg-linear-to-r" />
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,141 +1,136 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ChevronRightIcon } from "lucide-react";
|
import { ChevronRightIcon } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import deerMaskUrl from "@/assets/deer.svg";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Button } from "@/components/ui/button";
|
import { FlickeringGrid } from "@/components/ui/flickering-grid";
|
||||||
import { FlickeringGrid } from "@/components/ui/flickering-grid";
|
import Galaxy from "@/components/ui/galaxy";
|
||||||
import Galaxy from "@/components/ui/galaxy";
|
import { WordRotate } from "@/components/ui/word-rotate";
|
||||||
import { WordRotate } from "@/components/ui/word-rotate";
|
import { env } from "@/env";
|
||||||
import { env } from "@/env";
|
import { cn } from "@/lib/utils";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
export function Hero({ className }: { className?: string }) {
|
||||||
export function Hero({ className }: { className?: string }) {
|
return (
|
||||||
return (
|
<div
|
||||||
<div
|
className={cn(
|
||||||
className={cn(
|
"flex size-full flex-col items-center justify-center",
|
||||||
"flex size-full flex-col items-center justify-center",
|
className,
|
||||||
className,
|
)}
|
||||||
)}
|
>
|
||||||
>
|
<div className="absolute inset-0 z-0 bg-black/40">
|
||||||
<div className="absolute inset-0 z-0 bg-black/40">
|
<Galaxy
|
||||||
<Galaxy
|
mouseRepulsion={false}
|
||||||
mouseRepulsion={false}
|
starSpeed={0.2}
|
||||||
starSpeed={0.2}
|
density={0.6}
|
||||||
density={0.6}
|
glowIntensity={0.35}
|
||||||
glowIntensity={0.35}
|
twinkleIntensity={0.3}
|
||||||
twinkleIntensity={0.3}
|
speed={0.5}
|
||||||
speed={0.5}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<FlickeringGrid
|
||||||
<FlickeringGrid
|
className="absolute inset-0 z-0 translate-y-8 mask-[url(/images/deer.svg)] mask-size-[100vw] mask-center mask-no-repeat md:mask-size-[72vh]"
|
||||||
className="absolute inset-0 z-0 translate-y-8 mask-size-[100vw] mask-center mask-no-repeat md:mask-size-[72vh]"
|
squareSize={4}
|
||||||
style={{
|
gridGap={4}
|
||||||
WebkitMaskImage: `url("${deerMaskUrl}")`,
|
color={"white"}
|
||||||
maskImage: `url("${deerMaskUrl}")`,
|
maxOpacity={0.3}
|
||||||
}}
|
flickerChance={0.25}
|
||||||
squareSize={4}
|
/>
|
||||||
gridGap={4}
|
<div className="container-md relative z-10 mx-auto flex h-screen flex-col items-center justify-center">
|
||||||
color={"white"}
|
<h1 className="flex items-center gap-2 text-4xl font-bold md:text-6xl">
|
||||||
maxOpacity={0.3}
|
<WordRotate
|
||||||
flickerChance={0.25}
|
words={[
|
||||||
/>
|
"Deep Research",
|
||||||
<div className="container-md relative z-10 mx-auto flex h-screen flex-col items-center justify-center">
|
"Collect Data",
|
||||||
<h1 className="flex items-center gap-2 text-4xl font-bold md:text-6xl">
|
"Analyze Data",
|
||||||
<WordRotate
|
"Generate Webpages",
|
||||||
words={[
|
"Vibe Coding",
|
||||||
"Deep Research",
|
"Generate Slides",
|
||||||
"Collect Data",
|
"Generate Images",
|
||||||
"Analyze Data",
|
"Generate Podcasts",
|
||||||
"Generate Webpages",
|
"Generate Videos",
|
||||||
"Vibe Coding",
|
"Generate Songs",
|
||||||
"Generate Slides",
|
"Organize Emails",
|
||||||
"Generate Images",
|
"Do Anything",
|
||||||
"Generate Podcasts",
|
"Learn Anything",
|
||||||
"Generate Videos",
|
]}
|
||||||
"Generate Songs",
|
/>{" "}
|
||||||
"Organize Emails",
|
<div>CM 助手</div>
|
||||||
"Do Anything",
|
</h1>
|
||||||
"Learn Anything",
|
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
||||||
]}
|
<a
|
||||||
/>{" "}
|
href="https://byteplus.com"
|
||||||
<div>CM 助手</div>
|
target="_blank"
|
||||||
</h1>
|
rel="noopener noreferrer"
|
||||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY && (
|
>
|
||||||
<a
|
<div className="mt-4 flex size-fit items-center rounded-lg border border-white px-3 text-center text-shadow-sm">
|
||||||
href="https://byteplus.com"
|
<span>In partnership with</span>
|
||||||
target="_blank"
|
<BytePlusIcon className="h-4" />
|
||||||
rel="noopener noreferrer"
|
</div>
|
||||||
>
|
</a>
|
||||||
<div className="mt-4 flex size-fit items-center rounded-lg border border-white px-3 text-center text-shadow-sm">
|
)}
|
||||||
<span>In partnership with</span>
|
<p className="text-muted-foreground mt-8 scale-105 text-center text-2xl text-shadow-sm">
|
||||||
<BytePlusIcon className="h-4" />
|
An open-source SuperAgent harness that researches, codes, and creates.
|
||||||
</div>
|
With
|
||||||
</a>
|
<br />
|
||||||
)}
|
the help of sandboxes, memories, tools, skills and subagents, it
|
||||||
<p className="text-muted-foreground mt-8 scale-105 text-center text-2xl text-shadow-sm">
|
handles
|
||||||
An open-source SuperAgent harness that researches, codes, and creates.
|
<br />
|
||||||
With
|
different levels of tasks that could take minutes to hours.
|
||||||
<br />
|
</p>
|
||||||
the help of sandboxes, memories, tools, skills and subagents, it
|
<Link href="/workspace">
|
||||||
handles
|
<Button className="size-lg mt-8 scale-108" size="lg">
|
||||||
<br />
|
<span className="text-md">Get Started with 2.0</span>
|
||||||
different levels of tasks that could take minutes to hours.
|
<ChevronRightIcon className="size-4" />
|
||||||
</p>
|
</Button>
|
||||||
<Link href="/workspace">
|
</Link>
|
||||||
<Button className="size-lg mt-8 scale-108" size="lg">
|
</div>
|
||||||
<span className="text-md">Get Started with 2.0</span>
|
</div>
|
||||||
<ChevronRightIcon className="size-4" />
|
);
|
||||||
</Button>
|
}
|
||||||
</Link>
|
|
||||||
</div>
|
function BytePlusIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
</div>
|
return (
|
||||||
);
|
<svg
|
||||||
}
|
width={80}
|
||||||
|
height={24}
|
||||||
function BytePlusIcon(props: React.SVGProps<SVGSVGElement>) {
|
viewBox="0 0 120 24"
|
||||||
return (
|
fill="none"
|
||||||
<svg
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={80}
|
{...props}
|
||||||
height={24}
|
>
|
||||||
viewBox="0 0 120 24"
|
<path
|
||||||
fill="none"
|
d="M30.8027 3.64648H37.9333C38.8894 3.64648 39.7259 3.81911 40.4297 4.16435C41.1334 4.50959 41.6911 4.98761 42.0762 5.59843C42.4613 6.20924 42.6472 6.88644 42.6472 7.6566C42.6472 8.34708 42.4746 8.99773 42.1426 9.58198C41.7973 10.1795 41.3326 10.631 40.7483 10.9629C41.5583 11.3082 42.1691 11.8128 42.5808 12.49C42.9924 13.1672 43.1916 13.9108 43.1916 14.734C43.1916 15.5175 42.9924 16.208 42.5941 16.8188C42.1957 17.4296 41.638 17.9209 40.921 18.2661C40.2039 18.6246 39.3408 18.7973 38.3449 18.7973H30.8027V3.64648ZM37.8005 9.99361C38.4113 9.99361 38.9026 9.82099 39.2877 9.47575C39.6728 9.13051 39.8587 8.69232 39.8587 8.1479C39.8587 7.5902 39.6728 7.12546 39.301 6.76694C38.9292 6.40842 38.4379 6.22252 37.8138 6.22252H33.5248V9.99361H37.8005ZM38.1458 16.2345C38.7831 16.2345 39.301 16.0619 39.6993 15.7034C40.0977 15.3449 40.2969 14.8934 40.2969 14.349C40.2969 13.9772 40.2039 13.6319 40.018 13.3398C39.8321 13.0477 39.5798 12.8087 39.2479 12.636C38.9292 12.4634 38.5574 12.3705 38.1325 12.3705H33.5381V16.2212H38.1458V16.2345Z"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
fill="currentColor"
|
||||||
{...props}
|
/>
|
||||||
>
|
<path
|
||||||
<path
|
d="M49.2392 18.2385L44.127 7.3236H46.7295L50.567 15.4899L54.0062 7.3236H57.6445V5.09281L60.2073 4.33594V7.3236H63.0887V9.75357H60.2073V15.304C60.2073 15.7023 60.3135 16.021 60.5259 16.2202C60.7384 16.4327 61.0438 16.5655 61.4422 16.6318C61.8405 16.6982 62.3982 16.7115 63.0887 16.6584V18.9556C61.7741 19.1016 60.7251 19.0618 59.9417 18.8626C59.1582 18.6502 58.574 18.2518 58.2022 17.6676C57.8304 17.0833 57.6445 16.2866 57.6445 15.2907V9.75357H55.5863L49.4782 24.0014H46.7959L49.2392 18.2385Z"
|
||||||
d="M30.8027 3.64648H37.9333C38.8894 3.64648 39.7259 3.81911 40.4297 4.16435C41.1334 4.50959 41.6911 4.98761 42.0762 5.59843C42.4613 6.20924 42.6472 6.88644 42.6472 7.6566C42.6472 8.34708 42.4746 8.99773 42.1426 9.58198C41.7973 10.1795 41.3326 10.631 40.7483 10.9629C41.5583 11.3082 42.1691 11.8128 42.5808 12.49C42.9924 13.1672 43.1916 13.9108 43.1916 14.734C43.1916 15.5175 42.9924 16.208 42.5941 16.8188C42.1957 17.4296 41.638 17.9209 40.921 18.2661C40.2039 18.6246 39.3408 18.7973 38.3449 18.7973H30.8027V3.64648ZM37.8005 9.99361C38.4113 9.99361 38.9026 9.82099 39.2877 9.47575C39.6728 9.13051 39.8587 8.69232 39.8587 8.1479C39.8587 7.5902 39.6728 7.12546 39.301 6.76694C38.9292 6.40842 38.4379 6.22252 37.8138 6.22252H33.5248V9.99361H37.8005ZM38.1458 16.2345C38.7831 16.2345 39.301 16.0619 39.6993 15.7034C40.0977 15.3449 40.2969 14.8934 40.2969 14.349C40.2969 13.9772 40.2039 13.6319 40.018 13.3398C39.8321 13.0477 39.5798 12.8087 39.2479 12.636C38.9292 12.4634 38.5574 12.3705 38.1325 12.3705H33.5381V16.2212H38.1458V16.2345Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M70.2461 19.1147C69.1572 19.1147 68.1746 18.8491 67.2849 18.318C66.3953 17.7869 65.6782 17.0565 65.1604 16.1138C64.6425 15.1843 64.377 14.1485 64.377 13.0066C64.377 11.8912 64.6425 10.882 65.1737 9.97908C65.7048 9.06286 66.4218 8.34582 67.3248 7.82796C68.2277 7.29682 69.2236 7.03125 70.2992 7.03125C71.6536 7.03125 72.7822 7.33666 73.6719 7.93419C74.5616 8.53172 75.2122 9.36827 75.584 10.4438C75.9691 11.5194 76.0753 12.7676 75.916 14.1751L67.0061 14.1884C67.1389 14.7062 67.3779 15.1577 67.7364 15.5295C68.0949 15.9013 68.5066 16.1802 68.958 16.366C69.4095 16.5519 69.8477 16.6316 70.2461 16.6316C70.6975 16.6316 71.1092 16.5918 71.5075 16.4988C71.8926 16.4059 72.2378 16.2731 72.5432 16.0739C72.8486 15.8747 73.1142 15.649 73.3665 15.3967L73.4727 15.2639L75.5043 16.6051C74.9998 17.4416 74.296 18.0657 73.4063 18.4773C72.5167 18.9022 71.4677 19.1147 70.2461 19.1147ZM73.486 11.9576C73.3665 11.4663 73.1673 11.0281 72.8619 10.6563C72.5565 10.2845 72.1847 9.99236 71.7332 9.79318C71.2818 9.594 70.8037 9.48778 70.2992 9.48778C69.8211 9.48778 69.3564 9.594 68.8916 9.79318C68.4269 9.99236 68.0285 10.2845 67.6833 10.6563C67.3381 11.0281 67.1256 11.4663 67.0459 11.9576H73.486Z"
|
||||||
d="M49.2392 18.2385L44.127 7.3236H46.7295L50.567 15.4899L54.0062 7.3236H57.6445V5.09281L60.2073 4.33594V7.3236H63.0887V9.75357H60.2073V15.304C60.2073 15.7023 60.3135 16.021 60.5259 16.2202C60.7384 16.4327 61.0438 16.5655 61.4422 16.6318C61.8405 16.6982 62.3982 16.7115 63.0887 16.6584V18.9556C61.7741 19.1016 60.7251 19.0618 59.9417 18.8626C59.1582 18.6502 58.574 18.2518 58.2022 17.6676C57.8304 17.0833 57.6445 16.2866 57.6445 15.2907V9.75357H55.5863L49.4782 24.0014H46.7959L49.2392 18.2385Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M78 3.64648H84.3206C86.0733 3.64648 87.4278 4.0714 88.3838 4.92122C89.3399 5.77105 89.8179 6.93956 89.8179 8.42675C89.8179 9.39608 89.6054 10.2459 89.1805 10.9762C88.7556 11.7065 88.1315 12.2642 87.3215 12.6493C86.5115 13.0344 85.5156 13.2336 84.3471 13.2336H80.7354V18.7973H78V3.64648ZM84.3737 10.7505C84.9314 10.7505 85.4227 10.6575 85.8078 10.4849C86.2061 10.3123 86.5115 10.0467 86.724 9.70149C86.9364 9.35625 87.0427 8.93133 87.0427 8.42675C87.0427 7.6566 86.8169 7.08562 86.3655 6.71382C85.914 6.34202 85.2501 6.15612 84.387 6.15612H80.7487V10.7505H84.3737Z"
|
||||||
d="M70.2461 19.1147C69.1572 19.1147 68.1746 18.8491 67.2849 18.318C66.3953 17.7869 65.6782 17.0565 65.1604 16.1138C64.6425 15.1843 64.377 14.1485 64.377 13.0066C64.377 11.8912 64.6425 10.882 65.1737 9.97908C65.7048 9.06286 66.4218 8.34582 67.3248 7.82796C68.2277 7.29682 69.2236 7.03125 70.2992 7.03125C71.6536 7.03125 72.7822 7.33666 73.6719 7.93419C74.5616 8.53172 75.2122 9.36827 75.584 10.4438C75.9691 11.5194 76.0753 12.7676 75.916 14.1751L67.0061 14.1884C67.1389 14.7062 67.3779 15.1577 67.7364 15.5295C68.0949 15.9013 68.5066 16.1802 68.958 16.366C69.4095 16.5519 69.8477 16.6316 70.2461 16.6316C70.6975 16.6316 71.1092 16.5918 71.5075 16.4988C71.8926 16.4059 72.2378 16.2731 72.5432 16.0739C72.8486 15.8747 73.1142 15.649 73.3665 15.3967L73.4727 15.2639L75.5043 16.6051C74.9998 17.4416 74.296 18.0657 73.4063 18.4773C72.5167 18.9022 71.4677 19.1147 70.2461 19.1147ZM73.486 11.9576C73.3665 11.4663 73.1673 11.0281 72.8619 10.6563C72.5565 10.2845 72.1847 9.99236 71.7332 9.79318C71.2818 9.594 70.8037 9.48778 70.2992 9.48778C69.8211 9.48778 69.3564 9.594 68.8916 9.79318C68.4269 9.99236 68.0285 10.2845 67.6833 10.6563C67.3381 11.0281 67.1256 11.4663 67.0459 11.9576H73.486Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M92.168 3.36719H94.8104V18.8101H92.168V3.36719Z"
|
||||||
d="M78 3.64648H84.3206C86.0733 3.64648 87.4278 4.0714 88.3838 4.92122C89.3399 5.77105 89.8179 6.93956 89.8179 8.42675C89.8179 9.39608 89.6054 10.2459 89.1805 10.9762C88.7556 11.7065 88.1315 12.2642 87.3215 12.6493C86.5115 13.0344 85.5156 13.2336 84.3471 13.2336H80.7354V18.7973H78V3.64648ZM84.3737 10.7505C84.9314 10.7505 85.4227 10.6575 85.8078 10.4849C86.2061 10.3123 86.5115 10.0467 86.724 9.70149C86.9364 9.35625 87.0427 8.93133 87.0427 8.42675C87.0427 7.6566 86.8169 7.08562 86.3655 6.71382C85.914 6.34202 85.2501 6.15612 84.387 6.15612H80.7487V10.7505H84.3737Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M101.982 19.1155C101.158 19.1155 100.415 18.9164 99.764 18.5313C99.1134 18.1462 98.5955 17.6018 98.2237 16.9113C97.8519 16.2208 97.666 15.4507 97.666 14.6008V7.32422H100.282V13.6979C100.282 14.2822 100.375 14.7735 100.574 15.1984C100.773 15.6233 101.052 15.9553 101.424 16.1943C101.796 16.4333 102.247 16.5528 102.765 16.5528C103.256 16.5528 103.708 16.4333 104.106 16.181C104.504 15.942 104.836 15.5835 105.075 15.1187C105.314 14.654 105.434 14.1228 105.434 13.512V7.32422H108.076V18.8101H105.527V17.3628C105.155 17.947 104.651 18.3719 104.04 18.6774C103.442 18.9562 102.752 19.1155 101.982 19.1155Z"
|
||||||
d="M92.168 3.36719H94.8104V18.8101H92.168V3.36719Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M115.287 19.1163C114.424 19.1163 113.535 18.9304 112.645 18.5586C111.755 18.1868 111.012 17.6424 110.414 16.9386L112.1 15.5045C112.525 15.8763 112.95 16.1817 113.375 16.4075C113.813 16.6332 114.358 16.766 115.048 16.8058C115.752 16.8457 116.35 16.766 116.828 16.5535C117.306 16.3411 117.558 16.0357 117.584 15.6107C117.598 15.3186 117.518 15.0929 117.345 14.9335C117.173 14.7742 116.907 14.6281 116.535 14.5219C116.164 14.4157 115.593 14.2962 114.809 14.1634L114.265 14.0572C113.123 13.8713 112.246 13.5127 111.622 12.9949C110.998 12.477 110.693 11.7334 110.693 10.7906C110.693 10.0072 110.905 9.34329 111.317 8.77231C111.742 8.21461 112.286 7.7897 112.99 7.51085C113.681 7.232 114.451 7.08594 115.314 7.08594C116.19 7.08594 117.053 7.24528 117.863 7.56396C118.687 7.88265 119.364 8.40051 119.882 9.10427L118.209 10.5118C117.903 10.1267 117.505 9.83459 117 9.63541C116.496 9.43624 115.951 9.34329 115.354 9.34329C114.969 9.34329 114.597 9.38312 114.252 9.47607C113.906 9.56902 113.614 9.70181 113.388 9.90098C113.163 10.1002 113.043 10.3392 113.043 10.6446C113.043 10.9102 113.123 11.1093 113.269 11.2554C113.415 11.4015 113.694 11.5342 114.079 11.6405C114.464 11.7467 115.128 11.8928 116.044 12.0654H116.084C117.226 12.2778 118.169 12.6762 118.899 13.2339C119.629 13.7916 120.001 14.5617 120.001 15.5045C120.001 16.2348 119.775 16.8855 119.324 17.4299C118.872 17.9743 118.288 18.3992 117.558 18.6914C116.841 18.9702 116.084 19.1163 115.287 19.1163Z"
|
||||||
d="M101.982 19.1155C101.158 19.1155 100.415 18.9164 99.764 18.5313C99.1134 18.1462 98.5955 17.6018 98.2237 16.9113C97.8519 16.2208 97.666 15.4507 97.666 14.6008V7.32422H100.282V13.6979C100.282 14.2822 100.375 14.7735 100.574 15.1984C100.773 15.6233 101.052 15.9553 101.424 16.1943C101.796 16.4333 102.247 16.5528 102.765 16.5528C103.256 16.5528 103.708 16.4333 104.106 16.181C104.504 15.942 104.836 15.5835 105.075 15.1187C105.314 14.654 105.434 14.1228 105.434 13.512V7.32422H108.076V18.8101H105.527V17.3628C105.155 17.947 104.651 18.3719 104.04 18.6774C103.442 18.9562 102.752 19.1155 101.982 19.1155Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
<path
|
||||||
<path
|
d="M14.7392 9.80759C14.5931 9.92709 14.3806 9.83414 14.3806 9.63497V8.58596V2.23883C14.3806 2.05293 14.1549 1.9467 14.0221 2.06621L5.17862 9.56857C5.03256 9.68808 4.8201 9.59513 4.8201 9.39595V4.27044C4.8201 4.12438 4.70059 4.00487 4.55453 4.00487H0.26557C0.119507 4.00487 0 4.12438 0 4.27044V20.3242C0 20.5101 0.225735 20.6163 0.35852 20.4968L9.18873 12.9944C9.33479 12.8749 9.54725 12.9679 9.54725 13.1671V20.5765C9.54725 20.7624 9.77299 20.8686 9.90577 20.7491L18.736 13.2467C18.882 13.1272 19.0945 13.2202 19.0945 13.4193V18.5449C19.0945 18.6909 19.214 18.8104 19.3601 18.8104H23.649C23.7951 18.8104 23.9146 18.6909 23.9146 18.5449V2.47784C23.9146 2.29194 23.6889 2.18572 23.5561 2.30522L14.7392 9.80759Z"
|
||||||
d="M115.287 19.1163C114.424 19.1163 113.535 18.9304 112.645 18.5586C111.755 18.1868 111.012 17.6424 110.414 16.9386L112.1 15.5045C112.525 15.8763 112.95 16.1817 113.375 16.4075C113.813 16.6332 114.358 16.766 115.048 16.8058C115.752 16.8457 116.35 16.766 116.828 16.5535C117.306 16.3411 117.558 16.0357 117.584 15.6107C117.598 15.3186 117.518 15.0929 117.345 14.9335C117.173 14.7742 116.907 14.6281 116.535 14.5219C116.164 14.4157 115.593 14.2962 114.809 14.1634L114.265 14.0572C113.123 13.8713 112.246 13.5127 111.622 12.9949C110.998 12.477 110.693 11.7334 110.693 10.7906C110.693 10.0072 110.905 9.34329 111.317 8.77231C111.742 8.21461 112.286 7.7897 112.99 7.51085C113.681 7.232 114.451 7.08594 115.314 7.08594C116.19 7.08594 117.053 7.24528 117.863 7.56396C118.687 7.88265 119.364 8.40051 119.882 9.10427L118.209 10.5118C117.903 10.1267 117.505 9.83459 117 9.63541C116.496 9.43624 115.951 9.34329 115.354 9.34329C114.969 9.34329 114.597 9.38312 114.252 9.47607C113.906 9.56902 113.614 9.70181 113.388 9.90098C113.163 10.1002 113.043 10.3392 113.043 10.6446C113.043 10.9102 113.123 11.1093 113.269 11.2554C113.415 11.4015 113.694 11.5342 114.079 11.6405C114.464 11.7467 115.128 11.8928 116.044 12.0654H116.084C117.226 12.2778 118.169 12.6762 118.899 13.2339C119.629 13.7916 120.001 14.5617 120.001 15.5045C120.001 16.2348 119.775 16.8855 119.324 17.4299C118.872 17.9743 118.288 18.3992 117.558 18.6914C116.841 18.9702 116.084 19.1163 115.287 19.1163Z"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
/>
|
||||||
/>
|
</svg>
|
||||||
<path
|
);
|
||||||
d="M14.7392 9.80759C14.5931 9.92709 14.3806 9.83414 14.3806 9.63497V8.58596V2.23883C14.3806 2.05293 14.1549 1.9467 14.0221 2.06621L5.17862 9.56857C5.03256 9.68808 4.8201 9.59513 4.8201 9.39595V4.27044C4.8201 4.12438 4.70059 4.00487 4.55453 4.00487H0.26557C0.119507 4.00487 0 4.12438 0 4.27044V20.3242C0 20.5101 0.225735 20.6163 0.35852 20.4968L9.18873 12.9944C9.33479 12.8749 9.54725 12.9679 9.54725 13.1671V20.5765C9.54725 20.7624 9.77299 20.8686 9.90577 20.7491L18.736 13.2467C18.882 13.1272 19.0945 13.2202 19.0945 13.4193V18.5449C19.0945 18.6909 19.214 18.8104 19.3601 18.8104H23.649C23.7951 18.8104 23.9146 18.6909 23.9146 18.5449V2.47784C23.9146 2.29194 23.6889 2.18572 23.5561 2.30522L14.7392 9.80759Z"
|
}
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,163 +1,163 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { getBlogRoute, normalizeTagSlug, type BlogPost } from "@/core/blog";
|
import { getBlogRoute, normalizeTagSlug, type BlogPost } from "@/core/blog";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type PostListProps = {
|
type PostListProps = {
|
||||||
description?: string;
|
description?: string;
|
||||||
posts: BlogPost[];
|
posts: BlogPost[];
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PostMetaProps = {
|
type PostMetaProps = {
|
||||||
currentLang?: string;
|
currentLang?: string;
|
||||||
date?: string | null;
|
date?: string | null;
|
||||||
languages?: string[];
|
languages?: string[];
|
||||||
pathname?: string;
|
pathname?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatDate(date?: string): string | null {
|
function formatDate(date?: string): string | null {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = new Date(date);
|
const value = new Date(date);
|
||||||
if (Number.isNaN(value.getTime())) {
|
if (Number.isNaN(value.getTime())) {
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Intl.DateTimeFormat("en-US", {
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}).format(value);
|
}).format(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostMeta({
|
export function PostMeta({
|
||||||
currentLang,
|
currentLang,
|
||||||
date,
|
date,
|
||||||
languages,
|
languages,
|
||||||
pathname,
|
pathname,
|
||||||
}: PostMetaProps) {
|
}: PostMetaProps) {
|
||||||
const formattedDate = formatDate(date ?? undefined);
|
const formattedDate = formatDate(date ?? undefined);
|
||||||
const availableLanguages = Array.isArray(languages)
|
const availableLanguages = Array.isArray(languages)
|
||||||
? languages.filter((lang): lang is string => typeof lang === "string")
|
? languages.filter((lang): lang is string => typeof lang === "string")
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (!formattedDate && availableLanguages.length <= 1) {
|
if (!formattedDate && availableLanguages.length <= 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-8 text-sm">
|
<div className="flex flex-wrap items-center gap-8 text-sm">
|
||||||
{formattedDate ? (
|
{formattedDate ? (
|
||||||
<p className="text-muted-foreground">{formattedDate}</p>
|
<p className="text-muted-foreground">{formattedDate}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{pathname && availableLanguages.length > 1 ? (
|
{pathname && availableLanguages.length > 1 ? (
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<span className="text-secondary-foreground text-sm">Language:</span>
|
<span className="text-secondary-foreground text-sm">Language:</span>
|
||||||
{availableLanguages.map((lang) => {
|
{availableLanguages.map((lang) => {
|
||||||
const isActive = lang === currentLang;
|
const isActive = lang === currentLang;
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={lang}
|
key={lang}
|
||||||
href={`${pathname}?lang=${lang}`}
|
href={`${pathname}?lang=${lang}`}
|
||||||
className={
|
className={
|
||||||
isActive
|
isActive
|
||||||
? "text-foreground text-sm font-medium"
|
? "text-foreground text-sm font-medium"
|
||||||
: "text-muted-foreground hover:text-foreground text-sm transition-colors"
|
: "text-muted-foreground hover:text-foreground text-sm transition-colors"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{lang.toUpperCase()}
|
{lang.toUpperCase()}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostTags({
|
export function PostTags({
|
||||||
tags,
|
tags,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
tags?: unknown;
|
tags?: unknown;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
if (!Array.isArray(tags)) {
|
if (!Array.isArray(tags)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validTags = tags.filter(
|
const validTags = tags.filter(
|
||||||
(tag): tag is string => typeof tag === "string" && tag.length > 0,
|
(tag): tag is string => typeof tag === "string" && tag.length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (validTags.length === 0) {
|
if (validTags.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-wrap items-center gap-3", className)}>
|
<div className={cn("flex flex-wrap items-center gap-3", className)}>
|
||||||
<span className="text-secondary-foreground text-sm">Tags:</span>
|
<span className="text-secondary-foreground text-sm">Tags:</span>
|
||||||
{validTags.map((tag) => (
|
{validTags.map((tag) => (
|
||||||
<Link
|
<Link
|
||||||
key={tag}
|
key={tag}
|
||||||
href={`/blog/tags/${normalizeTagSlug(tag)}`}
|
href={`/blog/tags/${normalizeTagSlug(tag)}`}
|
||||||
className="border-border text-secondary-foreground hover:text-foreground rounded-xl border px-2 py-1 text-sm transition-colors"
|
className="border-border text-secondary-foreground hover:text-foreground rounded-xl border px-2 py-1 text-sm transition-colors"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostList({ description, posts, title }: PostListProps) {
|
export function PostList({ description, posts, title }: PostListProps) {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-12 px-6">
|
<div className="mx-auto flex w-full max-w-5xl flex-col gap-12 px-6">
|
||||||
<header className="space-y-4">
|
<header className="space-y-4">
|
||||||
<h2 className="text-foreground text-4xl font-semibold tracking-tight">
|
<h2 className="text-foreground text-4xl font-semibold tracking-tight">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="text-secondary-foreground">{description}</p>
|
<p className="text-secondary-foreground">{description}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="space-y-12">
|
<div className="space-y-12">
|
||||||
{posts.map((post) => {
|
{posts.map((post) => {
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
key={post.slug.join("/")}
|
key={post.slug.join("/")}
|
||||||
className="border-border space-y-5 border-b pb-12 last:border-b-0 last:pb-0"
|
className="border-border space-y-5 border-b pb-12 last:border-b-0 last:pb-0"
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<PostMeta
|
<PostMeta
|
||||||
currentLang={post.lang}
|
currentLang={post.lang}
|
||||||
date={post.metadata.date}
|
date={post.metadata.date}
|
||||||
languages={post.languages}
|
languages={post.languages}
|
||||||
pathname={getBlogRoute(post.slug)}
|
pathname={getBlogRoute(post.slug)}
|
||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
href={getBlogRoute(post.slug)}
|
href={getBlogRoute(post.slug)}
|
||||||
className="text-foreground hover:text-primary block text-2xl font-semibold tracking-tight transition-colors"
|
className="text-foreground hover:text-primary block text-2xl font-semibold tracking-tight transition-colors"
|
||||||
>
|
>
|
||||||
{post.title}
|
{post.title}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{post.metadata.description ? (
|
{post.metadata.description ? (
|
||||||
<p className="text-secondary-foreground leading-10">
|
<p className="text-secondary-foreground leading-10">
|
||||||
{post.metadata.description}
|
{post.metadata.description}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<PostTags tags={post.metadata.tags} />
|
<PostTags tags={post.metadata.tags} />
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,29 +1,29 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function Section({
|
export function Section({
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
title: React.ReactNode;
|
title: React.ReactNode;
|
||||||
subtitle?: React.ReactNode;
|
subtitle?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<section className={cn("mx-auto flex flex-col py-16", className)}>
|
<section className={cn("mx-auto flex flex-col py-16", className)}>
|
||||||
<header className="flex flex-col items-center justify-between">
|
<header className="flex flex-col items-center justify-between">
|
||||||
<div className="mb-4 bg-linear-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-center text-5xl font-bold text-transparent">
|
<div className="mb-4 bg-linear-to-r from-white via-gray-200 to-gray-400 bg-clip-text text-center text-5xl font-bold text-transparent">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div className="text-muted-foreground text-center text-xl">
|
<div className="text-muted-foreground text-center text-xl">
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
<main className="mt-4">{children}</main>
|
<main className="mt-4">{children}</main>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,99 +1,99 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { pathOfThread } from "@/core/threads/utils";
|
import { pathOfThread } from "@/core/threads/utils";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { Section } from "../section";
|
import { Section } from "../section";
|
||||||
|
|
||||||
export function CaseStudySection({ className }: { className?: string }) {
|
export function CaseStudySection({ className }: { className?: string }) {
|
||||||
const caseStudies = [
|
const caseStudies = [
|
||||||
{
|
{
|
||||||
threadId: "7cfa5f8f-a2f8-47ad-acbd-da7137baf990",
|
threadId: "7cfa5f8f-a2f8-47ad-acbd-da7137baf990",
|
||||||
title: "Forecast 2026 Agent Trends and Opportunities",
|
title: "Forecast 2026 Agent Trends and Opportunities",
|
||||||
description:
|
description:
|
||||||
"Create a webpage with a Deep Research report forecasting the agent technology trends and opportunities in 2026.",
|
"Create a webpage with a Deep Research report forecasting the agent technology trends and opportunities in 2026.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: "4f3e55ee-f853-43db-bfb3-7d1a411f03cb",
|
threadId: "4f3e55ee-f853-43db-bfb3-7d1a411f03cb",
|
||||||
title: 'Generate a Video Based On the Novel "Pride and Prejudice"',
|
title: 'Generate a Video Based On the Novel "Pride and Prejudice"',
|
||||||
description:
|
description:
|
||||||
'Search the specific scene from the novel "Pride and Prejudice", then generate a video as well as a reference image based on the scenes.',
|
'Search the specific scene from the novel "Pride and Prejudice", then generate a video as well as a reference image based on the scenes.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: "21cfea46-34bd-4aa6-9e1f-3009452fbeb9",
|
threadId: "21cfea46-34bd-4aa6-9e1f-3009452fbeb9",
|
||||||
title: "Doraemon Explains the MOE Architecture",
|
title: "Doraemon Explains the MOE Architecture",
|
||||||
description:
|
description:
|
||||||
"Generate a Doraemon comic strip explaining the MOE architecture to the teenagers who are interested in AI.",
|
"Generate a Doraemon comic strip explaining the MOE architecture to the teenagers who are interested in AI.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: "ad76c455-5bf9-4335-8517-fc03834ab828",
|
threadId: "ad76c455-5bf9-4335-8517-fc03834ab828",
|
||||||
title: "An Exploratory Data Analysis of the Titanic Dataset",
|
title: "An Exploratory Data Analysis of the Titanic Dataset",
|
||||||
description:
|
description:
|
||||||
"Explore the Titanic dataset and identify the key factors that influenced survival rates with visualizations and insights.",
|
"Explore the Titanic dataset and identify the key factors that influenced survival rates with visualizations and insights.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: "d3e5adaf-084c-4dd5-9d29-94f1d6bccd98",
|
threadId: "d3e5adaf-084c-4dd5-9d29-94f1d6bccd98",
|
||||||
title: "Watch Y Combinator's Video then Conduct a Deep Research",
|
title: "Watch Y Combinator's Video then Conduct a Deep Research",
|
||||||
description:
|
description:
|
||||||
"Watch the given Y Combinator's YouTube video and conduct a deep research on the YC's tips for technical startup founders.",
|
"Watch the given Y Combinator's YouTube video and conduct a deep research on the YC's tips for technical startup founders.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: "3823e443-4e2b-4679-b496-a9506eae462b",
|
threadId: "3823e443-4e2b-4679-b496-a9506eae462b",
|
||||||
title: "Collect and Summarize Dr. Fei Fei Li's Podcasts",
|
title: "Collect and Summarize Dr. Fei Fei Li's Podcasts",
|
||||||
description:
|
description:
|
||||||
"Collect all the podcast appearances of Dr. Fei Fei Li in the last 6 months, then summarize them into a comprehensive report.",
|
"Collect all the podcast appearances of Dr. Fei Fei Li in the last 6 months, then summarize them into a comprehensive report.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
className={className}
|
className={className}
|
||||||
title="Case Studies"
|
title="Case Studies"
|
||||||
subtitle="CM 助手"
|
subtitle="CM 助手"
|
||||||
>
|
>
|
||||||
<div className="container-md mt-8 grid grid-cols-1 gap-4 px-4 md:grid-cols-2 md:px-20 lg:grid-cols-3">
|
<div className="container-md mt-8 grid grid-cols-1 gap-4 px-4 md:grid-cols-2 md:px-20 lg:grid-cols-3">
|
||||||
{caseStudies.map((caseStudy) => (
|
{caseStudies.map((caseStudy) => (
|
||||||
<Link
|
<Link
|
||||||
key={caseStudy.title}
|
key={caseStudy.title}
|
||||||
href={pathOfThread(caseStudy.threadId) + "?mock=true"}
|
href={pathOfThread(caseStudy.threadId) + "?mock=true"}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Card className="group/card relative h-64 overflow-hidden">
|
<Card className="group/card relative h-64 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 z-0 bg-cover bg-center bg-no-repeat transition-all duration-300 group-hover/card:scale-110 group-hover/card:brightness-90"
|
className="absolute inset-0 z-0 bg-cover bg-center bg-no-repeat transition-all duration-300 group-hover/card:scale-110 group-hover/card:brightness-90"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `url(/images/${caseStudy.threadId}.jpg)`,
|
backgroundImage: `url(/images/${caseStudy.threadId}.jpg)`,
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full translate-y-[calc(100%-60px)] flex-col items-center",
|
"flex h-full w-full translate-y-[calc(100%-60px)] flex-col items-center",
|
||||||
"transition-all duration-300",
|
"transition-all duration-300",
|
||||||
"group-hover/card:translate-y-[calc(100%-128px)]",
|
"group-hover/card:translate-y-[calc(100%-128px)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex w-full flex-col p-4"
|
className="flex w-full flex-col p-4"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
"linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%)",
|
"linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h3 className="flex h-14 items-center text-xl font-bold text-shadow-black">
|
<h3 className="flex h-14 items-center text-xl font-bold text-shadow-black">
|
||||||
{caseStudy.title}
|
{caseStudy.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="box-shadow-black overflow-hidden text-sm text-white/85 text-shadow-black">
|
<p className="box-shadow-black overflow-hidden text-sm text-white/85 text-shadow-black">
|
||||||
{caseStudy.description}
|
{caseStudy.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AuroraText } from "@/components/ui/aurora-text";
|
import { AuroraText } from "@/components/ui/aurora-text";
|
||||||
|
|
||||||
import { Section } from "../section";
|
import { Section } from "../section";
|
||||||
|
|
||||||
export function CommunitySection() {
|
export function CommunitySection() {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
title={
|
title={
|
||||||
<AuroraText colors={["#60A5FA", "#A5FA60", "#A560FA"]}>
|
<AuroraText colors={["#60A5FA", "#A5FA60", "#A560FA"]}>
|
||||||
Join the Community
|
Join the Community
|
||||||
</AuroraText>
|
</AuroraText>
|
||||||
}
|
}
|
||||||
subtitle="Contribute brilliant ideas to shape the future of CM Assistant. Collaborate, innovate, and make impacts."
|
subtitle="Contribute brilliant ideas to shape the future of CM Assistant. Collaborate, innovate, and make impacts."
|
||||||
>
|
>
|
||||||
<div />
|
<div />
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,126 +1,126 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnimatedSpan,
|
AnimatedSpan,
|
||||||
Terminal,
|
Terminal,
|
||||||
TypingAnimation,
|
TypingAnimation,
|
||||||
} from "@/components/ui/terminal";
|
} from "@/components/ui/terminal";
|
||||||
|
|
||||||
import { Section } from "../section";
|
import { Section } from "../section";
|
||||||
|
|
||||||
export function SandboxSection({ className }: { className?: string }) {
|
export function SandboxSection({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
className={className}
|
className={className}
|
||||||
title="Agent Runtime Environment"
|
title="Agent Runtime Environment"
|
||||||
subtitle={
|
subtitle={
|
||||||
<p>
|
<p>
|
||||||
We give CM Assistant a "computer", which can execute commands,
|
We give CM Assistant a "computer", which can execute commands,
|
||||||
manage files, and run long tasks — all in a secure Docker-based
|
manage files, and run long tasks — all in a secure Docker-based
|
||||||
sandbox
|
sandbox
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="mt-8 flex w-full max-w-6xl flex-col items-center gap-12 lg:flex-row lg:gap-16">
|
<div className="mt-8 flex w-full max-w-6xl flex-col items-center gap-12 lg:flex-row lg:gap-16">
|
||||||
{/* Left: Terminal */}
|
{/* Left: Terminal */}
|
||||||
<div className="w-full flex-1">
|
<div className="w-full flex-1">
|
||||||
<Terminal className="h-[360px] w-full">
|
<Terminal className="h-[360px] w-full">
|
||||||
{/* Scene 1: Build a Game */}
|
{/* Scene 1: Build a Game */}
|
||||||
<TypingAnimation>$ cat requirements.txt</TypingAnimation>
|
<TypingAnimation>$ cat requirements.txt</TypingAnimation>
|
||||||
<AnimatedSpan delay={800} className="text-zinc-400">
|
<AnimatedSpan delay={800} className="text-zinc-400">
|
||||||
pygame==2.5.0
|
pygame==2.5.0
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
|
|
||||||
<TypingAnimation delay={1200}>
|
<TypingAnimation delay={1200}>
|
||||||
$ pip install -r requirements.txt
|
$ pip install -r requirements.txt
|
||||||
</TypingAnimation>
|
</TypingAnimation>
|
||||||
<AnimatedSpan delay={2000} className="text-green-500">
|
<AnimatedSpan delay={2000} className="text-green-500">
|
||||||
[ok] Installed pygame
|
[ok] Installed pygame
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
|
|
||||||
<TypingAnimation delay={2400}>
|
<TypingAnimation delay={2400}>
|
||||||
$ write game.py --lines 156
|
$ write game.py --lines 156
|
||||||
</TypingAnimation>
|
</TypingAnimation>
|
||||||
<AnimatedSpan delay={3200} className="text-blue-500">
|
<AnimatedSpan delay={3200} className="text-blue-500">
|
||||||
[ok] Written 156 lines
|
[ok] Written 156 lines
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
|
|
||||||
<TypingAnimation delay={3600}>
|
<TypingAnimation delay={3600}>
|
||||||
$ python game.py --test
|
$ python game.py --test
|
||||||
</TypingAnimation>
|
</TypingAnimation>
|
||||||
<AnimatedSpan delay={4200} className="text-green-500">
|
<AnimatedSpan delay={4200} className="text-green-500">
|
||||||
[ok] All sprites loaded
|
[ok] All sprites loaded
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
<AnimatedSpan delay={4500} className="text-green-500">
|
<AnimatedSpan delay={4500} className="text-green-500">
|
||||||
[ok] Physics engine OK
|
[ok] Physics engine OK
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
<AnimatedSpan delay={4800} className="text-green-500">
|
<AnimatedSpan delay={4800} className="text-green-500">
|
||||||
[ok] 60 FPS stable
|
[ok] 60 FPS stable
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
|
|
||||||
{/* Scene 2: Data Analysis */}
|
{/* Scene 2: Data Analysis */}
|
||||||
<TypingAnimation delay={5400}>
|
<TypingAnimation delay={5400}>
|
||||||
$ curl -O sales-2024.csv
|
$ curl -O sales-2024.csv
|
||||||
</TypingAnimation>
|
</TypingAnimation>
|
||||||
<AnimatedSpan delay={6200} className="text-zinc-400">
|
<AnimatedSpan delay={6200} className="text-zinc-400">
|
||||||
Downloaded 12.4 MB
|
Downloaded 12.4 MB
|
||||||
</AnimatedSpan>
|
</AnimatedSpan>
|
||||||
</Terminal>
|
</Terminal>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Description */}
|
{/* Right: Description */}
|
||||||
<div className="w-full flex-1 space-y-6">
|
<div className="w-full flex-1 space-y-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm font-medium tracking-wider text-purple-400 uppercase">
|
<p className="text-sm font-medium tracking-wider text-purple-400 uppercase">
|
||||||
Open-source
|
Open-source
|
||||||
</p>
|
</p>
|
||||||
<h2 className="text-4xl font-bold tracking-tight lg:text-5xl">
|
<h2 className="text-4xl font-bold tracking-tight lg:text-5xl">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/agent-infra/sandbox"
|
href="https://github.com/agent-infra/sandbox"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
AIO Sandbox
|
AIO Sandbox
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 text-lg text-zinc-400">
|
<div className="space-y-4 text-lg text-zinc-400">
|
||||||
<p>
|
<p>
|
||||||
We recommend using{" "}
|
We recommend using{" "}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/agent-infra/sandbox"
|
href="https://github.com/agent-infra/sandbox"
|
||||||
className="underline"
|
className="underline"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
All-in-One Sandbox
|
All-in-One Sandbox
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
that combines Browser, Shell, File, MCP and VSCode Server in a
|
that combines Browser, Shell, File, MCP and VSCode Server in a
|
||||||
single Docker container.
|
single Docker container.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Feature Tags */}
|
{/* Feature Tags */}
|
||||||
<div className="flex flex-wrap gap-3 pt-4">
|
<div className="flex flex-wrap gap-3 pt-4">
|
||||||
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
||||||
Isolated
|
Isolated
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
||||||
Safe
|
Safe
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
||||||
Persistent
|
Persistent
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
||||||
Mountable FS
|
Mountable FS
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
<span className="rounded-full border border-zinc-800 bg-zinc-900 px-4 py-2 text-sm text-zinc-300">
|
||||||
Long-running
|
Long-running
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import ProgressiveSkillsAnimation from "../progressive-skills-animation";
|
import ProgressiveSkillsAnimation from "../progressive-skills-animation";
|
||||||
import { Section } from "../section";
|
import { Section } from "../section";
|
||||||
|
|
||||||
export function SkillsSection({ className }: { className?: string }) {
|
export function SkillsSection({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
className={cn("h-[calc(100vh-64px)] w-full bg-white/2", className)}
|
className={cn("h-[calc(100vh-64px)] w-full bg-white/2", className)}
|
||||||
title="Agent Skills"
|
title="Agent Skills"
|
||||||
subtitle={
|
subtitle={
|
||||||
<div>
|
<div>
|
||||||
Agent Skills are loaded progressively — only what's needed, when
|
Agent Skills are loaded progressively — only what's needed, when
|
||||||
it's needed.
|
it's needed.
|
||||||
<br />
|
<br />
|
||||||
Extend CM Assistant with your own skill files, or use our built-in
|
Extend CM Assistant with your own skill files, or use our built-in
|
||||||
library.
|
library.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="relative overflow-hidden">
|
<div className="relative overflow-hidden">
|
||||||
<ProgressiveSkillsAnimation />
|
<ProgressiveSkillsAnimation />
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import MagicBento, { type BentoCardProps } from "@/components/ui/magic-bento";
|
import MagicBento, { type BentoCardProps } from "@/components/ui/magic-bento";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { Section } from "../section";
|
import { Section } from "../section";
|
||||||
|
|
||||||
const COLOR = "#0a0a0a";
|
const COLOR = "#0a0a0a";
|
||||||
const features: BentoCardProps[] = [
|
const features: BentoCardProps[] = [
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Context Engineering",
|
label: "Context Engineering",
|
||||||
title: "Long/Short-term Memory",
|
title: "Long/Short-term Memory",
|
||||||
description: "Now the agent can better understand you",
|
description: "Now the agent can better understand you",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Long Task Running",
|
label: "Long Task Running",
|
||||||
title: "Planning and Sub-tasking",
|
title: "Planning and Sub-tasking",
|
||||||
description:
|
description:
|
||||||
"Plans ahead, reasons through complexity, then executes sequentially or in parallel",
|
"Plans ahead, reasons through complexity, then executes sequentially or in parallel",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Extensible",
|
label: "Extensible",
|
||||||
title: "Skills and Tools",
|
title: "Skills and Tools",
|
||||||
description:
|
description:
|
||||||
"Plug, play, or even swap built-in tools. Build the agent you want.",
|
"Plug, play, or even swap built-in tools. Build the agent you want.",
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Persistent",
|
label: "Persistent",
|
||||||
title: "Sandbox with File System",
|
title: "Sandbox with File System",
|
||||||
description: "Read, write, run — like a real computer",
|
description: "Read, write, run — like a real computer",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Flexible",
|
label: "Flexible",
|
||||||
title: "Multi-Model Support",
|
title: "Multi-Model Support",
|
||||||
description: "Doubao, DeepSeek, OpenAI, Gemini, etc.",
|
description: "Doubao, DeepSeek, OpenAI, Gemini, etc.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
color: COLOR,
|
color: COLOR,
|
||||||
label: "Free",
|
label: "Free",
|
||||||
title: "Open Source",
|
title: "Open Source",
|
||||||
description: "MIT License, self-hosted, full control",
|
description: "MIT License, self-hosted, full control",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function WhatsNewSection({ className }: { className?: string }) {
|
export function WhatsNewSection({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
className={cn("", className)}
|
className={cn("", className)}
|
||||||
title="CM 助手"
|
title="CM 助手"
|
||||||
subtitle="CM 助手"
|
subtitle="CM 助手"
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-center">
|
<div className="flex w-full items-center justify-center">
|
||||||
<MagicBento data={features} />
|
<MagicBento data={features} />
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ type PageSidebarProps = {
|
|||||||
headerHidden?: boolean;
|
headerHidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "encoder" | "about";
|
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem, pathname: string): boolean =>
|
const hasActiveChild = (item: MenuItem, pathname: string): boolean =>
|
||||||
menuItemContainsPath(item, pathname);
|
menuItemContainsPath(item, pathname);
|
||||||
@@ -270,12 +270,6 @@ function SecondaryNavSidebar({ headerHidden }: { headerHidden: boolean }) {
|
|||||||
模型配置
|
模型配置
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{isAdmin && (
|
|
||||||
<DropdownMenuItem onClick={() => openSettings("encoder")}>
|
|
||||||
<Cpu className="size-4" />
|
|
||||||
本地编码器
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => navigate("/page/strategy/settings/model-concurrency")}
|
onClick={() => navigate("/page/strategy/settings/model-concurrency")}
|
||||||
@@ -591,12 +585,6 @@ function LegacyAccordionSidebar({
|
|||||||
模型配置
|
模型配置
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{isAdmin && (
|
|
||||||
<DropdownMenuItem onClick={() => openSettings("encoder")}>
|
|
||||||
<Cpu className="size-4" />
|
|
||||||
本地编码器
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<DropdownMenuItem onClick={() => navigate("/page/strategy/admin/login-whitelist")}>
|
<DropdownMenuItem onClick={() => navigate("/page/strategy/admin/login-whitelist")}>
|
||||||
<ShieldCheck className="size-4" />
|
<ShieldCheck className="size-4" />
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
QueryClient,
|
QueryClient,
|
||||||
QueryClientProvider as TanStackQueryClientProvider,
|
QueryClientProvider as TanStackQueryClientProvider,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
export const queryClient = new QueryClient();
|
export const queryClient = new QueryClient();
|
||||||
|
|
||||||
export function QueryClientProvider({
|
export function QueryClientProvider({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<TanStackQueryClientProvider client={queryClient}>
|
<TanStackQueryClientProvider client={queryClient}>
|
||||||
{children}
|
{children}
|
||||||
</TanStackQueryClientProvider>
|
</TanStackQueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||||
|
|
||||||
export function ThemeProvider({
|
export function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
return (
|
return (
|
||||||
<NextThemesProvider
|
<NextThemesProvider
|
||||||
{...props}
|
{...props}
|
||||||
forcedTheme={pathname === "/landing" ? "dark" : undefined}
|
forcedTheme={pathname === "/landing" ? "dark" : undefined}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</NextThemesProvider>
|
</NextThemesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,66 +1,66 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-card text-card-foreground",
|
default: "bg-card text-card-foreground",
|
||||||
destructive:
|
destructive:
|
||||||
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function Alert({
|
function Alert({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="alert"
|
data-slot="alert"
|
||||||
role="alert"
|
role="alert"
|
||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="alert-title"
|
data-slot="alert-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AlertDescription({
|
function AlertDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div">) {
|
}: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="alert-description"
|
data-slot="alert-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Alert, AlertTitle, AlertDescription };
|
export { Alert, AlertTitle, AlertDescription };
|
||||||
@@ -1,43 +1,43 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { memo } from "react";
|
import React, { memo } from "react";
|
||||||
|
|
||||||
interface AuroraTextProps {
|
interface AuroraTextProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
colors?: string[];
|
colors?: string[];
|
||||||
speed?: number;
|
speed?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuroraText = memo(
|
export const AuroraText = memo(
|
||||||
({
|
({
|
||||||
children,
|
children,
|
||||||
className = "",
|
className = "",
|
||||||
colors = ["#FF0080", "#7928CA", "#0070F3", "#38bdf8"],
|
colors = ["#FF0080", "#7928CA", "#0070F3", "#38bdf8"],
|
||||||
speed = 1,
|
speed = 1,
|
||||||
}: AuroraTextProps) => {
|
}: AuroraTextProps) => {
|
||||||
const gradientStyle = {
|
const gradientStyle = {
|
||||||
backgroundImage: `linear-gradient(135deg, ${colors.join(", ")}, ${
|
backgroundImage: `linear-gradient(135deg, ${colors.join(", ")}, ${
|
||||||
colors[0]
|
colors[0]
|
||||||
})`,
|
})`,
|
||||||
WebkitBackgroundClip: "text",
|
WebkitBackgroundClip: "text",
|
||||||
WebkitTextFillColor: "transparent",
|
WebkitTextFillColor: "transparent",
|
||||||
animationDuration: `${10 / speed}s`,
|
animationDuration: `${10 / speed}s`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`relative inline-block ${className}`}>
|
<span className={`relative inline-block ${className}`}>
|
||||||
<span className="sr-only">{children}</span>
|
<span className="sr-only">{children}</span>
|
||||||
<span
|
<span
|
||||||
className="animate-aurora relative bg-size-[200%_auto] bg-clip-text text-transparent"
|
className="animate-aurora relative bg-size-[200%_auto] bg-clip-text text-transparent"
|
||||||
style={gradientStyle}
|
style={gradientStyle}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
AuroraText.displayName = "AuroraText";
|
AuroraText.displayName = "AuroraText";
|
||||||
@@ -1,53 +1,53 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Avatar({
|
function Avatar({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Root
|
<AvatarPrimitive.Root
|
||||||
data-slot="avatar"
|
data-slot="avatar"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarImage({
|
function AvatarImage({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Image
|
<AvatarPrimitive.Image
|
||||||
data-slot="avatar-image"
|
data-slot="avatar-image"
|
||||||
className={cn("aspect-square size-full", className)}
|
className={cn("aspect-square size-full", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AvatarFallback({
|
function AvatarFallback({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||||
return (
|
return (
|
||||||
<AvatarPrimitive.Fallback
|
<AvatarPrimitive.Fallback
|
||||||
data-slot="avatar-fallback"
|
data-slot="avatar-fallback"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback };
|
export { Avatar, AvatarImage, AvatarFallback };
|
||||||
@@ -1,46 +1,46 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||||
secondary:
|
secondary:
|
||||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||||
destructive:
|
destructive:
|
||||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
outline:
|
||||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function Badge({
|
function Badge({
|
||||||
className,
|
className,
|
||||||
variant,
|
variant,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span"> &
|
}: React.ComponentProps<"span"> &
|
||||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "span";
|
const Comp = asChild ? Slot : "span";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="badge"
|
data-slot="badge"
|
||||||
className={cn(badgeVariants({ variant }), className)}
|
className={cn(badgeVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Badge, badgeVariants };
|
export { Badge, badgeVariants };
|
||||||
@@ -1,36 +1,36 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||||
return (
|
return (
|
||||||
<ol
|
<ol
|
||||||
data-slot="breadcrumb-list"
|
data-slot="breadcrumb-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
data-slot="breadcrumb-item"
|
data-slot="breadcrumb-item"
|
||||||
className={cn("inline-flex items-center gap-1.5", className)}
|
className={cn("inline-flex items-center gap-1.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const BreadcrumbLink = React.forwardRef<
|
const BreadcrumbLink = React.forwardRef<
|
||||||
HTMLAnchorElement,
|
HTMLAnchorElement,
|
||||||
React.ComponentProps<"a"> & {
|
React.ComponentProps<"a"> & {
|
||||||
@@ -50,62 +50,62 @@ const BreadcrumbLink = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
BreadcrumbLink.displayName = "BreadcrumbLink";
|
BreadcrumbLink.displayName = "BreadcrumbLink";
|
||||||
|
|
||||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="breadcrumb-page"
|
data-slot="breadcrumb-page"
|
||||||
role="link"
|
role="link"
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
className={cn("text-foreground font-normal", className)}
|
className={cn("text-foreground font-normal", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbSeparator({
|
function BreadcrumbSeparator({
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"li">) {
|
}: React.ComponentProps<"li">) {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
data-slot="breadcrumb-separator"
|
data-slot="breadcrumb-separator"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn("[&>svg]:size-3.5", className)}
|
className={cn("[&>svg]:size-3.5", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children ?? <ChevronRight />}
|
{children ?? <ChevronRight />}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreadcrumbEllipsis({
|
function BreadcrumbEllipsis({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span">) {
|
}: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="breadcrumb-ellipsis"
|
data-slot="breadcrumb-ellipsis"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={cn("flex size-9 items-center justify-center", className)}
|
className={cn("flex size-9 items-center justify-center", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="size-4" />
|
<MoreHorizontal className="size-4" />
|
||||||
<span className="sr-only">More</span>
|
<span className="sr-only">More</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
};
|
};
|
||||||
@@ -1,83 +1,83 @@
|
|||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
const buttonGroupVariants = cva(
|
const buttonGroupVariants = cva(
|
||||||
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
orientation: {
|
orientation: {
|
||||||
horizontal:
|
horizontal:
|
||||||
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
||||||
vertical:
|
vertical:
|
||||||
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
orientation: "horizontal",
|
orientation: "horizontal",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function ButtonGroup({
|
function ButtonGroup({
|
||||||
className,
|
className,
|
||||||
orientation,
|
orientation,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
data-slot="button-group"
|
data-slot="button-group"
|
||||||
data-orientation={orientation}
|
data-orientation={orientation}
|
||||||
className={cn(buttonGroupVariants({ orientation }), className)}
|
className={cn(buttonGroupVariants({ orientation }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupText({
|
function ButtonGroupText({
|
||||||
className,
|
className,
|
||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const Comp = asChild ? Slot : "div";
|
const Comp = asChild ? Slot : "div";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ButtonGroupSeparator({
|
function ButtonGroupSeparator({
|
||||||
className,
|
className,
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Separator>) {
|
}: React.ComponentProps<typeof Separator>) {
|
||||||
return (
|
return (
|
||||||
<Separator
|
<Separator
|
||||||
data-slot="button-group-separator"
|
data-slot="button-group-separator"
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
ButtonGroupSeparator,
|
ButtonGroupSeparator,
|
||||||
ButtonGroupText,
|
ButtonGroupText,
|
||||||
buttonGroupVariants,
|
buttonGroupVariants,
|
||||||
};
|
};
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"cursor-pointer bg-primary text-[var(--button-foreground,var(--primary-foreground))] hover:bg-primary/90",
|
"cursor-pointer bg-primary text-[var(--button-foreground,var(--primary-foreground))] hover:bg-primary/90",
|
||||||
destructive:
|
destructive:
|
||||||
"cursor-pointer bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"cursor-pointer bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
outline:
|
||||||
"cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-[#213D5E] dark:bg-[#022248] dark:text-[#CCD2DA] dark:hover:border-[#213D5E] dark:hover:bg-[#0a2f58] dark:hover:text-[#CCD2DA] dark:[&_svg]:text-[#CCD2DA]",
|
"cursor-pointer border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-[#213D5E] dark:bg-[#022248] dark:text-[#CCD2DA] dark:hover:border-[#213D5E] dark:hover:bg-[#0a2f58] dark:hover:text-[#CCD2DA] dark:[&_svg]:text-[#CCD2DA]",
|
||||||
secondary:
|
secondary:
|
||||||
"cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"cursor-pointer bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost:
|
||||||
"cursor-pointer hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"cursor-pointer hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
||||||
link: "cursor-pointer text-primary underline-offset-4 hover:underline",
|
link: "cursor-pointer text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
"icon-sm": "size-8",
|
"icon-sm": "size-8",
|
||||||
"icon-lg": "size-10",
|
"icon-lg": "size-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const Button = React.forwardRef<
|
const Button = React.forwardRef<
|
||||||
HTMLButtonElement,
|
HTMLButtonElement,
|
||||||
React.ComponentProps<"button"> &
|
React.ComponentProps<"button"> &
|
||||||
VariantProps<typeof buttonVariants> & {
|
VariantProps<typeof buttonVariants> & {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => {
|
>(({ className, variant = "default", size = "default", asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button";
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="button"
|
data-slot="button"
|
||||||
{...(variant !== undefined && { "data-variant": variant })}
|
{...(variant !== undefined && { "data-variant": variant })}
|
||||||
{...(size !== undefined && { "data-size": size })}
|
{...(size !== undefined && { "data-size": size })}
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Button.displayName = "Button";
|
Button.displayName = "Button";
|
||||||
|
|
||||||
export { Button, buttonVariants };
|
export { Button, buttonVariants };
|
||||||
@@ -1,92 +1,92 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-[4px] border py-6 shadow-sm dark:border-[#0049A5] dark:bg-[#02244C]",
|
"bg-card text-card-foreground flex flex-col gap-6 rounded-[4px] border py-6 shadow-sm dark:border-[#0049A5] dark:bg-[#02244C]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-title"
|
data-slot="card-title"
|
||||||
className={cn("leading-none font-semibold", className)}
|
className={cn("leading-none font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-description"
|
data-slot="card-description"
|
||||||
className={cn("text-muted-foreground text-sm dark:text-[#F2F3F5]", className)}
|
className={cn("text-muted-foreground text-sm dark:text-[#F2F3F5]", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-action"
|
data-slot="card-action"
|
||||||
className={cn(
|
className={cn(
|
||||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-content"
|
data-slot="card-content"
|
||||||
className={cn("px-6 ", className)}
|
className={cn("px-6 ", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardAction,
|
CardAction,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent,
|
CardContent,
|
||||||
};
|
};
|
||||||
@@ -1,241 +1,241 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import useEmblaCarousel, {
|
import useEmblaCarousel, {
|
||||||
type UseEmblaCarouselType,
|
type UseEmblaCarouselType,
|
||||||
} from "embla-carousel-react";
|
} from "embla-carousel-react";
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1];
|
type CarouselApi = UseEmblaCarouselType[1];
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
|
||||||
type CarouselOptions = UseCarouselParameters[0];
|
type CarouselOptions = UseCarouselParameters[0];
|
||||||
type CarouselPlugin = UseCarouselParameters[1];
|
type CarouselPlugin = UseCarouselParameters[1];
|
||||||
|
|
||||||
type CarouselProps = {
|
type CarouselProps = {
|
||||||
opts?: CarouselOptions;
|
opts?: CarouselOptions;
|
||||||
plugins?: CarouselPlugin;
|
plugins?: CarouselPlugin;
|
||||||
orientation?: "horizontal" | "vertical";
|
orientation?: "horizontal" | "vertical";
|
||||||
setApi?: (api: CarouselApi) => void;
|
setApi?: (api: CarouselApi) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CarouselContextProps = {
|
type CarouselContextProps = {
|
||||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
|
||||||
api: ReturnType<typeof useEmblaCarousel>[1];
|
api: ReturnType<typeof useEmblaCarousel>[1];
|
||||||
scrollPrev: () => void;
|
scrollPrev: () => void;
|
||||||
scrollNext: () => void;
|
scrollNext: () => void;
|
||||||
canScrollPrev: boolean;
|
canScrollPrev: boolean;
|
||||||
canScrollNext: boolean;
|
canScrollNext: boolean;
|
||||||
} & CarouselProps;
|
} & CarouselProps;
|
||||||
|
|
||||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
|
||||||
|
|
||||||
function useCarousel() {
|
function useCarousel() {
|
||||||
const context = React.useContext(CarouselContext);
|
const context = React.useContext(CarouselContext);
|
||||||
|
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useCarousel must be used within a <Carousel />");
|
throw new Error("useCarousel must be used within a <Carousel />");
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Carousel({
|
function Carousel({
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
opts,
|
opts,
|
||||||
setApi,
|
setApi,
|
||||||
plugins,
|
plugins,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & CarouselProps) {
|
}: React.ComponentProps<"div"> & CarouselProps) {
|
||||||
const [carouselRef, api] = useEmblaCarousel(
|
const [carouselRef, api] = useEmblaCarousel(
|
||||||
{
|
{
|
||||||
...opts,
|
...opts,
|
||||||
axis: orientation === "horizontal" ? "x" : "y",
|
axis: orientation === "horizontal" ? "x" : "y",
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
);
|
);
|
||||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
||||||
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
||||||
|
|
||||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||||
if (!api) return;
|
if (!api) return;
|
||||||
setCanScrollPrev(api.canScrollPrev());
|
setCanScrollPrev(api.canScrollPrev());
|
||||||
setCanScrollNext(api.canScrollNext());
|
setCanScrollNext(api.canScrollNext());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const scrollPrev = React.useCallback(() => {
|
const scrollPrev = React.useCallback(() => {
|
||||||
api?.scrollPrev();
|
api?.scrollPrev();
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
const scrollNext = React.useCallback(() => {
|
const scrollNext = React.useCallback(() => {
|
||||||
api?.scrollNext();
|
api?.scrollNext();
|
||||||
}, [api]);
|
}, [api]);
|
||||||
|
|
||||||
const handleKeyDown = React.useCallback(
|
const handleKeyDown = React.useCallback(
|
||||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (event.key === "ArrowLeft") {
|
if (event.key === "ArrowLeft") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
scrollPrev();
|
scrollPrev();
|
||||||
} else if (event.key === "ArrowRight") {
|
} else if (event.key === "ArrowRight") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
scrollNext();
|
scrollNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollPrev, scrollNext],
|
[scrollPrev, scrollNext],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api || !setApi) return;
|
if (!api || !setApi) return;
|
||||||
setApi(api);
|
setApi(api);
|
||||||
}, [api, setApi]);
|
}, [api, setApi]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!api) return;
|
if (!api) return;
|
||||||
onSelect(api);
|
onSelect(api);
|
||||||
api.on("reInit", onSelect);
|
api.on("reInit", onSelect);
|
||||||
api.on("select", onSelect);
|
api.on("select", onSelect);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
api?.off("select", onSelect);
|
api?.off("select", onSelect);
|
||||||
};
|
};
|
||||||
}, [api, onSelect]);
|
}, [api, onSelect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarouselContext.Provider
|
<CarouselContext.Provider
|
||||||
value={{
|
value={{
|
||||||
carouselRef,
|
carouselRef,
|
||||||
api: api,
|
api: api,
|
||||||
opts,
|
opts,
|
||||||
orientation:
|
orientation:
|
||||||
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||||
scrollPrev,
|
scrollPrev,
|
||||||
scrollNext,
|
scrollNext,
|
||||||
canScrollPrev,
|
canScrollPrev,
|
||||||
canScrollNext,
|
canScrollNext,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onKeyDownCapture={handleKeyDown}
|
onKeyDownCapture={handleKeyDown}
|
||||||
className={cn("relative", className)}
|
className={cn("relative", className)}
|
||||||
role="region"
|
role="region"
|
||||||
aria-roledescription="carousel"
|
aria-roledescription="carousel"
|
||||||
data-slot="carousel"
|
data-slot="carousel"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</CarouselContext.Provider>
|
</CarouselContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { carouselRef, orientation } = useCarousel();
|
const { carouselRef, orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={carouselRef}
|
ref={carouselRef}
|
||||||
className="overflow-hidden"
|
className="overflow-hidden"
|
||||||
data-slot="carousel-content"
|
data-slot="carousel-content"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex",
|
"flex",
|
||||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
const { orientation } = useCarousel();
|
const { orientation } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
aria-roledescription="slide"
|
aria-roledescription="slide"
|
||||||
data-slot="carousel-item"
|
data-slot="carousel-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-w-0 shrink-0 grow-0 basis-full",
|
"min-w-0 shrink-0 grow-0 basis-full",
|
||||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselPrevious({
|
function CarouselPrevious({
|
||||||
className,
|
className,
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "icon",
|
size = "icon",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
data-slot="carousel-previous"
|
data-slot="carousel-previous"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute size-8 rounded-full",
|
"absolute size-8 rounded-full",
|
||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -left-12 -translate-y-1/2"
|
? "top-1/2 -left-12 -translate-y-1/2"
|
||||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollPrev}
|
disabled={!canScrollPrev}
|
||||||
onClick={scrollPrev}
|
onClick={scrollPrev}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
<span className="sr-only">Previous slide</span>
|
<span className="sr-only">Previous slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CarouselNext({
|
function CarouselNext({
|
||||||
className,
|
className,
|
||||||
variant = "outline",
|
variant = "outline",
|
||||||
size = "icon",
|
size = "icon",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Button>) {
|
}: React.ComponentProps<typeof Button>) {
|
||||||
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
const { orientation, scrollNext, canScrollNext } = useCarousel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
data-slot="carousel-next"
|
data-slot="carousel-next"
|
||||||
variant={variant}
|
variant={variant}
|
||||||
size={size}
|
size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute size-8 rounded-full",
|
"absolute size-8 rounded-full",
|
||||||
orientation === "horizontal"
|
orientation === "horizontal"
|
||||||
? "top-1/2 -right-12 -translate-y-1/2"
|
? "top-1/2 -right-12 -translate-y-1/2"
|
||||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
disabled={!canScrollNext}
|
disabled={!canScrollNext}
|
||||||
onClick={scrollNext}
|
onClick={scrollNext}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
<span className="sr-only">Next slide</span>
|
<span className="sr-only">Next slide</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type CarouselApi,
|
type CarouselApi,
|
||||||
Carousel,
|
Carousel,
|
||||||
CarouselContent,
|
CarouselContent,
|
||||||
CarouselItem,
|
CarouselItem,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
};
|
};
|
||||||
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Collapsible({
|
function Collapsible({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
|
||||||
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollapsibleTrigger = React.forwardRef<
|
const CollapsibleTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof CollapsiblePrimitive.CollapsibleTrigger>,
|
React.ElementRef<typeof CollapsiblePrimitive.CollapsibleTrigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleTrigger>
|
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleTrigger>
|
||||||
@@ -43,5 +43,5 @@ const CollapsibleContent = React.forwardRef<
|
|||||||
|
|
||||||
CollapsibleContent.displayName =
|
CollapsibleContent.displayName =
|
||||||
CollapsiblePrimitive.CollapsibleContent.displayName;
|
CollapsiblePrimitive.CollapsibleContent.displayName;
|
||||||
|
|
||||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||||
@@ -1,181 +1,181 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Command as CommandPrimitive } from "cmdk";
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
import { SearchIcon } from "lucide-react";
|
import { SearchIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
function Command({
|
function Command({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive
|
<CommandPrimitive
|
||||||
data-slot="command"
|
data-slot="command"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandDialog({
|
function CommandDialog({
|
||||||
title = "Command Palette",
|
title = "Command Palette",
|
||||||
description = "Search for a command to run...",
|
description = "Search for a command to run...",
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Dialog> & {
|
}: React.ComponentProps<typeof Dialog> & {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
showCloseButton?: boolean;
|
showCloseButton?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog {...props}>
|
<Dialog {...props}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn("overflow-hidden p-0", className)}
|
className={cn("overflow-hidden p-0", className)}
|
||||||
showCloseButton={showCloseButton}
|
showCloseButton={showCloseButton}
|
||||||
>
|
>
|
||||||
<DialogTitle className="sr-only">{title}</DialogTitle>
|
<DialogTitle className="sr-only">{title}</DialogTitle>
|
||||||
<DialogDescription className="sr-only">{description}</DialogDescription>
|
<DialogDescription className="sr-only">{description}</DialogDescription>
|
||||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
{children}
|
{children}
|
||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandInput({
|
function CommandInput({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="command-input-wrapper"
|
data-slot="command-input-wrapper"
|
||||||
className="flex h-9 items-center gap-2 border-b px-3"
|
className="flex h-9 items-center gap-2 border-b px-3"
|
||||||
>
|
>
|
||||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
data-slot="command-input"
|
data-slot="command-input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandList({
|
function CommandList({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.List
|
<CommandPrimitive.List
|
||||||
data-slot="command-list"
|
data-slot="command-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandEmpty({
|
function CommandEmpty({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Empty
|
<CommandPrimitive.Empty
|
||||||
data-slot="command-empty"
|
data-slot="command-empty"
|
||||||
className="py-6 text-center text-sm"
|
className="py-6 text-center text-sm"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandGroup({
|
function CommandGroup({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Group
|
<CommandPrimitive.Group
|
||||||
data-slot="command-group"
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandSeparator({
|
function CommandSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Separator
|
<CommandPrimitive.Separator
|
||||||
data-slot="command-separator"
|
data-slot="command-separator"
|
||||||
className={cn("bg-border -mx-1 h-px", className)}
|
className={cn("bg-border -mx-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandItem({
|
function CommandItem({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
return (
|
return (
|
||||||
<CommandPrimitive.Item
|
<CommandPrimitive.Item
|
||||||
data-slot="command-item"
|
data-slot="command-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function CommandShortcut({
|
function CommandShortcut({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span">) {
|
}: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="command-shortcut"
|
data-slot="command-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
CommandDialog,
|
CommandDialog,
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandList,
|
CommandList,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
};
|
};
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { type MouseEventHandler } from "react";
|
import React, { type MouseEventHandler } from "react";
|
||||||
import confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
interface ConfettiButtonProps extends React.ComponentProps<typeof Button> {
|
interface ConfettiButtonProps extends React.ComponentProps<typeof Button> {
|
||||||
angle?: number;
|
angle?: number;
|
||||||
particleCount?: number;
|
particleCount?: number;
|
||||||
startVelocity?: number;
|
startVelocity?: number;
|
||||||
spread?: number;
|
spread?: number;
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfettiButton({
|
export function ConfettiButton({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
angle = 90,
|
angle = 90,
|
||||||
particleCount = 75,
|
particleCount = 75,
|
||||||
startVelocity = 35,
|
startVelocity = 35,
|
||||||
spread = 70,
|
spread = 70,
|
||||||
onClick,
|
onClick,
|
||||||
...props
|
...props
|
||||||
}: ConfettiButtonProps) {
|
}: ConfettiButtonProps) {
|
||||||
const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
const handleClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
||||||
const target = event.currentTarget;
|
const target = event.currentTarget;
|
||||||
if (target) {
|
if (target) {
|
||||||
const rect = target.getBoundingClientRect();
|
const rect = target.getBoundingClientRect();
|
||||||
confetti({
|
confetti({
|
||||||
particleCount,
|
particleCount,
|
||||||
startVelocity,
|
startVelocity,
|
||||||
angle,
|
angle,
|
||||||
spread,
|
spread,
|
||||||
origin: {
|
origin: {
|
||||||
x: (rect.left + rect.width / 2) / window.innerWidth,
|
x: (rect.left + rect.width / 2) / window.innerWidth,
|
||||||
y: (rect.top + rect.height / 2) / window.innerHeight,
|
y: (rect.top + rect.height / 2) / window.innerHeight,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleClick} className={className} {...props}>
|
<Button onClick={handleClick} className={className} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,76 +1,76 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
import { isWujieEnv } from "@/core/embed/wujie";
|
import { isWujieEnv } from "@/core/embed/wujie";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Dialog({
|
function Dialog({
|
||||||
modal,
|
modal,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||||
// 无界嵌入时 Radix modal 会给 body 注入异常 margin-right,导致右侧空白。
|
// 无界嵌入时 Radix modal 会给 body 注入异常 margin-right,导致右侧空白。
|
||||||
// 默认降为 non-modal;显式传 modal={true} 仍可强制 modal。
|
// 默认降为 non-modal;显式传 modal={true} 仍可强制 modal。
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Root
|
<DialogPrimitive.Root
|
||||||
data-slot="dialog"
|
data-slot="dialog"
|
||||||
modal={modal ?? !isWujieEnv()}
|
modal={modal ?? !isWujieEnv()}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DialogTrigger = React.forwardRef<
|
const DialogTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Trigger>,
|
React.ElementRef<typeof DialogPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Trigger>
|
||||||
>(({ ...props }, ref) => {
|
>(({ ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Trigger
|
<DialogPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="dialog-trigger"
|
data-slot="dialog-trigger"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
DialogTrigger.displayName = DialogPrimitive.Trigger.displayName;
|
DialogTrigger.displayName = DialogPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
function DialogPortal({
|
function DialogPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DialogClose = React.forwardRef<
|
const DialogClose = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Close>,
|
React.ElementRef<typeof DialogPrimitive.Close>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
|
||||||
>(({ ...props }, ref) => {
|
>(({ ...props }, ref) => {
|
||||||
return <DialogPrimitive.Close ref={ref} data-slot="dialog-close" {...props} />;
|
return <DialogPrimitive.Close ref={ref} data-slot="dialog-close" {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
DialogClose.displayName = DialogPrimitive.Close.displayName;
|
DialogClose.displayName = DialogPrimitive.Close.displayName;
|
||||||
|
|
||||||
const DialogOverlay = React.forwardRef<
|
const DialogOverlay = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="dialog-overlay"
|
data-slot="dialog-overlay"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
const DialogContent = React.forwardRef<
|
const DialogContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
||||||
@@ -82,90 +82,90 @@ const DialogContent = React.forwardRef<
|
|||||||
return (
|
return (
|
||||||
<DialogPortal data-slot="dialog-portal">
|
<DialogPortal data-slot="dialog-portal">
|
||||||
<DialogOverlay className={overlayClassName} />
|
<DialogOverlay className={overlayClassName} />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
{showCloseButton && (
|
{showCloseButton && (
|
||||||
<DialogPrimitive.Close
|
<DialogPrimitive.Close
|
||||||
data-slot="dialog-close"
|
data-slot="dialog-close"
|
||||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
)}
|
)}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-header"
|
data-slot="dialog-header"
|
||||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-footer"
|
data-slot="dialog-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogTitle({
|
function DialogTitle({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
data-slot="dialog-title"
|
data-slot="dialog-title"
|
||||||
className={cn("text-lg leading-none font-semibold", className)}
|
className={cn("text-lg leading-none font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogDescription({
|
function DialogDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||||
return (
|
return (
|
||||||
<DialogPrimitive.Description
|
<DialogPrimitive.Description
|
||||||
data-slot="dialog-description"
|
data-slot="dialog-description"
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogOverlay,
|
DialogOverlay,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
};
|
};
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function DropdownMenu({
|
function DropdownMenu({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuPortal({
|
function DropdownMenuPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownMenuTrigger = React.forwardRef<
|
const DropdownMenuTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
|
||||||
@@ -34,7 +34,7 @@ const DropdownMenuTrigger = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;
|
DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
const DropdownMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
@@ -56,15 +56,15 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||||
|
|
||||||
function DropdownMenuGroup({
|
function DropdownMenuGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownMenuItem = React.forwardRef<
|
const DropdownMenuItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
@@ -88,123 +88,123 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||||
|
|
||||||
function DropdownMenuCheckboxItem({
|
function DropdownMenuCheckboxItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
checked,
|
checked,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
<CheckIcon className="size-4" />
|
<CheckIcon className="size-4" />
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({
|
function DropdownMenuRadioGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioGroup
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
data-slot="dropdown-menu-radio-group"
|
data-slot="dropdown-menu-radio-group"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioItem({
|
function DropdownMenuRadioItem({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
<DropdownMenuPrimitive.ItemIndicator>
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
<CircleIcon className="size-2 fill-current" />
|
<CircleIcon className="size-2 fill-current" />
|
||||||
</DropdownMenuPrimitive.ItemIndicator>
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuLabel({
|
function DropdownMenuLabel({
|
||||||
className,
|
className,
|
||||||
inset,
|
inset,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||||
inset?: boolean;
|
inset?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Label
|
<DropdownMenuPrimitive.Label
|
||||||
data-slot="dropdown-menu-label"
|
data-slot="dropdown-menu-label"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSeparator({
|
function DropdownMenuSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuPrimitive.Separator
|
<DropdownMenuPrimitive.Separator
|
||||||
data-slot="dropdown-menu-separator"
|
data-slot="dropdown-menu-separator"
|
||||||
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuShortcut({
|
function DropdownMenuShortcut({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"span">) {
|
}: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="dropdown-menu-shortcut"
|
data-slot="dropdown-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSub({
|
function DropdownMenuSub({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownMenuSubTrigger = React.forwardRef<
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
@@ -230,7 +230,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||||||
|
|
||||||
DropdownMenuSubTrigger.displayName =
|
DropdownMenuSubTrigger.displayName =
|
||||||
DropdownMenuPrimitive.SubTrigger.displayName;
|
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||||
@@ -250,21 +250,21 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
|
|
||||||
DropdownMenuSubContent.displayName =
|
DropdownMenuSubContent.displayName =
|
||||||
DropdownMenuPrimitive.SubContent.displayName;
|
DropdownMenuPrimitive.SubContent.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuShortcut,
|
DropdownMenuShortcut,
|
||||||
DropdownMenuSub,
|
DropdownMenuSub,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
};
|
};
|
||||||
@@ -1,104 +1,104 @@
|
|||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
function Empty({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty"
|
data-slot="empty"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-header"
|
data-slot="empty-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex max-w-sm flex-col items-center gap-2 text-center",
|
"flex max-w-sm flex-col items-center gap-2 text-center",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyMediaVariants = cva(
|
const emptyMediaVariants = cva(
|
||||||
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6 dark:text-[#55A6FE] dark:bg-[#022248]",
|
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6 dark:text-[#55A6FE] dark:bg-[#022248]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function EmptyMedia({
|
function EmptyMedia({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-icon"
|
data-slot="empty-icon"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(emptyMediaVariants({ variant, className }))}
|
className={cn(emptyMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-title"
|
data-slot="empty-title"
|
||||||
className={cn("text-lg font-medium tracking-tight dark:text-[#AACEFF]", className)}
|
className={cn("text-lg font-medium tracking-tight dark:text-[#AACEFF]", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-description"
|
data-slot="empty-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 dark:text-[#55A6FE]",
|
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 dark:text-[#55A6FE]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="empty-content"
|
data-slot="empty-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyHeader,
|
EmptyHeader,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
EmptyContent,
|
EmptyContent,
|
||||||
EmptyMedia,
|
EmptyMedia,
|
||||||
};
|
};
|
||||||
@@ -1,202 +1,202 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface FlickeringGridProps extends React.HTMLAttributes<HTMLDivElement> {
|
interface FlickeringGridProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
squareSize?: number;
|
squareSize?: number;
|
||||||
gridGap?: number;
|
gridGap?: number;
|
||||||
flickerChance?: number;
|
flickerChance?: number;
|
||||||
color?: string;
|
color?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
maxOpacity?: number;
|
maxOpacity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FlickeringGrid: React.FC<FlickeringGridProps> = ({
|
export const FlickeringGrid: React.FC<FlickeringGridProps> = ({
|
||||||
squareSize = 4,
|
squareSize = 4,
|
||||||
gridGap = 6,
|
gridGap = 6,
|
||||||
flickerChance = 0.3,
|
flickerChance = 0.3,
|
||||||
color = "rgb(0, 0, 0)",
|
color = "rgb(0, 0, 0)",
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
className,
|
className,
|
||||||
maxOpacity = 0.3,
|
maxOpacity = 0.3,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [isInView, setIsInView] = useState(false);
|
const [isInView, setIsInView] = useState(false);
|
||||||
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
|
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
const memoizedColor = useMemo(() => {
|
const memoizedColor = useMemo(() => {
|
||||||
const toRGBA = (color: string) => {
|
const toRGBA = (color: string) => {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return `rgba(0, 0, 0,`;
|
return `rgba(0, 0, 0,`;
|
||||||
}
|
}
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = canvas.height = 1;
|
canvas.width = canvas.height = 1;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
if (!ctx) return "rgba(255, 0, 0,";
|
if (!ctx) return "rgba(255, 0, 0,";
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.fillRect(0, 0, 1, 1);
|
ctx.fillRect(0, 0, 1, 1);
|
||||||
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
|
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
|
||||||
return `rgba(${r}, ${g}, ${b},`;
|
return `rgba(${r}, ${g}, ${b},`;
|
||||||
};
|
};
|
||||||
return toRGBA(color);
|
return toRGBA(color);
|
||||||
}, [color]);
|
}, [color]);
|
||||||
|
|
||||||
const setupCanvas = useCallback(
|
const setupCanvas = useCallback(
|
||||||
(canvas: HTMLCanvasElement, width: number, height: number) => {
|
(canvas: HTMLCanvasElement, width: number, height: number) => {
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
canvas.width = width * dpr;
|
canvas.width = width * dpr;
|
||||||
canvas.height = height * dpr;
|
canvas.height = height * dpr;
|
||||||
canvas.style.width = `${width}px`;
|
canvas.style.width = `${width}px`;
|
||||||
canvas.style.height = `${height}px`;
|
canvas.style.height = `${height}px`;
|
||||||
const cols = Math.floor(width / (squareSize + gridGap));
|
const cols = Math.floor(width / (squareSize + gridGap));
|
||||||
const rows = Math.floor(height / (squareSize + gridGap));
|
const rows = Math.floor(height / (squareSize + gridGap));
|
||||||
|
|
||||||
const squares = new Float32Array(cols * rows);
|
const squares = new Float32Array(cols * rows);
|
||||||
for (let i = 0; i < squares.length; i++) {
|
for (let i = 0; i < squares.length; i++) {
|
||||||
squares[i] = Math.random() * maxOpacity;
|
squares[i] = Math.random() * maxOpacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { cols, rows, squares, dpr };
|
return { cols, rows, squares, dpr };
|
||||||
},
|
},
|
||||||
[squareSize, gridGap, maxOpacity],
|
[squareSize, gridGap, maxOpacity],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateSquares = useCallback(
|
const updateSquares = useCallback(
|
||||||
(squares: Float32Array, deltaTime: number) => {
|
(squares: Float32Array, deltaTime: number) => {
|
||||||
for (let i = 0; i < squares.length; i++) {
|
for (let i = 0; i < squares.length; i++) {
|
||||||
if (Math.random() < flickerChance * deltaTime) {
|
if (Math.random() < flickerChance * deltaTime) {
|
||||||
squares[i] = Math.random() * maxOpacity;
|
squares[i] = Math.random() * maxOpacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[flickerChance, maxOpacity],
|
[flickerChance, maxOpacity],
|
||||||
);
|
);
|
||||||
|
|
||||||
const drawGrid = useCallback(
|
const drawGrid = useCallback(
|
||||||
(
|
(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
cols: number,
|
cols: number,
|
||||||
rows: number,
|
rows: number,
|
||||||
squares: Float32Array,
|
squares: Float32Array,
|
||||||
dpr: number,
|
dpr: number,
|
||||||
) => {
|
) => {
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
ctx.fillStyle = "transparent";
|
ctx.fillStyle = "transparent";
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
for (let i = 0; i < cols; i++) {
|
for (let i = 0; i < cols; i++) {
|
||||||
for (let j = 0; j < rows; j++) {
|
for (let j = 0; j < rows; j++) {
|
||||||
const opacity = squares[i * rows + j];
|
const opacity = squares[i * rows + j];
|
||||||
ctx.fillStyle = `${memoizedColor}${opacity})`;
|
ctx.fillStyle = `${memoizedColor}${opacity})`;
|
||||||
ctx.fillRect(
|
ctx.fillRect(
|
||||||
i * (squareSize + gridGap) * dpr,
|
i * (squareSize + gridGap) * dpr,
|
||||||
j * (squareSize + gridGap) * dpr,
|
j * (squareSize + gridGap) * dpr,
|
||||||
squareSize * dpr,
|
squareSize * dpr,
|
||||||
squareSize * dpr,
|
squareSize * dpr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[memoizedColor, squareSize, gridGap],
|
[memoizedColor, squareSize, gridGap],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!canvas || !container) return;
|
if (!canvas || !container) return;
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
let animationFrameId: number;
|
let animationFrameId: number;
|
||||||
let gridParams: ReturnType<typeof setupCanvas>;
|
let gridParams: ReturnType<typeof setupCanvas>;
|
||||||
|
|
||||||
const updateCanvasSize = () => {
|
const updateCanvasSize = () => {
|
||||||
const newWidth = width || container.clientWidth;
|
const newWidth = width || container.clientWidth;
|
||||||
const newHeight = height || container.clientHeight;
|
const newHeight = height || container.clientHeight;
|
||||||
setCanvasSize({ width: newWidth, height: newHeight });
|
setCanvasSize({ width: newWidth, height: newHeight });
|
||||||
gridParams = setupCanvas(canvas, newWidth, newHeight);
|
gridParams = setupCanvas(canvas, newWidth, newHeight);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateCanvasSize();
|
updateCanvasSize();
|
||||||
|
|
||||||
let lastTime = 0;
|
let lastTime = 0;
|
||||||
const animate = (time: number) => {
|
const animate = (time: number) => {
|
||||||
if (!isInView) return;
|
if (!isInView) return;
|
||||||
|
|
||||||
const deltaTime = (time - lastTime) / 1000;
|
const deltaTime = (time - lastTime) / 1000;
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
|
|
||||||
updateSquares(gridParams.squares, deltaTime);
|
updateSquares(gridParams.squares, deltaTime);
|
||||||
drawGrid(
|
drawGrid(
|
||||||
ctx,
|
ctx,
|
||||||
canvas.width,
|
canvas.width,
|
||||||
canvas.height,
|
canvas.height,
|
||||||
gridParams.cols,
|
gridParams.cols,
|
||||||
gridParams.rows,
|
gridParams.rows,
|
||||||
gridParams.squares,
|
gridParams.squares,
|
||||||
gridParams.dpr,
|
gridParams.dpr,
|
||||||
);
|
);
|
||||||
animationFrameId = requestAnimationFrame(animate);
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
updateCanvasSize();
|
updateCanvasSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeObserver.observe(container);
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
const intersectionObserver = new IntersectionObserver(
|
const intersectionObserver = new IntersectionObserver(
|
||||||
([entry]) => {
|
([entry]) => {
|
||||||
if (entry) {
|
if (entry) {
|
||||||
setIsInView(entry.isIntersecting);
|
setIsInView(entry.isIntersecting);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ threshold: 0 },
|
{ threshold: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
intersectionObserver.observe(canvas);
|
intersectionObserver.observe(canvas);
|
||||||
|
|
||||||
if (isInView) {
|
if (isInView) {
|
||||||
animationFrameId = requestAnimationFrame(animate);
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(animationFrameId);
|
cancelAnimationFrame(animationFrameId);
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
intersectionObserver.disconnect();
|
intersectionObserver.disconnect();
|
||||||
};
|
};
|
||||||
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
|
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={cn(`h-full w-full ${className}`)}
|
className={cn(`h-full w-full ${className}`)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
className="pointer-events-none"
|
className="pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
width: canvasSize.width,
|
width: canvasSize.width,
|
||||||
height: canvasSize.height,
|
height: canvasSize.height,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
.galaxy-container {
|
.galaxy-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -1,361 +1,361 @@
|
|||||||
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
|
import { Renderer, Program, Mesh, Color, Triangle } from "ogl";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import "./galaxy.css";
|
import "./galaxy.css";
|
||||||
|
|
||||||
const vertexShader = `
|
const vertexShader = `
|
||||||
attribute vec2 uv;
|
attribute vec2 uv;
|
||||||
attribute vec2 position;
|
attribute vec2 position;
|
||||||
|
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vUv = uv;
|
vUv = uv;
|
||||||
gl_Position = vec4(position, 0, 1);
|
gl_Position = vec4(position, 0, 1);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const fragmentShader = `
|
const fragmentShader = `
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
uniform float uTime;
|
uniform float uTime;
|
||||||
uniform vec3 uResolution;
|
uniform vec3 uResolution;
|
||||||
uniform vec2 uFocal;
|
uniform vec2 uFocal;
|
||||||
uniform vec2 uRotation;
|
uniform vec2 uRotation;
|
||||||
uniform float uStarSpeed;
|
uniform float uStarSpeed;
|
||||||
uniform float uDensity;
|
uniform float uDensity;
|
||||||
uniform float uHueShift;
|
uniform float uHueShift;
|
||||||
uniform float uSpeed;
|
uniform float uSpeed;
|
||||||
uniform vec2 uMouse;
|
uniform vec2 uMouse;
|
||||||
uniform float uGlowIntensity;
|
uniform float uGlowIntensity;
|
||||||
uniform float uSaturation;
|
uniform float uSaturation;
|
||||||
uniform bool uMouseRepulsion;
|
uniform bool uMouseRepulsion;
|
||||||
uniform float uTwinkleIntensity;
|
uniform float uTwinkleIntensity;
|
||||||
uniform float uRotationSpeed;
|
uniform float uRotationSpeed;
|
||||||
uniform float uRepulsionStrength;
|
uniform float uRepulsionStrength;
|
||||||
uniform float uMouseActiveFactor;
|
uniform float uMouseActiveFactor;
|
||||||
uniform float uAutoCenterRepulsion;
|
uniform float uAutoCenterRepulsion;
|
||||||
uniform bool uTransparent;
|
uniform bool uTransparent;
|
||||||
|
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
|
|
||||||
#define NUM_LAYER 4.0
|
#define NUM_LAYER 4.0
|
||||||
#define STAR_COLOR_CUTOFF 0.2
|
#define STAR_COLOR_CUTOFF 0.2
|
||||||
#define MAT45 mat2(0.7071, -0.7071, 0.7071, 0.7071)
|
#define MAT45 mat2(0.7071, -0.7071, 0.7071, 0.7071)
|
||||||
#define PERIOD 3.0
|
#define PERIOD 3.0
|
||||||
|
|
||||||
float Hash21(vec2 p) {
|
float Hash21(vec2 p) {
|
||||||
p = fract(p * vec2(123.34, 456.21));
|
p = fract(p * vec2(123.34, 456.21));
|
||||||
p += dot(p, p + 45.32);
|
p += dot(p, p + 45.32);
|
||||||
return fract(p.x * p.y);
|
return fract(p.x * p.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
float tri(float x) {
|
float tri(float x) {
|
||||||
return abs(fract(x) * 2.0 - 1.0);
|
return abs(fract(x) * 2.0 - 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
float tris(float x) {
|
float tris(float x) {
|
||||||
float t = fract(x);
|
float t = fract(x);
|
||||||
return 1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0));
|
return 1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
float trisn(float x) {
|
float trisn(float x) {
|
||||||
float t = fract(x);
|
float t = fract(x);
|
||||||
return 2.0 * (1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0))) - 1.0;
|
return 2.0 * (1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0))) - 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 hsv2rgb(vec3 c) {
|
vec3 hsv2rgb(vec3 c) {
|
||||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Star(vec2 uv, float flare) {
|
float Star(vec2 uv, float flare) {
|
||||||
float d = length(uv);
|
float d = length(uv);
|
||||||
float m = (0.05 * uGlowIntensity) / d;
|
float m = (0.05 * uGlowIntensity) / d;
|
||||||
float rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
|
float rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
|
||||||
m += rays * flare * uGlowIntensity;
|
m += rays * flare * uGlowIntensity;
|
||||||
uv *= MAT45;
|
uv *= MAT45;
|
||||||
rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
|
rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
|
||||||
m += rays * 0.3 * flare * uGlowIntensity;
|
m += rays * 0.3 * flare * uGlowIntensity;
|
||||||
m *= smoothstep(1.0, 0.2, d);
|
m *= smoothstep(1.0, 0.2, d);
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 StarLayer(vec2 uv) {
|
vec3 StarLayer(vec2 uv) {
|
||||||
vec3 col = vec3(0.0);
|
vec3 col = vec3(0.0);
|
||||||
|
|
||||||
vec2 gv = fract(uv) - 0.5;
|
vec2 gv = fract(uv) - 0.5;
|
||||||
vec2 id = floor(uv);
|
vec2 id = floor(uv);
|
||||||
|
|
||||||
for (int y = -1; y <= 1; y++) {
|
for (int y = -1; y <= 1; y++) {
|
||||||
for (int x = -1; x <= 1; x++) {
|
for (int x = -1; x <= 1; x++) {
|
||||||
vec2 offset = vec2(float(x), float(y));
|
vec2 offset = vec2(float(x), float(y));
|
||||||
vec2 si = id + vec2(float(x), float(y));
|
vec2 si = id + vec2(float(x), float(y));
|
||||||
float seed = Hash21(si);
|
float seed = Hash21(si);
|
||||||
float size = fract(seed * 345.32);
|
float size = fract(seed * 345.32);
|
||||||
float glossLocal = tri(uStarSpeed / (PERIOD * seed + 1.0));
|
float glossLocal = tri(uStarSpeed / (PERIOD * seed + 1.0));
|
||||||
float flareSize = smoothstep(0.9, 1.0, size) * glossLocal;
|
float flareSize = smoothstep(0.9, 1.0, size) * glossLocal;
|
||||||
|
|
||||||
float red = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 1.0)) + STAR_COLOR_CUTOFF;
|
float red = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 1.0)) + STAR_COLOR_CUTOFF;
|
||||||
float blu = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 3.0)) + STAR_COLOR_CUTOFF;
|
float blu = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 3.0)) + STAR_COLOR_CUTOFF;
|
||||||
float grn = min(red, blu) * seed;
|
float grn = min(red, blu) * seed;
|
||||||
vec3 base = vec3(red, grn, blu);
|
vec3 base = vec3(red, grn, blu);
|
||||||
|
|
||||||
float hue = atan(base.g - base.r, base.b - base.r) / (2.0 * 3.14159) + 0.5;
|
float hue = atan(base.g - base.r, base.b - base.r) / (2.0 * 3.14159) + 0.5;
|
||||||
hue = fract(hue + uHueShift / 360.0);
|
hue = fract(hue + uHueShift / 360.0);
|
||||||
float sat = length(base - vec3(dot(base, vec3(0.299, 0.587, 0.114)))) * uSaturation;
|
float sat = length(base - vec3(dot(base, vec3(0.299, 0.587, 0.114)))) * uSaturation;
|
||||||
float val = max(max(base.r, base.g), base.b);
|
float val = max(max(base.r, base.g), base.b);
|
||||||
base = hsv2rgb(vec3(hue, sat, val));
|
base = hsv2rgb(vec3(hue, sat, val));
|
||||||
|
|
||||||
vec2 pad = vec2(tris(seed * 34.0 + uTime * uSpeed / 10.0), tris(seed * 38.0 + uTime * uSpeed / 30.0)) - 0.5;
|
vec2 pad = vec2(tris(seed * 34.0 + uTime * uSpeed / 10.0), tris(seed * 38.0 + uTime * uSpeed / 30.0)) - 0.5;
|
||||||
|
|
||||||
float star = Star(gv - offset - pad, flareSize);
|
float star = Star(gv - offset - pad, flareSize);
|
||||||
vec3 color = base;
|
vec3 color = base;
|
||||||
|
|
||||||
float twinkle = trisn(uTime * uSpeed + seed * 6.2831) * 0.5 + 1.0;
|
float twinkle = trisn(uTime * uSpeed + seed * 6.2831) * 0.5 + 1.0;
|
||||||
twinkle = mix(1.0, twinkle, uTwinkleIntensity);
|
twinkle = mix(1.0, twinkle, uTwinkleIntensity);
|
||||||
star *= twinkle;
|
star *= twinkle;
|
||||||
|
|
||||||
col += star * size * color;
|
col += star * size * color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 focalPx = uFocal * uResolution.xy;
|
vec2 focalPx = uFocal * uResolution.xy;
|
||||||
vec2 uv = (vUv * uResolution.xy - focalPx) / uResolution.y;
|
vec2 uv = (vUv * uResolution.xy - focalPx) / uResolution.y;
|
||||||
|
|
||||||
vec2 mouseNorm = uMouse - vec2(0.5);
|
vec2 mouseNorm = uMouse - vec2(0.5);
|
||||||
|
|
||||||
if (uAutoCenterRepulsion > 0.0) {
|
if (uAutoCenterRepulsion > 0.0) {
|
||||||
vec2 centerUV = vec2(0.0, 0.0);
|
vec2 centerUV = vec2(0.0, 0.0);
|
||||||
float centerDist = length(uv - centerUV);
|
float centerDist = length(uv - centerUV);
|
||||||
vec2 repulsion = normalize(uv - centerUV) * (uAutoCenterRepulsion / (centerDist + 0.1));
|
vec2 repulsion = normalize(uv - centerUV) * (uAutoCenterRepulsion / (centerDist + 0.1));
|
||||||
uv += repulsion * 0.05;
|
uv += repulsion * 0.05;
|
||||||
} else if (uMouseRepulsion) {
|
} else if (uMouseRepulsion) {
|
||||||
vec2 mousePosUV = (uMouse * uResolution.xy - focalPx) / uResolution.y;
|
vec2 mousePosUV = (uMouse * uResolution.xy - focalPx) / uResolution.y;
|
||||||
float mouseDist = length(uv - mousePosUV);
|
float mouseDist = length(uv - mousePosUV);
|
||||||
vec2 repulsion = normalize(uv - mousePosUV) * (uRepulsionStrength / (mouseDist + 0.1));
|
vec2 repulsion = normalize(uv - mousePosUV) * (uRepulsionStrength / (mouseDist + 0.1));
|
||||||
uv += repulsion * 0.05 * uMouseActiveFactor;
|
uv += repulsion * 0.05 * uMouseActiveFactor;
|
||||||
} else {
|
} else {
|
||||||
vec2 mouseOffset = mouseNorm * 0.1 * uMouseActiveFactor;
|
vec2 mouseOffset = mouseNorm * 0.1 * uMouseActiveFactor;
|
||||||
uv += mouseOffset;
|
uv += mouseOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
float autoRotAngle = uTime * uRotationSpeed;
|
float autoRotAngle = uTime * uRotationSpeed;
|
||||||
mat2 autoRot = mat2(cos(autoRotAngle), -sin(autoRotAngle), sin(autoRotAngle), cos(autoRotAngle));
|
mat2 autoRot = mat2(cos(autoRotAngle), -sin(autoRotAngle), sin(autoRotAngle), cos(autoRotAngle));
|
||||||
uv = autoRot * uv;
|
uv = autoRot * uv;
|
||||||
|
|
||||||
uv = mat2(uRotation.x, -uRotation.y, uRotation.y, uRotation.x) * uv;
|
uv = mat2(uRotation.x, -uRotation.y, uRotation.y, uRotation.x) * uv;
|
||||||
|
|
||||||
vec3 col = vec3(0.0);
|
vec3 col = vec3(0.0);
|
||||||
|
|
||||||
for (float i = 0.0; i < 1.0; i += 1.0 / NUM_LAYER) {
|
for (float i = 0.0; i < 1.0; i += 1.0 / NUM_LAYER) {
|
||||||
float depth = fract(i + uStarSpeed * uSpeed);
|
float depth = fract(i + uStarSpeed * uSpeed);
|
||||||
float scale = mix(20.0 * uDensity, 0.5 * uDensity, depth);
|
float scale = mix(20.0 * uDensity, 0.5 * uDensity, depth);
|
||||||
float fade = depth * smoothstep(1.0, 0.9, depth);
|
float fade = depth * smoothstep(1.0, 0.9, depth);
|
||||||
col += StarLayer(uv * scale + i * 453.32) * fade;
|
col += StarLayer(uv * scale + i * 453.32) * fade;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uTransparent) {
|
if (uTransparent) {
|
||||||
float alpha = length(col);
|
float alpha = length(col);
|
||||||
alpha = smoothstep(0.0, 0.3, alpha);
|
alpha = smoothstep(0.0, 0.3, alpha);
|
||||||
alpha = min(alpha, 1.0);
|
alpha = min(alpha, 1.0);
|
||||||
gl_FragColor = vec4(col, alpha);
|
gl_FragColor = vec4(col, alpha);
|
||||||
} else {
|
} else {
|
||||||
gl_FragColor = vec4(col, 1.0);
|
gl_FragColor = vec4(col, 1.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function Galaxy({
|
export default function Galaxy({
|
||||||
focal = [0.5, 0.5],
|
focal = [0.5, 0.5],
|
||||||
rotation = [1.0, 0.0],
|
rotation = [1.0, 0.0],
|
||||||
starSpeed = 0.5,
|
starSpeed = 0.5,
|
||||||
density = 1,
|
density = 1,
|
||||||
hueShift = 140,
|
hueShift = 140,
|
||||||
disableAnimation = false,
|
disableAnimation = false,
|
||||||
speed = 1.0,
|
speed = 1.0,
|
||||||
mouseInteraction = true,
|
mouseInteraction = true,
|
||||||
glowIntensity = 0.3,
|
glowIntensity = 0.3,
|
||||||
saturation = 0.0,
|
saturation = 0.0,
|
||||||
mouseRepulsion = true,
|
mouseRepulsion = true,
|
||||||
repulsionStrength = 2,
|
repulsionStrength = 2,
|
||||||
twinkleIntensity = 0.3,
|
twinkleIntensity = 0.3,
|
||||||
rotationSpeed = 0.1,
|
rotationSpeed = 0.1,
|
||||||
autoCenterRepulsion = 0,
|
autoCenterRepulsion = 0,
|
||||||
transparent = true,
|
transparent = true,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const ctnDom = useRef(null);
|
const ctnDom = useRef(null);
|
||||||
const targetMousePos = useRef({ x: 0.5, y: 0.5 });
|
const targetMousePos = useRef({ x: 0.5, y: 0.5 });
|
||||||
const smoothMousePos = useRef({ x: 0.5, y: 0.5 });
|
const smoothMousePos = useRef({ x: 0.5, y: 0.5 });
|
||||||
const targetMouseActive = useRef(0.0);
|
const targetMouseActive = useRef(0.0);
|
||||||
const smoothMouseActive = useRef(0.0);
|
const smoothMouseActive = useRef(0.0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ctnDom.current) return;
|
if (!ctnDom.current) return;
|
||||||
const ctn = ctnDom.current;
|
const ctn = ctnDom.current;
|
||||||
|
|
||||||
let renderer;
|
let renderer;
|
||||||
try {
|
try {
|
||||||
renderer = new Renderer({
|
renderer = new Renderer({
|
||||||
alpha: transparent,
|
alpha: transparent,
|
||||||
premultipliedAlpha: false,
|
premultipliedAlpha: false,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Galaxy: WebGL is not available. The galaxy background will not be rendered.",
|
"Galaxy: WebGL is not available. The galaxy background will not be rendered.",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gl = renderer.gl;
|
const gl = renderer.gl;
|
||||||
if (!gl) {
|
if (!gl) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Galaxy: WebGL context is null. The galaxy background will not be rendered.",
|
"Galaxy: WebGL context is null. The galaxy background will not be rendered.",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transparent) {
|
if (transparent) {
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
gl.clearColor(0, 0, 0, 0);
|
gl.clearColor(0, 0, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
gl.clearColor(0, 0, 0, 1);
|
gl.clearColor(0, 0, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Program | undefined} */
|
/** @type {Program | undefined} */
|
||||||
let program;
|
let program;
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
const scale = 1;
|
const scale = 1;
|
||||||
renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
|
renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
|
||||||
if (program) {
|
if (program) {
|
||||||
program.uniforms.uResolution.value = new Color(
|
program.uniforms.uResolution.value = new Color(
|
||||||
gl.canvas.width,
|
gl.canvas.width,
|
||||||
gl.canvas.height,
|
gl.canvas.height,
|
||||||
gl.canvas.width / gl.canvas.height,
|
gl.canvas.width / gl.canvas.height,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.addEventListener("resize", resize, false);
|
window.addEventListener("resize", resize, false);
|
||||||
resize();
|
resize();
|
||||||
|
|
||||||
const geometry = new Triangle(gl);
|
const geometry = new Triangle(gl);
|
||||||
program = new Program(gl, {
|
program = new Program(gl, {
|
||||||
vertex: vertexShader,
|
vertex: vertexShader,
|
||||||
fragment: fragmentShader,
|
fragment: fragmentShader,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
uTime: { value: 0 },
|
uTime: { value: 0 },
|
||||||
uResolution: {
|
uResolution: {
|
||||||
value: new Color(
|
value: new Color(
|
||||||
gl.canvas.width,
|
gl.canvas.width,
|
||||||
gl.canvas.height,
|
gl.canvas.height,
|
||||||
gl.canvas.width / gl.canvas.height,
|
gl.canvas.width / gl.canvas.height,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
uFocal: { value: new Float32Array(focal) },
|
uFocal: { value: new Float32Array(focal) },
|
||||||
uRotation: { value: new Float32Array(rotation) },
|
uRotation: { value: new Float32Array(rotation) },
|
||||||
uStarSpeed: { value: starSpeed },
|
uStarSpeed: { value: starSpeed },
|
||||||
uDensity: { value: density },
|
uDensity: { value: density },
|
||||||
uHueShift: { value: hueShift },
|
uHueShift: { value: hueShift },
|
||||||
uSpeed: { value: speed },
|
uSpeed: { value: speed },
|
||||||
uMouse: {
|
uMouse: {
|
||||||
value: new Float32Array([
|
value: new Float32Array([
|
||||||
smoothMousePos.current.x,
|
smoothMousePos.current.x,
|
||||||
smoothMousePos.current.y,
|
smoothMousePos.current.y,
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
uGlowIntensity: { value: glowIntensity },
|
uGlowIntensity: { value: glowIntensity },
|
||||||
uSaturation: { value: saturation },
|
uSaturation: { value: saturation },
|
||||||
uMouseRepulsion: { value: mouseRepulsion },
|
uMouseRepulsion: { value: mouseRepulsion },
|
||||||
uTwinkleIntensity: { value: twinkleIntensity },
|
uTwinkleIntensity: { value: twinkleIntensity },
|
||||||
uRotationSpeed: { value: rotationSpeed },
|
uRotationSpeed: { value: rotationSpeed },
|
||||||
uRepulsionStrength: { value: repulsionStrength },
|
uRepulsionStrength: { value: repulsionStrength },
|
||||||
uMouseActiveFactor: { value: 0.0 },
|
uMouseActiveFactor: { value: 0.0 },
|
||||||
uAutoCenterRepulsion: { value: autoCenterRepulsion },
|
uAutoCenterRepulsion: { value: autoCenterRepulsion },
|
||||||
uTransparent: { value: transparent },
|
uTransparent: { value: transparent },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mesh = new Mesh(gl, { geometry, program });
|
const mesh = new Mesh(gl, { geometry, program });
|
||||||
let animateId;
|
let animateId;
|
||||||
|
|
||||||
function update(t) {
|
function update(t) {
|
||||||
animateId = requestAnimationFrame(update);
|
animateId = requestAnimationFrame(update);
|
||||||
if (!disableAnimation) {
|
if (!disableAnimation) {
|
||||||
program.uniforms.uTime.value = t * 0.001;
|
program.uniforms.uTime.value = t * 0.001;
|
||||||
program.uniforms.uStarSpeed.value = (t * 0.001 * starSpeed) / 10.0;
|
program.uniforms.uStarSpeed.value = (t * 0.001 * starSpeed) / 10.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lerpFactor = 0.05;
|
const lerpFactor = 0.05;
|
||||||
smoothMousePos.current.x +=
|
smoothMousePos.current.x +=
|
||||||
(targetMousePos.current.x - smoothMousePos.current.x) * lerpFactor;
|
(targetMousePos.current.x - smoothMousePos.current.x) * lerpFactor;
|
||||||
smoothMousePos.current.y +=
|
smoothMousePos.current.y +=
|
||||||
(targetMousePos.current.y - smoothMousePos.current.y) * lerpFactor;
|
(targetMousePos.current.y - smoothMousePos.current.y) * lerpFactor;
|
||||||
|
|
||||||
smoothMouseActive.current +=
|
smoothMouseActive.current +=
|
||||||
(targetMouseActive.current - smoothMouseActive.current) * lerpFactor;
|
(targetMouseActive.current - smoothMouseActive.current) * lerpFactor;
|
||||||
|
|
||||||
program.uniforms.uMouse.value[0] = smoothMousePos.current.x;
|
program.uniforms.uMouse.value[0] = smoothMousePos.current.x;
|
||||||
program.uniforms.uMouse.value[1] = smoothMousePos.current.y;
|
program.uniforms.uMouse.value[1] = smoothMousePos.current.y;
|
||||||
program.uniforms.uMouseActiveFactor.value = smoothMouseActive.current;
|
program.uniforms.uMouseActiveFactor.value = smoothMouseActive.current;
|
||||||
|
|
||||||
renderer.render({ scene: mesh });
|
renderer.render({ scene: mesh });
|
||||||
}
|
}
|
||||||
animateId = requestAnimationFrame(update);
|
animateId = requestAnimationFrame(update);
|
||||||
ctn.appendChild(gl.canvas);
|
ctn.appendChild(gl.canvas);
|
||||||
|
|
||||||
function handleMouseMove(e) {
|
function handleMouseMove(e) {
|
||||||
const rect = ctn.getBoundingClientRect();
|
const rect = ctn.getBoundingClientRect();
|
||||||
const x = (e.clientX - rect.left) / rect.width;
|
const x = (e.clientX - rect.left) / rect.width;
|
||||||
const y = 1.0 - (e.clientY - rect.top) / rect.height;
|
const y = 1.0 - (e.clientY - rect.top) / rect.height;
|
||||||
targetMousePos.current = { x, y };
|
targetMousePos.current = { x, y };
|
||||||
targetMouseActive.current = 1.0;
|
targetMouseActive.current = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseLeave() {
|
function handleMouseLeave() {
|
||||||
targetMouseActive.current = 0.0;
|
targetMouseActive.current = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouseInteraction) {
|
if (mouseInteraction) {
|
||||||
ctn.addEventListener("mousemove", handleMouseMove);
|
ctn.addEventListener("mousemove", handleMouseMove);
|
||||||
ctn.addEventListener("mouseleave", handleMouseLeave);
|
ctn.addEventListener("mouseleave", handleMouseLeave);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(animateId);
|
cancelAnimationFrame(animateId);
|
||||||
window.removeEventListener("resize", resize);
|
window.removeEventListener("resize", resize);
|
||||||
if (mouseInteraction) {
|
if (mouseInteraction) {
|
||||||
ctn.removeEventListener("mousemove", handleMouseMove);
|
ctn.removeEventListener("mousemove", handleMouseMove);
|
||||||
ctn.removeEventListener("mouseleave", handleMouseLeave);
|
ctn.removeEventListener("mouseleave", handleMouseLeave);
|
||||||
}
|
}
|
||||||
ctn.removeChild(gl.canvas);
|
ctn.removeChild(gl.canvas);
|
||||||
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
focal,
|
focal,
|
||||||
rotation,
|
rotation,
|
||||||
starSpeed,
|
starSpeed,
|
||||||
density,
|
density,
|
||||||
hueShift,
|
hueShift,
|
||||||
disableAnimation,
|
disableAnimation,
|
||||||
speed,
|
speed,
|
||||||
mouseInteraction,
|
mouseInteraction,
|
||||||
glowIntensity,
|
glowIntensity,
|
||||||
saturation,
|
saturation,
|
||||||
mouseRepulsion,
|
mouseRepulsion,
|
||||||
twinkleIntensity,
|
twinkleIntensity,
|
||||||
rotationSpeed,
|
rotationSpeed,
|
||||||
repulsionStrength,
|
repulsionStrength,
|
||||||
autoCenterRepulsion,
|
autoCenterRepulsion,
|
||||||
transparent,
|
transparent,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <div ref={ctnDom} className="galaxy-container" {...rest} />;
|
return <div ref={ctnDom} className="galaxy-container" {...rest} />;
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function HoverCard({
|
function HoverCard({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
|
||||||
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
|
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HoverCardTrigger = React.forwardRef<
|
const HoverCardTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof HoverCardPrimitive.Trigger>,
|
React.ElementRef<typeof HoverCardPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Trigger>
|
||||||
@@ -48,5 +48,5 @@ const HoverCardContent = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
|
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
export { HoverCard, HoverCardTrigger, HoverCardContent };
|
||||||
@@ -1,173 +1,173 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="input-group"
|
data-slot="input-group"
|
||||||
role="group"
|
role="group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/input-group relative flex w-full items-center rounded-md border border-input/50 bg-white/80 shadow-xs transition-[color,box-shadow] outline-none dark:border-[#1B477E] dark:bg-[#022248]",
|
"group/input-group relative flex w-full items-center rounded-md border border-input/50 bg-white/80 shadow-xs transition-[color,box-shadow] outline-none dark:border-[#1B477E] dark:bg-[#022248]",
|
||||||
"h-9 min-w-0 has-[>textarea]:h-auto",
|
"h-9 min-w-0 has-[>textarea]:h-auto",
|
||||||
|
|
||||||
// Variants based on alignment.
|
// Variants based on alignment.
|
||||||
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
||||||
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
"has-[>[data-align=inline-end]]:[&>input]:pr-2",
|
||||||
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
||||||
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
||||||
|
|
||||||
// Focus state.
|
// Focus state.
|
||||||
"has-[[data-slot=input-group-control]:focus-visible]:border-input has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
|
"has-[[data-slot=input-group-control]:focus-visible]:border-input has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
|
||||||
|
|
||||||
// Error state.
|
// Error state.
|
||||||
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
||||||
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupAddonVariants = cva(
|
const inputGroupAddonVariants = cva(
|
||||||
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
"text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none [&>svg:not([class*='size-'])]:size-4 [&>kbd]:rounded-[calc(var(--radius)-5px)] group-data-[disabled=true]/input-group:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
align: {
|
align: {
|
||||||
"inline-start":
|
"inline-start":
|
||||||
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
"order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
|
||||||
"inline-end":
|
"inline-end":
|
||||||
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
"order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
|
||||||
"block-start":
|
"block-start":
|
||||||
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
"order-first w-full justify-start px-3 pt-3 [.border-b]:pb-3 group-has-[>input]/input-group:pt-2.5",
|
||||||
"block-end":
|
"block-end":
|
||||||
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
"order-last w-full justify-start px-3 pb-3 [.border-t]:pt-3 group-has-[>input]/input-group:pb-2.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
align: "inline-start",
|
align: "inline-start",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function InputGroupAddon({
|
function InputGroupAddon({
|
||||||
className,
|
className,
|
||||||
align = "inline-start",
|
align = "inline-start",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
data-slot="input-group-addon"
|
data-slot="input-group-addon"
|
||||||
data-align={align}
|
data-align={align}
|
||||||
className={cn(inputGroupAddonVariants({ align }), className)}
|
className={cn(inputGroupAddonVariants({ align }), className)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if ((e.target as HTMLElement).closest("button")) {
|
if ((e.target as HTMLElement).closest("button")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
e.currentTarget.parentElement?.querySelector("input")?.focus();
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputGroupButtonVariants = cva(
|
const inputGroupButtonVariants = cva(
|
||||||
"text-sm shadow-none flex gap-2 items-center",
|
"text-sm shadow-none flex gap-2 items-center",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
|
||||||
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
|
||||||
"icon-xs":
|
"icon-xs":
|
||||||
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
|
||||||
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: "xs",
|
size: "xs",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function InputGroupButton({
|
function InputGroupButton({
|
||||||
className,
|
className,
|
||||||
type = "button",
|
type = "button",
|
||||||
variant = "ghost",
|
variant = "ghost",
|
||||||
size = "xs",
|
size = "xs",
|
||||||
...props
|
...props
|
||||||
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
}: Omit<React.ComponentProps<typeof Button>, "size"> &
|
||||||
VariantProps<typeof inputGroupButtonVariants>) {
|
VariantProps<typeof inputGroupButtonVariants>) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type={type}
|
type={type}
|
||||||
data-size={size}
|
data-size={size}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
className={cn(inputGroupButtonVariants({ size }), className)}
|
className={cn(inputGroupButtonVariants({ size }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputGroupInput({
|
function InputGroupInput({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"input">) {
|
}: React.ComponentProps<"input">) {
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputGroupTextarea = React.forwardRef<
|
const InputGroupTextarea = React.forwardRef<
|
||||||
HTMLTextAreaElement,
|
HTMLTextAreaElement,
|
||||||
React.ComponentProps<"textarea">
|
React.ComponentProps<"textarea">
|
||||||
>(function InputGroupTextarea({ className, ...props }, ref) {
|
>(function InputGroupTextarea({ className, ...props }, ref) {
|
||||||
return (
|
return (
|
||||||
<Textarea
|
<Textarea
|
||||||
data-slot="input-group-control"
|
data-slot="input-group-control"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
InputGroupTextarea.displayName = "InputGroupTextarea";
|
InputGroupTextarea.displayName = "InputGroupTextarea";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputGroupAddon,
|
InputGroupAddon,
|
||||||
InputGroupButton,
|
InputGroupButton,
|
||||||
InputGroupText,
|
InputGroupText,
|
||||||
InputGroupInput,
|
InputGroupInput,
|
||||||
InputGroupTextarea,
|
InputGroupTextarea,
|
||||||
};
|
};
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
const usesDarkNativePicker =
|
const usesDarkNativePicker =
|
||||||
type === "number" ||
|
type === "number" ||
|
||||||
type === "date" ||
|
type === "date" ||
|
||||||
type === "datetime-local" ||
|
type === "datetime-local" ||
|
||||||
type === "time" ||
|
type === "time" ||
|
||||||
type === "month" ||
|
type === "month" ||
|
||||||
type === "week";
|
type === "week";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
"h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm",
|
||||||
"border-input placeholder:text-muted-foreground",
|
"border-input placeholder:text-muted-foreground",
|
||||||
"dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] dark:rounded-[4px] dark:text-[16px] md:dark:text-[16px]",
|
"dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] dark:rounded-[4px] dark:text-[16px] md:dark:text-[16px]",
|
||||||
usesDarkNativePicker && "dark:[color-scheme:dark]",
|
usesDarkNativePicker && "dark:[color-scheme:dark]",
|
||||||
"file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
"file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
||||||
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Input };
|
export { Input };
|
||||||
@@ -1,193 +1,193 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Slot } from "@radix-ui/react-slot";
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
role="list"
|
role="list"
|
||||||
data-slot="item-group"
|
data-slot="item-group"
|
||||||
className={cn("group/item-group flex flex-col", className)}
|
className={cn("group/item-group flex flex-col", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemSeparator({
|
function ItemSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof Separator>) {
|
}: React.ComponentProps<typeof Separator>) {
|
||||||
return (
|
return (
|
||||||
<Separator
|
<Separator
|
||||||
data-slot="item-separator"
|
data-slot="item-separator"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
className={cn("my-0", className)}
|
className={cn("my-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemVariants = cva(
|
const itemVariants = cva(
|
||||||
"group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
outline: "border-border",
|
outline: "border-border",
|
||||||
muted: "bg-muted/50",
|
muted: "bg-muted/50",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "p-4 gap-4 ",
|
default: "p-4 gap-4 ",
|
||||||
sm: "py-3 px-4 gap-2.5",
|
sm: "py-3 px-4 gap-2.5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function Item({
|
function Item({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
size = "default",
|
size = "default",
|
||||||
asChild = false,
|
asChild = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> &
|
}: React.ComponentProps<"div"> &
|
||||||
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
|
||||||
const Comp = asChild ? Slot : "div";
|
const Comp = asChild ? Slot : "div";
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
data-slot="item"
|
data-slot="item"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(itemVariants({ variant, size, className }))}
|
className={cn(itemVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemMediaVariants = cva(
|
const itemMediaVariants = cva(
|
||||||
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5",
|
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: "bg-transparent",
|
||||||
icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
|
icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4",
|
||||||
image:
|
image:
|
||||||
"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
|
"size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function ItemMedia({
|
function ItemMedia({
|
||||||
className,
|
className,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
|
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-media"
|
data-slot="item-media"
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(itemMediaVariants({ variant, className }))}
|
className={cn(itemMediaVariants({ variant, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-content"
|
data-slot="item-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-title"
|
data-slot="item-title"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
|
"flex w-fit items-center gap-2 text-sm leading-snug font-medium",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
data-slot="item-description"
|
data-slot="item-description"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
|
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
|
||||||
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-actions"
|
data-slot="item-actions"
|
||||||
className={cn("flex items-center gap-2", className)}
|
className={cn("flex items-center gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-header"
|
data-slot="item-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="item-footer"
|
data-slot="item-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex basis-full items-center justify-between gap-2",
|
"flex basis-full items-center justify-between gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Item,
|
Item,
|
||||||
ItemMedia,
|
ItemMedia,
|
||||||
ItemContent,
|
ItemContent,
|
||||||
ItemActions,
|
ItemActions,
|
||||||
ItemGroup,
|
ItemGroup,
|
||||||
ItemSeparator,
|
ItemSeparator,
|
||||||
ItemTitle,
|
ItemTitle,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemHeader,
|
ItemHeader,
|
||||||
ItemFooter,
|
ItemFooter,
|
||||||
};
|
};
|
||||||
@@ -1,217 +1,217 @@
|
|||||||
:root {
|
:root {
|
||||||
--hue: 27;
|
--hue: 27;
|
||||||
--sat: 69%;
|
--sat: 69%;
|
||||||
--white: hsl(0, 0%, 100%);
|
--white: hsl(0, 0%, 100%);
|
||||||
--purple-primary: rgba(132, 0, 255, 1);
|
--purple-primary: rgba(132, 0, 255, 1);
|
||||||
--purple-glow: rgba(132, 0, 255, 0.2);
|
--purple-glow: rgba(132, 0, 255, 0.2);
|
||||||
--purple-border: rgba(132, 0, 255, 0.8);
|
--purple-border: rgba(132, 0, 255, 0.8);
|
||||||
--border-color: #392e4e;
|
--border-color: #392e4e;
|
||||||
--background-dark: #060010;
|
--background-dark: #060010;
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
max-width: 54em;
|
max-width: 54em;
|
||||||
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.5rem);
|
font-size: clamp(1rem, 0.9rem + 0.5vw, 1.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card {
|
.magic-bento-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
position: relative;
|
position: relative;
|
||||||
aspect-ratio: 4/3;
|
aspect-ratio: 4/3;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 1.25em;
|
padding: 1.25em;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
background: var(--background-dark);
|
background: var(--background-dark);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
--glow-x: 50%;
|
--glow-x: 50%;
|
||||||
--glow-y: 50%;
|
--glow-y: 50%;
|
||||||
--glow-intensity: 0;
|
--glow-intensity: 0;
|
||||||
--glow-radius: 200px;
|
--glow-radius: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card:hover {
|
.magic-bento-card:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__header,
|
.magic-bento-card__header,
|
||||||
.magic-bento-card__content {
|
.magic-bento-card__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__header {
|
.magic-bento-card__header {
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__content {
|
.magic-bento-card__content {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__label {
|
.magic-bento-card__label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__title,
|
.magic-bento-card__title,
|
||||||
.magic-bento-card__description {
|
.magic-bento-card__description {
|
||||||
--clamp-title: 1;
|
--clamp-title: 1;
|
||||||
--clamp-desc: 2;
|
--clamp-desc: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__title {
|
.magic-bento-card__title {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin: 0 0 0.25em;
|
margin: 0 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card__description {
|
.magic-bento-card__description {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card--text-autohide .magic-bento-card__title,
|
.magic-bento-card--text-autohide .magic-bento-card__title,
|
||||||
.magic-bento-card--text-autohide .magic-bento-card__description {
|
.magic-bento-card--text-autohide .magic-bento-card__description {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card--text-autohide .magic-bento-card__title {
|
.magic-bento-card--text-autohide .magic-bento-card__title {
|
||||||
-webkit-line-clamp: var(--clamp-title);
|
-webkit-line-clamp: var(--clamp-title);
|
||||||
line-clamp: var(--clamp-title);
|
line-clamp: var(--clamp-title);
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card--text-autohide .magic-bento-card__description {
|
.magic-bento-card--text-autohide .magic-bento-card__description {
|
||||||
-webkit-line-clamp: var(--clamp-desc);
|
-webkit-line-clamp: var(--clamp-desc);
|
||||||
line-clamp: var(--clamp-desc);
|
line-clamp: var(--clamp-desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 599px) {
|
@media (max-width: 599px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card {
|
.magic-bento-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 180px;
|
min-height: 180px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card:nth-child(3) {
|
.magic-bento-card:nth-child(3) {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
grid-row: span 2;
|
grid-row: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card:nth-child(4) {
|
.magic-bento-card:nth-child(4) {
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
grid-row: 2 / span 2;
|
grid-row: 2 / span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card:nth-child(6) {
|
.magic-bento-card:nth-child(6) {
|
||||||
grid-column: 4;
|
grid-column: 4;
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Border glow effect */
|
/* Border glow effect */
|
||||||
.magic-bento-card--border-glow::after {
|
.magic-bento-card--border-glow::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
var(--glow-radius) circle at var(--glow-x) var(--glow-y),
|
var(--glow-radius) circle at var(--glow-x) var(--glow-y),
|
||||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
|
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.8)) 0%,
|
||||||
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
|
rgba(132, 0, 255, calc(var(--glow-intensity) * 0.4)) 30%,
|
||||||
transparent 60%
|
transparent 60%
|
||||||
);
|
);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
-webkit-mask:
|
-webkit-mask:
|
||||||
linear-gradient(#fff 0 0) content-box,
|
linear-gradient(#fff 0 0) content-box,
|
||||||
linear-gradient(#fff 0 0);
|
linear-gradient(#fff 0 0);
|
||||||
-webkit-mask-composite: xor;
|
-webkit-mask-composite: xor;
|
||||||
mask:
|
mask:
|
||||||
linear-gradient(#fff 0 0) content-box,
|
linear-gradient(#fff 0 0) content-box,
|
||||||
linear-gradient(#fff 0 0);
|
linear-gradient(#fff 0 0);
|
||||||
mask-composite: exclude;
|
mask-composite: exclude;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card--border-glow:hover::after {
|
.magic-bento-card--border-glow:hover::after {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.magic-bento-card--border-glow:hover {
|
.magic-bento-card--border-glow:hover {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 4px 20px rgba(46, 24, 78, 0.4),
|
0 4px 20px rgba(46, 24, 78, 0.4),
|
||||||
0 0 30px var(--purple-glow);
|
0 0 30px var(--purple-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.particle-container {
|
.particle-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.particle::before {
|
.particle::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -2px;
|
top: -2px;
|
||||||
left: -2px;
|
left: -2px;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
background: rgba(132, 0, 255, 0.2);
|
background: rgba(132, 0, 255, 0.2);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.particle-container:hover {
|
.particle-container:hover {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 4px 20px rgba(46, 24, 78, 0.2),
|
0 4px 20px rgba(46, 24, 78, 0.2),
|
||||||
0 0 30px var(--purple-glow);
|
0 0 30px var(--purple-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global spotlight styles */
|
/* Global spotlight styles */
|
||||||
.global-spotlight {
|
.global-spotlight {
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
z-index: 200 !important;
|
z-index: 200 !important;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bento-section {
|
.bento-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,67 +1,67 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type ComponentPropsWithoutRef, useEffect, useRef } from "react";
|
import { type ComponentPropsWithoutRef, useEffect, useRef } from "react";
|
||||||
import { useInView, useMotionValue, useSpring } from "motion/react";
|
import { useInView, useMotionValue, useSpring } from "motion/react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface NumberTickerProps extends ComponentPropsWithoutRef<"span"> {
|
interface NumberTickerProps extends ComponentPropsWithoutRef<"span"> {
|
||||||
value: number;
|
value: number;
|
||||||
startValue?: number;
|
startValue?: number;
|
||||||
direction?: "up" | "down";
|
direction?: "up" | "down";
|
||||||
delay?: number;
|
delay?: number;
|
||||||
decimalPlaces?: number;
|
decimalPlaces?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NumberTicker({
|
export function NumberTicker({
|
||||||
value,
|
value,
|
||||||
startValue = 0,
|
startValue = 0,
|
||||||
direction = "up",
|
direction = "up",
|
||||||
delay = 0,
|
delay = 0,
|
||||||
className,
|
className,
|
||||||
decimalPlaces = 0,
|
decimalPlaces = 0,
|
||||||
...props
|
...props
|
||||||
}: NumberTickerProps) {
|
}: NumberTickerProps) {
|
||||||
const ref = useRef<HTMLSpanElement>(null);
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
const motionValue = useMotionValue(direction === "down" ? value : startValue);
|
const motionValue = useMotionValue(direction === "down" ? value : startValue);
|
||||||
const springValue = useSpring(motionValue, {
|
const springValue = useSpring(motionValue, {
|
||||||
damping: 60,
|
damping: 60,
|
||||||
stiffness: 100,
|
stiffness: 100,
|
||||||
});
|
});
|
||||||
const isInView = useInView(ref, { once: true, margin: "0px" });
|
const isInView = useInView(ref, { once: true, margin: "0px" });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInView) {
|
if (isInView) {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
motionValue.set(direction === "down" ? startValue : value);
|
motionValue.set(direction === "down" ? startValue : value);
|
||||||
}, delay * 1000);
|
}, delay * 1000);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [motionValue, isInView, delay, value, direction, startValue]);
|
}, [motionValue, isInView, delay, value, direction, startValue]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() =>
|
() =>
|
||||||
springValue.on("change", (latest) => {
|
springValue.on("change", (latest) => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.textContent = Intl.NumberFormat("en-US", {
|
ref.current.textContent = Intl.NumberFormat("en-US", {
|
||||||
minimumFractionDigits: decimalPlaces,
|
minimumFractionDigits: decimalPlaces,
|
||||||
maximumFractionDigits: decimalPlaces,
|
maximumFractionDigits: decimalPlaces,
|
||||||
}).format(Number(latest.toFixed(decimalPlaces)));
|
}).format(Number(latest.toFixed(decimalPlaces)));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[springValue, decimalPlaces],
|
[springValue, decimalPlaces],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-block tracking-wider text-black tabular-nums dark:text-white",
|
"inline-block tracking-wider text-black tabular-nums dark:text-white",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{startValue}
|
{startValue}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Progress({
|
function Progress({
|
||||||
className,
|
className,
|
||||||
value,
|
value,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
data-slot="progress"
|
data-slot="progress"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
data-slot="progress-indicator"
|
data-slot="progress-indicator"
|
||||||
className="bg-primary h-full w-full flex-1 transition-all"
|
className="bg-primary h-full w-full flex-1 transition-all"
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Progress };
|
export { Progress };
|
||||||
@@ -1,82 +1,82 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { GripVerticalIcon } from "lucide-react";
|
import { GripVerticalIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ResizablePrimitive from "react-resizable-panels";
|
import * as ResizablePrimitive from "react-resizable-panels";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function ResizablePanelGroup({
|
function ResizablePanelGroup({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ResizablePrimitive.Group>) {
|
}: React.ComponentProps<typeof ResizablePrimitive.Group>) {
|
||||||
return (
|
return (
|
||||||
<ResizablePrimitive.Group
|
<ResizablePrimitive.Group
|
||||||
data-slot="resizable-panel-group"
|
data-slot="resizable-panel-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// react-resizable-panels v4 changed size semantics: a bare *number* is now
|
// react-resizable-panels v4 changed size semantics: a bare *number* is now
|
||||||
// interpreted as PIXELS, while a unit-less *string* is a percentage. Every call
|
// interpreted as PIXELS, while a unit-less *string* is a percentage. Every call
|
||||||
// site in this app passes sizes as percentages (the v2/v3 convention), so a
|
// site in this app passes sizes as percentages (the v2/v3 convention), so a
|
||||||
// value like `maxSize={60}` would otherwise pin a panel to 60px and freeze the
|
// value like `maxSize={60}` would otherwise pin a panel to 60px and freeze the
|
||||||
// layout/drag. Normalize numbers to "<n>%" to preserve the intended percentage.
|
// layout/drag. Normalize numbers to "<n>%" to preserve the intended percentage.
|
||||||
const asPercent = (v: number | string | undefined) =>
|
const asPercent = (v: number | string | undefined) =>
|
||||||
typeof v === "number" ? `${v}%` : v;
|
typeof v === "number" ? `${v}%` : v;
|
||||||
|
|
||||||
function ResizablePanel({
|
function ResizablePanel({
|
||||||
defaultSize,
|
defaultSize,
|
||||||
minSize,
|
minSize,
|
||||||
maxSize,
|
maxSize,
|
||||||
collapsedSize,
|
collapsedSize,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
||||||
return (
|
return (
|
||||||
<ResizablePrimitive.Panel
|
<ResizablePrimitive.Panel
|
||||||
data-slot="resizable-panel"
|
data-slot="resizable-panel"
|
||||||
defaultSize={asPercent(defaultSize)}
|
defaultSize={asPercent(defaultSize)}
|
||||||
minSize={asPercent(minSize)}
|
minSize={asPercent(minSize)}
|
||||||
maxSize={asPercent(maxSize)}
|
maxSize={asPercent(maxSize)}
|
||||||
collapsedSize={asPercent(collapsedSize)}
|
collapsedSize={asPercent(collapsedSize)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResizableHandle({
|
function ResizableHandle({
|
||||||
withHandle,
|
withHandle,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ResizablePrimitive.Separator> & {
|
}: React.ComponentProps<typeof ResizablePrimitive.Separator> & {
|
||||||
withHandle?: boolean;
|
withHandle?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ResizablePrimitive.Separator
|
<ResizablePrimitive.Separator
|
||||||
data-slot="resizable-handle"
|
data-slot="resizable-handle"
|
||||||
className={cn(
|
className={cn(
|
||||||
// Thin visible divider, but a generous invisible hit area on both sides
|
// Thin visible divider, but a generous invisible hit area on both sides
|
||||||
// (after:w-4 → 16px) so the handle is easy to grab and drag. Hovering /
|
// (after:w-4 → 16px) so the handle is easy to grab and drag. Hovering /
|
||||||
// dragging tints the divider so it's obvious it's interactive.
|
// dragging tints the divider so it's obvious it's interactive.
|
||||||
"bg-border hover:bg-primary/60 active:bg-primary relative flex w-px items-center justify-center transition-colors",
|
"bg-border hover:bg-primary/60 active:bg-primary relative flex w-px items-center justify-center transition-colors",
|
||||||
"after:absolute after:inset-y-0 after:left-1/2 after:w-4 after:-translate-x-1/2 after:cursor-col-resize",
|
"after:absolute after:inset-y-0 after:left-1/2 after:w-4 after:-translate-x-1/2 after:cursor-col-resize",
|
||||||
"focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden",
|
"focus-visible:ring-ring focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{withHandle && (
|
{withHandle && (
|
||||||
<div className="bg-border z-10 flex h-8 w-4 items-center justify-center rounded-sm border shadow-sm">
|
<div className="bg-border z-10 flex h-8 w-4 items-center justify-center rounded-sm border shadow-sm">
|
||||||
<GripVerticalIcon className="size-3.5" />
|
<GripVerticalIcon className="size-3.5" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ResizablePrimitive.Separator>
|
</ResizablePrimitive.Separator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
|
export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
|
||||||
@@ -1,58 +1,58 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function ScrollArea({
|
function ScrollArea({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
data-slot="scroll-area"
|
data-slot="scroll-area"
|
||||||
className={cn("relative", className)}
|
className={cn("relative", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.Viewport
|
<ScrollAreaPrimitive.Viewport
|
||||||
data-slot="scroll-area-viewport"
|
data-slot="scroll-area-viewport"
|
||||||
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollBar />
|
<ScrollBar />
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScrollBar({
|
function ScrollBar({
|
||||||
className,
|
className,
|
||||||
orientation = "vertical",
|
orientation = "vertical",
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||||
return (
|
return (
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
data-slot="scroll-area-scrollbar"
|
data-slot="scroll-area-scrollbar"
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none p-px transition-colors select-none",
|
"flex touch-none p-px transition-colors select-none",
|
||||||
orientation === "vertical" &&
|
orientation === "vertical" &&
|
||||||
"h-full w-2.5 border-l border-l-transparent",
|
"h-full w-2.5 border-l border-l-transparent",
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent",
|
"h-2.5 flex-col border-t border-t-transparent",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||||
data-slot="scroll-area-thumb"
|
data-slot="scroll-area-thumb"
|
||||||
className="bg-border relative flex-1 rounded-full"
|
className="bg-border relative flex-1 rounded-full"
|
||||||
/>
|
/>
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar };
|
export { ScrollArea, ScrollBar };
|
||||||
@@ -1,198 +1,198 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Select({
|
function Select({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectGroup({
|
function SelectGroup({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectValue({
|
function SelectValue({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectTrigger = React.forwardRef<
|
const SelectTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
|
||||||
size?: "sm" | "default";
|
size?: "sm" | "default";
|
||||||
}
|
}
|
||||||
>(({ className, size = "default", children, ...props }, ref) => {
|
>(({ className, size = "default", children, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEE2] dark:data-[placeholder]:text-[#566B7F] dark:hover:bg-[#022248] dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
"dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEE2] dark:data-[placeholder]:text-[#566B7F] dark:hover:bg-[#022248] dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<SelectPrimitive.Icon asChild>
|
<SelectPrimitive.Icon asChild>
|
||||||
<ChevronDownIcon className="size-4 opacity-50" />
|
<ChevronDownIcon className="size-4 opacity-50" />
|
||||||
</SelectPrimitive.Icon>
|
</SelectPrimitive.Icon>
|
||||||
</SelectPrimitive.Trigger>
|
</SelectPrimitive.Trigger>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const SelectContent = React.forwardRef<
|
const SelectContent = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => {
|
>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Portal>
|
<SelectPrimitive.Portal>
|
||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF]",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF]",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
align={align}
|
align={align}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SelectScrollUpButton />
|
<SelectScrollUpButton />
|
||||||
<SelectPrimitive.Viewport
|
<SelectPrimitive.Viewport
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SelectPrimitive.Viewport>
|
</SelectPrimitive.Viewport>
|
||||||
<SelectScrollDownButton />
|
<SelectScrollDownButton />
|
||||||
</SelectPrimitive.Content>
|
</SelectPrimitive.Content>
|
||||||
</SelectPrimitive.Portal>
|
</SelectPrimitive.Portal>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
function SelectLabel({
|
function SelectLabel({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Label
|
<SelectPrimitive.Label
|
||||||
data-slot="select-label"
|
data-slot="select-label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground px-2 py-1.5 text-xs dark:text-[#566B7F]",
|
"text-muted-foreground px-2 py-1.5 text-xs dark:text-[#566B7F]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectItem = React.forwardRef<
|
const SelectItem = React.forwardRef<
|
||||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
"dark:text-[#AACEFF] dark:focus:bg-[#0062D9] dark:focus:text-white dark:data-[highlighted]:bg-[#0062D9] dark:data-[highlighted]:text-white dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus:[&_svg:not([class*='text-'])]:text-white dark:data-[highlighted]:[&_svg:not([class*='text-'])]:text-white",
|
"dark:text-[#AACEFF] dark:focus:bg-[#0062D9] dark:focus:text-white dark:data-[highlighted]:bg-[#0062D9] dark:data-[highlighted]:text-white dark:[&_svg:not([class*='text-'])]:text-[#AACEFF] dark:focus:[&_svg:not([class*='text-'])]:text-white dark:data-[highlighted]:[&_svg:not([class*='text-'])]:text-white",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-slot="select-item-indicator"
|
data-slot="select-item-indicator"
|
||||||
className="absolute right-2 flex size-3.5 items-center justify-center"
|
className="absolute right-2 flex size-3.5 items-center justify-center"
|
||||||
>
|
>
|
||||||
<SelectPrimitive.ItemIndicator>
|
<SelectPrimitive.ItemIndicator>
|
||||||
<CheckIcon className="size-4" />
|
<CheckIcon className="size-4" />
|
||||||
</SelectPrimitive.ItemIndicator>
|
</SelectPrimitive.ItemIndicator>
|
||||||
</span>
|
</span>
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
</SelectPrimitive.Item>
|
</SelectPrimitive.Item>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
function SelectSeparator({
|
function SelectSeparator({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.Separator
|
<SelectPrimitive.Separator
|
||||||
data-slot="select-separator"
|
data-slot="select-separator"
|
||||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
function SelectScrollUpButton({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollUpButton
|
<SelectPrimitive.ScrollUpButton
|
||||||
data-slot="select-scroll-up-button"
|
data-slot="select-scroll-up-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronUpIcon className="size-4" />
|
<ChevronUpIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollUpButton>
|
</SelectPrimitive.ScrollUpButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
function SelectScrollDownButton({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||||
return (
|
return (
|
||||||
<SelectPrimitive.ScrollDownButton
|
<SelectPrimitive.ScrollDownButton
|
||||||
data-slot="select-scroll-down-button"
|
data-slot="select-scroll-down-button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
"flex cursor-default items-center justify-center py-1 dark:text-[#AACEFF]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
</SelectPrimitive.ScrollDownButton>
|
</SelectPrimitive.ScrollDownButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectGroup,
|
SelectGroup,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectLabel,
|
SelectLabel,
|
||||||
SelectScrollDownButton,
|
SelectScrollDownButton,
|
||||||
SelectScrollUpButton,
|
SelectScrollUpButton,
|
||||||
SelectSeparator,
|
SelectSeparator,
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
};
|
};
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Separator({
|
function Separator({
|
||||||
className,
|
className,
|
||||||
orientation = "horizontal",
|
orientation = "horizontal",
|
||||||
decorative = true,
|
decorative = true,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
return (
|
return (
|
||||||
<SeparatorPrimitive.Root
|
<SeparatorPrimitive.Root
|
||||||
data-slot="separator"
|
data-slot="separator"
|
||||||
decorative={decorative}
|
decorative={decorative}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Separator };
|
export { Separator };
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SheetTrigger = React.forwardRef<
|
const SheetTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof SheetPrimitive.Trigger>,
|
React.ElementRef<typeof SheetPrimitive.Trigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Trigger>
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Trigger>
|
||||||
@@ -18,7 +18,7 @@ const SheetTrigger = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
SheetTrigger.displayName = SheetPrimitive.Trigger.displayName;
|
SheetTrigger.displayName = SheetPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const SheetClose = React.forwardRef<
|
const SheetClose = React.forwardRef<
|
||||||
React.ElementRef<typeof SheetPrimitive.Close>,
|
React.ElementRef<typeof SheetPrimitive.Close>,
|
||||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Close>
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Close>
|
||||||
@@ -27,13 +27,13 @@ const SheetClose = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
SheetClose.displayName = SheetPrimitive.Close.displayName;
|
SheetClose.displayName = SheetPrimitive.Close.displayName;
|
||||||
|
|
||||||
function SheetPortal({
|
function SheetPortal({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SheetOverlay = React.forwardRef<
|
const SheetOverlay = React.forwardRef<
|
||||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
@@ -52,7 +52,7 @@ const SheetOverlay = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
const SheetContent = React.forwardRef<
|
const SheetContent = React.forwardRef<
|
||||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> & {
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> & {
|
||||||
@@ -90,60 +90,60 @@ const SheetContent = React.forwardRef<
|
|||||||
});
|
});
|
||||||
|
|
||||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||||
|
|
||||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="sheet-header"
|
data-slot="sheet-header"
|
||||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="sheet-footer"
|
data-slot="sheet-footer"
|
||||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetTitle({
|
function SheetTitle({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
|
||||||
return (
|
return (
|
||||||
<SheetPrimitive.Title
|
<SheetPrimitive.Title
|
||||||
data-slot="sheet-title"
|
data-slot="sheet-title"
|
||||||
className={cn("text-foreground font-semibold", className)}
|
className={cn("text-foreground font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SheetDescription({
|
function SheetDescription({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
|
||||||
return (
|
return (
|
||||||
<SheetPrimitive.Description
|
<SheetPrimitive.Description
|
||||||
data-slot="sheet-description"
|
data-slot="sheet-description"
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
className={cn("text-muted-foreground text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
SheetClose,
|
SheetClose,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetFooter,
|
SheetFooter,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
SheetDescription,
|
SheetDescription,
|
||||||
};
|
};
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface ShineBorderProps extends React.HTMLAttributes<HTMLDivElement> {
|
interface ShineBorderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
/**
|
/**
|
||||||
* Width of the border in pixels
|
* Width of the border in pixels
|
||||||
* @default 1
|
* @default 1
|
||||||
*/
|
*/
|
||||||
borderWidth?: number;
|
borderWidth?: number;
|
||||||
/**
|
/**
|
||||||
* Duration of the animation in seconds
|
* Duration of the animation in seconds
|
||||||
* @default 14
|
* @default 14
|
||||||
*/
|
*/
|
||||||
duration?: number;
|
duration?: number;
|
||||||
/**
|
/**
|
||||||
* Color of the border, can be a single color or an array of colors
|
* Color of the border, can be a single color or an array of colors
|
||||||
* @default "#000000"
|
* @default "#000000"
|
||||||
*/
|
*/
|
||||||
shineColor?: string | string[];
|
shineColor?: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shine Border
|
* Shine Border
|
||||||
*
|
*
|
||||||
* An animated background border effect component with configurable properties.
|
* An animated background border effect component with configurable properties.
|
||||||
*/
|
*/
|
||||||
export function ShineBorder({
|
export function ShineBorder({
|
||||||
borderWidth = 1,
|
borderWidth = 1,
|
||||||
duration = 14,
|
duration = 14,
|
||||||
shineColor = "#000000",
|
shineColor = "#000000",
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
...props
|
...props
|
||||||
}: ShineBorderProps) {
|
}: ShineBorderProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--border-width": `${borderWidth}px`,
|
"--border-width": `${borderWidth}px`,
|
||||||
"--duration": `${duration}s`,
|
"--duration": `${duration}s`,
|
||||||
backgroundImage: `radial-gradient(transparent,transparent, ${
|
backgroundImage: `radial-gradient(transparent,transparent, ${
|
||||||
Array.isArray(shineColor) ? shineColor.join(",") : shineColor
|
Array.isArray(shineColor) ? shineColor.join(",") : shineColor
|
||||||
},transparent,transparent)`,
|
},transparent,transparent)`,
|
||||||
backgroundSize: "300% 300%",
|
backgroundSize: "300% 300%",
|
||||||
mask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
|
mask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
|
||||||
WebkitMask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
|
WebkitMask: `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
|
||||||
WebkitMaskComposite: "xor",
|
WebkitMaskComposite: "xor",
|
||||||
maskComposite: "exclude",
|
maskComposite: "exclude",
|
||||||
padding: "var(--border-width)",
|
padding: "var(--border-width)",
|
||||||
...style,
|
...style,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
className={cn(
|
className={cn(
|
||||||
"motion-safe:animate-shine pointer-events-none absolute inset-0 size-full rounded-[inherit] will-change-[background-position]",
|
"motion-safe:animate-shine pointer-events-none absolute inset-0 size-full rounded-[inherit] will-change-[background-position]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,13 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="skeleton"
|
data-slot="skeleton"
|
||||||
className={cn("bg-accent animate-pulse rounded-md", className)}
|
className={cn("bg-accent animate-pulse rounded-md", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Skeleton };
|
export { Skeleton };
|
||||||
@@ -1,40 +1,40 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
Loader2Icon,
|
Loader2Icon,
|
||||||
OctagonXIcon,
|
OctagonXIcon,
|
||||||
TriangleAlertIcon,
|
TriangleAlertIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme();
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps["theme"]}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
icons={{
|
icons={{
|
||||||
success: <CircleCheckIcon className="size-4" />,
|
success: <CircleCheckIcon className="size-4" />,
|
||||||
info: <InfoIcon className="size-4" />,
|
info: <InfoIcon className="size-4" />,
|
||||||
warning: <TriangleAlertIcon className="size-4" />,
|
warning: <TriangleAlertIcon className="size-4" />,
|
||||||
error: <OctagonXIcon className="size-4" />,
|
error: <OctagonXIcon className="size-4" />,
|
||||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||||
}}
|
}}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--normal-bg": "var(--popover)",
|
"--normal-bg": "var(--popover)",
|
||||||
"--normal-text": "var(--popover-foreground)",
|
"--normal-text": "var(--popover-foreground)",
|
||||||
"--normal-border": "var(--border)",
|
"--normal-border": "var(--border)",
|
||||||
"--border-radius": "var(--radius)",
|
"--border-radius": "var(--radius)",
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Toaster };
|
export { Toaster };
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
.card-spotlight {
|
.card-spotlight {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
border: 1px solid #222;
|
border: 1px solid #222;
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
--mouse-x: 50%;
|
--mouse-x: 50%;
|
||||||
--mouse-y: 50%;
|
--mouse-y: 50%;
|
||||||
--spotlight-color: rgba(255, 255, 255, 0.05);
|
--spotlight-color: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-spotlight::before {
|
.card-spotlight::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: radial-gradient(
|
background: radial-gradient(
|
||||||
circle at var(--mouse-x) var(--mouse-y),
|
circle at var(--mouse-x) var(--mouse-y),
|
||||||
var(--spotlight-color),
|
var(--spotlight-color),
|
||||||
transparent 80%
|
transparent 80%
|
||||||
);
|
);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.5s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-spotlight:hover::before,
|
.card-spotlight:hover::before,
|
||||||
.card-spotlight:focus-within::before {
|
.card-spotlight:focus-within::before {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
Loaded 100 of 1019 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user