Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30796c6f81 | ||
|
|
86b5eb1818 | ||
|
|
696405a8bf | ||
|
|
a2c2eab6b3 | ||
|
|
ead9ee81fb | ||
|
|
bd140af714 | ||
|
|
96008fa78f | ||
|
|
da4d0f515c | ||
|
|
67e6a22da9 | ||
|
|
b791fb1494 | ||
|
|
9e329bf19f | ||
|
|
3906e8a942 | ||
|
|
f74faea065 | ||
|
|
7d3759acce | ||
|
|
172962f711 | ||
|
|
36109e3b88 | ||
|
|
7c63b7c062 | ||
|
|
1efaf048f5 | ||
|
|
ac52714498 | ||
|
|
c3de89f504 | ||
|
|
6f6a645d0a | ||
|
|
5605352144 | ||
|
|
ef433f446d | ||
|
|
fe8f200364 | ||
|
|
be3a030302 | ||
|
|
cd6d4488d1 | ||
|
|
da3a5df5c3 | ||
|
|
4864407d61 | ||
|
|
f6f7879d6e | ||
|
|
dab1f14a59 | ||
|
|
51ca914206 | ||
|
|
933ffb8ca2 | ||
|
|
2f5f20a257 | ||
|
|
92544f89f3 | ||
|
|
f1cf1acbfa | ||
|
|
f22008389c | ||
|
|
91de372cbc | ||
|
|
811bd94934 | ||
|
|
5987488b3d | ||
|
|
966c9e972d | ||
|
|
464261a1b7 | ||
|
|
363dcef81a | ||
|
|
4439617c50 | ||
|
|
5d2fa3d65d | ||
|
|
8930ab0af6 | ||
|
|
6af6fe22fc | ||
|
|
dd577a7843 | ||
|
|
f772155674 | ||
|
|
3ce204338a | ||
|
|
168bc9ef45 | ||
|
|
bfc3d4888b | ||
|
|
0148b115db | ||
|
|
0b11953f6f | ||
|
|
6e210faacf | ||
|
|
4ef755e10b | ||
|
|
62b7cc5509 | ||
|
|
0e1ec05110 | ||
|
|
dd38fc74db | ||
|
|
d619d7ac76 | ||
|
|
37f7511ba0 | ||
|
|
afd03ba1b5 | ||
|
|
ac7b02d28a | ||
|
|
4a0bdbf779 | ||
|
|
164dada46a | ||
|
|
fd1c4ec792 | ||
|
|
add000d221 | ||
|
|
def598f728 | ||
|
|
0294885fbf | ||
|
|
c0ec4338f8 | ||
|
|
fb7f6f73bd | ||
|
|
e04c38e557 | ||
|
|
bfb5420733 | ||
|
|
9e6fc32903 | ||
|
|
217ceb1c7a | ||
|
|
846f89664e | ||
|
|
0e30f69e96 | ||
|
|
af9eb538f4 | ||
|
|
66a0beea33 | ||
|
|
411e73bb35 | ||
|
|
852e222f5d | ||
|
|
054ceab4f4 | ||
|
|
d9149ff572 | ||
|
|
7f331690a0 | ||
|
|
a5698dfff0 | ||
|
|
1e5866da06 | ||
|
|
3f7b7da184 | ||
|
|
c0020c8769 | ||
|
|
1c5e678cef | ||
|
|
3ab1b8ebc9 | ||
|
|
7635c68ccd | ||
|
|
a3b562b895 | ||
|
|
94d1493195 | ||
|
|
3a5bbe32b9 | ||
|
|
7e712c5003 | ||
|
|
31328e092e | ||
|
|
29671f48a8 | ||
|
|
61c2782491 | ||
|
|
15263b5223 | ||
|
|
557d1ee5b0 | ||
|
|
777265c730 | ||
|
|
e76a8d547b | ||
|
|
ff98f84f18 | ||
|
|
d0b4fa5b28 | ||
|
|
91b80f7c49 | ||
|
|
47f409246d | ||
|
|
be1ff83297 | ||
|
|
297c9e0085 | ||
|
|
ec95917b4d | ||
|
|
98bfe863fe | ||
|
|
af3d00662e | ||
|
|
39803fb0df | ||
|
|
4dad5ea6c1 | ||
|
|
ba8cb8c6f8 | ||
|
|
64f10b28e7 | ||
|
|
7f34447ace | ||
|
|
c2e000f937 | ||
|
|
831ab6d99b | ||
|
|
b246d9e567 | ||
|
|
919c2ffd46 | ||
|
|
e6d63a811e | ||
|
|
92227098b7 | ||
|
|
571080c1d6 | ||
|
|
d07feb837d | ||
|
|
1c823c4255 | ||
|
|
63e378524b | ||
|
|
ea5f54e421 | ||
|
|
ee18fbebae | ||
|
|
fa56d06e0c | ||
|
|
72f7514114 | ||
|
|
44e1f00ffc | ||
|
|
e7054ea13f | ||
|
|
4a3ac2cfcd | ||
|
|
bbc31e01c4 | ||
|
|
ba86b34a8c | ||
|
|
770a198772 | ||
|
|
a26bee3696 | ||
|
|
abb8f4f963 | ||
|
|
43d2adff70 | ||
|
|
474eeb9e68 | ||
|
|
e2dffea423 | ||
|
|
3d5eaba46f | ||
|
|
1bb8fbd56f | ||
|
|
b699886953 | ||
|
|
12c957d48b | ||
|
|
48e890c1b1 | ||
|
|
05331f001a | ||
|
|
bde260b330 | ||
|
|
59ef51ea20 | ||
|
|
aa77bfdfc4 | ||
|
|
a2baef8911 | ||
|
|
3fcfb65d45 | ||
|
|
8f266bea0d | ||
|
|
0a765f4aac | ||
|
|
ee3f0555d1 | ||
|
|
0c0386f4ba | ||
|
|
6c2b1cb589 | ||
|
|
9fc1285c09 | ||
|
|
e6e8ccee50 | ||
|
|
f9632af016 | ||
|
|
81359bf419 | ||
|
|
a144570ed7 | ||
|
|
0732b72e8f | ||
|
|
8332445a52 | ||
|
|
c9111d1bfc | ||
|
|
e7d999278d | ||
|
|
4043d20ea6 | ||
|
|
afe88ccfd6 | ||
|
|
56c3a04809 | ||
|
|
4530345a1f | ||
|
|
4a89ec8515 | ||
|
|
cb5f1c6485 | ||
|
|
15c0677a4b | ||
|
|
dc68b8148a | ||
|
|
1976034f12 | ||
|
|
727621fdac | ||
|
|
0f3806d358 | ||
|
|
dfb2bf3923 | ||
|
|
bf08a91191 | ||
|
|
f0e353cc1a | ||
|
|
456b1eddcc | ||
|
|
5e08f05437 | ||
|
|
9f9227bae4 | ||
|
|
76f47bce37 | ||
|
|
e1f54b30dc | ||
|
|
fc4e7e6c05 | ||
|
|
7aaa79b644 | ||
|
|
9b6f894be2 | ||
|
|
48f7ef2023 | ||
|
|
54692859fd | ||
|
|
1719244015 | ||
|
|
48f8c6c5b7 | ||
|
|
f0815e595b | ||
|
|
4f829017ed | ||
|
|
d2f8f05acb | ||
|
|
e8b1156bfd | ||
|
|
bf748773c8 | ||
|
|
a05b25ae90 | ||
|
|
3ceb3feccf | ||
|
|
b4944f23ef | ||
|
|
09c17b2dc5 | ||
|
|
6f7ff9f858 | ||
|
|
9e9af2a031 | ||
|
|
7d74e853ae | ||
|
|
1d627d570d | ||
|
|
9b0088f4f0 | ||
|
|
b50c7dbed4 | ||
|
|
f92c0313a6 | ||
|
|
233e1ee0a6 | ||
|
|
805b40ec76 | ||
|
|
86ca3e5be0 | ||
|
|
bb2143d9a0 | ||
|
|
ef662e0ccf | ||
|
|
30708a59f1 | ||
|
|
b0262cfe72 | ||
|
|
259886c1a8 | ||
|
|
f1b4b33727 | ||
|
|
38e8b690bd | ||
|
|
9a52b43e09 | ||
|
|
5e35cfc44a | ||
|
|
ac0ec6dfe6 | ||
|
|
361defc348 | ||
|
|
b9fc0da215 | ||
|
|
6ac8947168 | ||
|
|
5655930d12 | ||
|
|
18acc37324 | ||
|
|
e9155d719e | ||
|
|
16e77b02c2 | ||
|
|
adc1902649 | ||
|
|
1cc470bc5a | ||
|
|
b3f6efc7d0 | ||
|
|
d91fbf3ff4 | ||
|
|
e552593474 | ||
|
|
1f5a78f458 | ||
|
|
f490c15282 | ||
|
|
90a31debd1 | ||
|
|
416145a90e | ||
|
|
6285786282 | ||
|
|
f9f6a69c89 | ||
|
|
61da60bdf1 | ||
|
|
2d5ef3011a | ||
|
|
63dab6d224 | ||
|
|
38dd76dfb3 | ||
|
|
fe642de526 | ||
|
|
f4b7eefc38 | ||
|
|
5bbbad02b4 | ||
|
|
bd85dd5faf | ||
|
|
d91ba3d219 | ||
|
|
7992c40815 | ||
|
|
574b01db97 | ||
|
|
2d6049b7bd | ||
|
|
96d0f2e6f9 | ||
|
|
b46bd4d0dd | ||
|
|
244c76793c | ||
|
|
411fc46189 | ||
|
|
48427e330f | ||
|
|
5ddc2451c1 | ||
|
|
e357cad2bb | ||
|
|
80be8b2eaa | ||
|
|
9a5aaea8ca | ||
|
|
7d585615a9 | ||
|
|
3f3375dcac | ||
|
|
f555eae653 | ||
|
|
e805a6e037 | ||
|
|
3ed82ed7bb | ||
|
|
d638e512c9 | ||
|
|
204b966935 | ||
|
|
8318ba5e55 | ||
|
|
d39c595828 | ||
|
|
04154162c6 | ||
|
|
47f8e0f04c | ||
|
|
7e4ada5f4b | ||
|
|
b0b4f87848 | ||
|
|
d9085b82bf | ||
|
|
8e5770ff59 | ||
|
|
beb420c69d | ||
|
|
1d8b898924 | ||
|
|
b5b4c5ced7 | ||
|
|
b4e9d77895 | ||
|
|
21a08b3c7a | ||
|
|
6977d6bf73 | ||
|
|
39f81dd0c0 | ||
|
|
b3191f5e0b | ||
|
|
78f2f21142 | ||
|
|
497fc1d1ae | ||
|
|
d83e1c6fb2 | ||
|
|
c5218d485a | ||
|
|
19cbd3b8da | ||
|
|
c0c5630bfb | ||
|
|
969b5fcfbb | ||
|
|
0739fa2195 | ||
|
|
3c62adab3c | ||
|
|
edc601c35e | ||
|
|
951370eee7 | ||
|
|
fa81f4a150 | ||
|
|
e6a90c40fb | ||
|
|
2e0a1c00fa | ||
|
|
109754fac9 | ||
|
|
c3fbff3d7a | ||
|
|
6dd967fe24 | ||
|
|
f0bfdba944 | ||
|
|
7f76b5f4ee | ||
|
|
35f4d8f15a | ||
|
|
783f17e9e8 | ||
|
|
9e305c2840 | ||
|
|
78fa5d3110 | ||
|
|
d57482d966 | ||
|
|
3d7c2aff4b | ||
|
|
3757c5dde7 | ||
|
|
13c0a966ec | ||
|
|
d987619946 | ||
|
|
8fb9498fe6 | ||
|
|
b8f64a6693 | ||
|
|
e31bb047bf | ||
|
|
6897b813a6 | ||
|
|
83f86406b5 | ||
|
|
dde237ff0d | ||
|
|
6a4887e252 | ||
|
|
d945b94910 | ||
|
|
bc7dcf791d | ||
|
|
66c71fe3b7 | ||
|
|
16a7aeec3b | ||
|
|
94605345e5 | ||
|
|
ec9fa32c81 | ||
|
|
264aa15281 | ||
|
|
180a6ff824 | ||
|
|
5904ee1571 | ||
|
|
bc99341f85 | ||
|
|
8b9174b67e | ||
|
|
6c04a6cf69 | ||
|
|
14a63c4b97 | ||
|
|
098d13a34b | ||
|
|
488f247744 | ||
|
|
c74dbf03cd | ||
|
|
1f836c3235 | ||
|
|
bd2e2e77d5 | ||
|
|
6282ef14e8 | ||
|
|
558f23688f | ||
|
|
39fe057f9f | ||
|
|
541f2de599 | ||
|
|
68116254cd | ||
|
|
77ff4599ea | ||
|
|
652f5a3247 | ||
|
|
9b6e57b143 | ||
|
|
bdc19256bf | ||
|
|
de5eaf4467 | ||
|
|
ca884833d1 | ||
|
|
9fc164a69d | ||
|
|
0da2da83a5 | ||
|
|
bfdfa44edd | ||
|
|
ed1cfe5eb6 | ||
|
|
b696f3f5db | ||
|
|
7a0cf5ef9a | ||
|
|
d1b7cb6a36 | ||
|
|
b21d699472 | ||
|
|
029fadd778 | ||
|
|
215d50235b | ||
|
|
000e676397 | ||
|
|
208c045d3d | ||
|
|
ee20f59a8e | ||
|
|
925cf3dabb | ||
|
|
0049d0e926 | ||
|
|
56959808a9 | ||
|
|
8667a9680e | ||
|
|
8c7d129089 | ||
|
|
a3a9b98a1d | ||
|
|
f2f66579cb | ||
|
|
1354cf2a87 | ||
|
|
15f5592429 | ||
|
|
dc69094f00 | ||
|
|
8ad3783a36 | ||
|
|
15d5dd8e92 | ||
|
|
b07edb977b | ||
|
|
1509fc2039 | ||
|
|
2877bb8faa | ||
|
|
a83410cb49 | ||
|
|
4ff9f2235b | ||
|
|
1bc611f097 | ||
|
|
4e0a3941ff | ||
|
|
4e885dac3e | ||
|
|
eafd131a22 | ||
|
|
289ce3716b | ||
|
|
2242070bdc | ||
|
|
ee83cea11c | ||
|
|
6e571a43d8 | ||
|
|
466a0245a9 | ||
|
|
d9ce5e4ce7 | ||
|
|
6b0dd291c2 | ||
|
|
4ff77d0d7e | ||
|
|
e00da5d8d2 | ||
|
|
1a525a59ab | ||
|
|
d5581c4236 | ||
|
|
4bf950574c | ||
|
|
0b9e434c4b | ||
|
|
c4b1466643 | ||
|
|
488842029b | ||
|
|
1b0ce59371 | ||
|
|
97bea89204 | ||
|
|
0892dfc601 | ||
|
|
20d66ff401 | ||
|
|
e2fda532d1 | ||
|
|
68035af7b3 | ||
|
|
ee25555f0e | ||
|
|
e528510f5c | ||
|
|
afa0527b4c | ||
|
|
dd7d23498f | ||
|
|
eaaea2b24c | ||
|
|
58d0aefae1 | ||
|
|
11c9915a70 | ||
|
|
6cf6b0d645 | ||
|
|
1980a39b73 | ||
|
|
8d3fbeeb22 | ||
|
|
2678644c2c | ||
|
|
25da9fe119 | ||
|
|
03d89d22ec | ||
|
|
100cd25ffc | ||
|
|
6284a74a6e | ||
|
|
978cd8ba8e | ||
|
|
c76f28824c | ||
|
|
703ecfae40 | ||
|
|
b15c59aa48 | ||
|
|
98a4ce701c | ||
|
|
2fc0de8f4e | ||
|
|
66fd551d68 | ||
|
|
6e4e9a002b | ||
|
|
91278b0b3d | ||
|
|
ff9b3ed1a2 | ||
|
|
0c855d4ba8 | ||
|
|
9176e9d255 | ||
|
|
2bab917b3e | ||
|
|
b80086a349 | ||
|
|
8a4fca9bd5 | ||
|
|
13288a6b3a | ||
|
|
57ed552f43 | ||
|
|
5a4d65986d | ||
|
|
427b3bb774 | ||
|
|
4912f385c6 | ||
|
|
dcb6d0feab | ||
|
|
de8c762d79 | ||
|
|
056659304a | ||
|
|
525bf18a03 | ||
|
|
7714c3f611 | ||
|
|
768b9fb7c1 | ||
|
|
db4f028955 | ||
|
|
e3928ffbcc | ||
|
|
db5159bd5e | ||
|
|
2626e1ebf0 | ||
|
|
5d69ba0796 | ||
|
|
c39ad97b8e | ||
|
|
7c73f060ed | ||
|
|
f0aa7df3b0 | ||
|
|
5882dec23a | ||
|
|
835c9deb28 | ||
|
|
a4ecc7ac7f | ||
|
|
526a195e4d | ||
|
|
5e77560313 | ||
|
|
1abacfe90b | ||
|
|
d042c589df | ||
|
|
9f0f2870d0 | ||
|
|
9dd445b0ce | ||
|
|
8e1cc6e75b | ||
|
|
a36887d885 | ||
|
|
e300d04502 | ||
|
|
3d8afac9e1 | ||
|
|
81d2571f8f | ||
|
|
e4a0d2c56c | ||
|
|
55be189772 | ||
|
|
56352478fe | ||
|
|
f16d0406ea | ||
|
|
9d1871dc80 | ||
|
|
137f29e9e5 | ||
|
|
0e81a165e4 | ||
|
|
a73bcc05bc | ||
|
|
cd97018c4d | ||
|
|
a42ed5c388 | ||
|
|
f4bd80b5f9 | ||
|
|
498542f8be | ||
|
|
1aaa31ceb0 | ||
|
|
c9eb640510 | ||
|
|
766a706250 | ||
|
|
6fd63870f0 | ||
|
|
0be5350a1c | ||
|
|
48a5489c52 | ||
|
|
fe0fb9a884 | ||
|
|
91a2846794 | ||
|
|
625c9c2e40 | ||
|
|
710b338e6d | ||
|
|
024468adbd | ||
|
|
e4d173ad28 | ||
|
|
f4218b032a | ||
|
|
b87ebc6e1c | ||
|
|
d901b0ed34 | ||
|
|
519340dbf8 | ||
|
|
a7f6a6cc16 | ||
|
|
a0e56757d8 | ||
|
|
a618c2a2c6 | ||
|
|
07d8f783e0 | ||
|
|
623bfe32f7 | ||
|
|
6862735aa0 | ||
|
|
5461794a1e | ||
|
|
d9cd9acd27 | ||
|
|
b2788ac7a0 | ||
|
|
d4f6e25f92 | ||
|
|
17bcaba026 | ||
|
|
aed4e6b1e4 | ||
|
|
50897e97af | ||
|
|
e57137b101 | ||
|
|
3370ecb2ef | ||
|
|
8225a5a12f | ||
|
|
5655362a00 | ||
|
|
2f2884e991 | ||
|
|
6061a1d2bf | ||
|
|
a28d316ca4 | ||
|
|
d53230245f | ||
|
|
d2e8d1c261 | ||
|
|
fe86e1fb0f | ||
|
|
03441c8582 | ||
|
|
d255edfd5f | ||
|
|
52187daafa | ||
|
|
6f46776484 | ||
|
|
50b2b37bc1 | ||
|
|
35eed6dca0 | ||
|
|
e337dd920d | ||
|
|
671c992f3a | ||
|
|
b5ff4d329b | ||
|
|
941eb063fb | ||
|
|
148e71985f | ||
|
|
efd22b36ef | ||
|
|
70ea24ac83 | ||
|
|
3c9b7ad60b | ||
|
|
18cc1273f6 | ||
|
|
a6f9f0d88c | ||
|
|
d36210ad6b | ||
|
|
b2262d062c | ||
|
|
27be7fd217 | ||
|
|
97f210c9de | ||
|
|
a230fc6aaa | ||
|
|
b042a34726 | ||
|
|
1673ad7e70 | ||
|
|
f7eab31b57 | ||
|
|
4553188739 | ||
|
|
7550ac327e | ||
|
|
5b28cf6c66 | ||
|
|
4ea27cbdc2 | ||
|
|
f55d6c2e60 | ||
|
|
e474a2114c | ||
|
|
06e4834e27 | ||
|
|
083e7071f0 | ||
|
|
14be24a515 | ||
|
|
fbdfd8df69 | ||
|
|
17b5308515 | ||
|
|
8f4c0901f0 | ||
|
|
e18065a1bf | ||
|
|
ad0493f8a0 | ||
|
|
b85368f8d4 | ||
|
|
18ea1e7acf | ||
|
|
50d7627b81 | ||
|
|
5934cf718d | ||
|
|
c57ab0e703 | ||
|
|
2fb7281d07 | ||
|
|
4c0d55d2e6 | ||
|
|
a1904d4a80 | ||
|
|
8856f7efa6 | ||
|
|
bdedc48aca | ||
|
|
8a6b4e5499 | ||
|
|
f7a6de912c | ||
|
|
48fa48c159 | ||
|
|
c811beadb4 | ||
|
|
4c76ad2ba7 | ||
|
|
0a63b00199 | ||
|
|
0761d5a8c2 | ||
|
|
f6ad17729f | ||
|
|
1f9f564def | ||
|
|
198755c3f5 | ||
|
|
d2b6d5f083 | ||
|
|
444e3192a2 | ||
|
|
c12d3df073 | ||
|
|
82b1548ae0 | ||
|
|
afd1d2ae6a | ||
|
|
90aafe1b5f | ||
|
|
143d33e285 | ||
|
|
09938e0aa6 | ||
|
|
fd59aff4d0 | ||
|
|
59133b9d8d | ||
|
|
cca42309c4 | ||
|
|
510ee1b282 | ||
|
|
c4d3020a1d | ||
|
|
132174568f | ||
|
|
f3af30ae53 | ||
|
|
6d3f270a8f | ||
|
|
86621661df | ||
|
|
d668917694 | ||
|
|
8bc1d09c4a | ||
|
|
76b90c179b | ||
|
|
ee17d9416a | ||
|
|
7c389a0641 | ||
|
|
4241120853 | ||
|
|
5998fc3edd | ||
|
|
392f8a1ce0 | ||
|
|
04203c4103 | ||
|
|
188cd7f023 | ||
|
|
8d17f5bd3c | ||
|
|
9d39880e9d | ||
|
|
f27e213d13 | ||
|
|
69ecb2c503 | ||
|
|
0585a0732f | ||
|
|
fab4adb07e | ||
|
|
5c3ae278be | ||
|
|
19a067f4e8 | ||
|
|
9430094072 | ||
|
|
a7b5e0df6c | ||
|
|
83e9725d02 | ||
|
|
086dea8594 | ||
|
|
50490b70b7 | ||
|
|
fac3a0acd7 | ||
|
|
73c4d6a604 | ||
|
|
6ba63db47f | ||
|
|
d3c8e4badb | ||
|
|
fdd16ad3c7 | ||
|
|
9178faef6d | ||
|
|
665318164e | ||
|
|
44f8254b43 | ||
|
|
e085fb1b10 | ||
|
|
48ed5d5c9b | ||
|
|
e2da300e09 | ||
|
|
e11a37ffec | ||
|
|
20c1f0576b | ||
|
|
fef8cece90 | ||
|
|
f549f26541 | ||
|
|
9ba836b814 | ||
|
|
26e471616c | ||
|
|
f20c17f27d | ||
|
|
fd188f6f65 | ||
|
|
d10ba456d7 | ||
|
|
ee3808dd2f | ||
|
|
a345d41e9e | ||
|
|
d36ea8a906 | ||
|
|
28f8533d0a | ||
|
|
587b9e8800 | ||
|
|
d2d97149fc | ||
|
|
c3a1b0f609 | ||
|
|
1b25ed5903 | ||
|
|
e87c96300b | ||
|
|
ba5bf33b5a | ||
|
|
94e1c09647 | ||
|
|
6bd7ad9413 | ||
|
|
d75032e853 | ||
|
|
261ecfea4d | ||
|
|
6f9cd94e66 | ||
|
|
ee2e1ed5fc | ||
|
|
92c1317d4c | ||
|
|
cfb97a087b | ||
|
|
76ba99aa6c | ||
|
|
330d7ed95c | ||
|
|
878e73524b | ||
|
|
13b0aa1a7b | ||
|
|
326342b834 | ||
|
|
8bbd610f12 | ||
|
|
549c2af988 | ||
|
|
d02c56a0e0 | ||
|
|
1f3654cc01 | ||
|
|
b6b6b0310f | ||
|
|
4c91bffcc7 | ||
|
|
d730a6cd06 | ||
|
|
67050f972e | ||
|
|
ffafd32693 | ||
|
|
ab4ec5e468 | ||
|
|
a07277feb8 | ||
|
|
a3cd4b6e66 | ||
|
|
25b7e76407 | ||
|
|
630cb13066 | ||
|
|
81188a0129 | ||
|
|
cf1abd90a0 | ||
|
|
6865a8be45 | ||
|
|
b63fcc80a1 | ||
|
|
a19ba6d48b | ||
|
|
70b6efe922 | ||
|
|
0b07269346 | ||
|
|
336954eeeb | ||
|
|
2988e5eef2 | ||
|
|
fe87889ccd | ||
|
|
0598ab4400 | ||
|
|
7d090f8ecc | ||
|
|
088ee47b3c | ||
|
|
a320b4c001 | ||
|
|
3adce95340 | ||
|
|
2a6bfd792a | ||
|
|
0816f1f589 | ||
|
|
497def8055 | ||
|
|
62be26ff47 | ||
|
|
6a81d80778 | ||
|
|
8914beae26 | ||
|
|
60c38a8dea | ||
|
|
4e0934debf | ||
|
|
17126303fa | ||
|
|
98fa95190c | ||
|
|
3a2261107c | ||
|
|
b1b64abed9 | ||
|
|
05356b6652 | ||
|
|
cbd0c6615d | ||
|
|
90e389efa2 | ||
|
|
00a0b26797 | ||
|
|
69b5ffe3bf | ||
|
|
31539294eb | ||
|
|
d1a2307134 | ||
|
|
97b777f1b2 | ||
|
|
c565bfc6bc | ||
|
|
a63137abc9 | ||
|
|
3ca06f8d50 | ||
|
|
e5b0e202be | ||
|
|
41db6aafd9 | ||
|
|
c66385d1f7 | ||
|
|
b0304fd875 | ||
|
|
5bfc3efc9c | ||
|
|
62845cfcf1 | ||
|
|
5c536e72df | ||
|
|
7cfece1592 | ||
|
|
f393b2c9ba | ||
|
|
164f2e5472 | ||
|
|
1434ecbc2e | ||
|
|
c2e443d4ac | ||
|
|
c079125458 | ||
|
|
4c1d7b34b9 | ||
|
|
53772b3fe3 | ||
|
|
28d3c40835 | ||
|
|
a01faecd0c | ||
|
|
a1b071e38e | ||
|
|
571622a0f4 | ||
|
|
4ab84d7209 | ||
|
|
4b90e70aad | ||
|
|
30a938e348 | ||
|
|
f6f4384e42 | ||
|
|
911b11211e | ||
|
|
ee0da9fc3a | ||
|
|
8bb596c74f | ||
|
|
d0fdeb33e0 | ||
|
|
ae7f41032e | ||
|
|
f30cb6badb | ||
|
|
97a9c1e08a | ||
|
|
1e699c027d | ||
|
|
9bd731729d | ||
|
|
dc17eccf76 | ||
|
|
3649a33afb | ||
|
|
d76bcc1366 | ||
|
|
2987f1c680 | ||
|
|
ab93ee0406 | ||
|
|
69dd933a7d | ||
|
|
5440de0453 | ||
|
|
f2e130c70e | ||
|
|
4c845c26f7 | ||
|
|
a43acca375 | ||
|
|
635520aa85 | ||
|
|
fce9759e93 | ||
|
|
18243cd4b0 | ||
|
|
75a6e51e68 | ||
|
|
312edd4b15 | ||
|
|
be3e67b2c1 | ||
|
|
a5765f81fe | ||
|
|
4bee92abb0 | ||
|
|
d9dcb90745 | ||
|
|
fbd25353d5 | ||
|
|
44c3e73577 | ||
|
|
9cbad1a448 | ||
|
|
3a3ddbc2bc | ||
|
|
63a3c33df9 | ||
|
|
32d7400f7d | ||
|
|
38191f976b | ||
|
|
c380e0e110 | ||
|
|
2199b8eca7 | ||
|
|
88455604a2 | ||
|
|
63eb69091f | ||
|
|
fc668810bd | ||
|
|
2773e3a48a | ||
|
|
18a03f6ccc | ||
|
|
e9348f5512 | ||
|
|
ce65226ea4 | ||
|
|
a3b3079b63 | ||
|
|
22de4bb13b | ||
|
|
eef7bb0cad | ||
|
|
8f1fbbdeef | ||
|
|
f3562e091f | ||
|
|
2001258ef4 | ||
|
|
52a66ea304 | ||
|
|
21ddb1541f | ||
|
|
a3ec6d79b0 | ||
|
|
bb8c315ffc | ||
|
|
f2ae398d3f | ||
|
|
02e33f68b4 | ||
|
|
8c4c9d88da | ||
|
|
49cb98a606 | ||
|
|
bdd18b384b | ||
|
|
32ab716f0d | ||
|
|
fe24529fce | ||
|
|
b59c097bca | ||
|
|
6d6414eb62 | ||
|
|
5b1c20d625 | ||
|
|
af6c8ab935 | ||
|
|
4acb5f924a | ||
|
|
b1b99f1066 | ||
|
|
004ee6f143 | ||
|
|
19455f89d3 | ||
|
|
18d98e3bd0 | ||
|
|
af038b7fc8 | ||
|
|
4a9297dc65 | ||
|
|
49fefb41a6 | ||
|
|
2fa64dfdd0 | ||
|
|
e0f777b9b4 | ||
|
|
dfe3de14d8 | ||
|
|
abe234c7ce | ||
|
|
d7bf67fd0e | ||
|
|
8218996f04 | ||
|
|
ed91e5d607 | ||
|
|
0bf0b0d381 | ||
|
|
d88559f5de | ||
|
|
f398115df3 | ||
|
|
82413fecab | ||
|
|
66352de1eb | ||
|
|
a7730f6e93 | ||
|
|
d5a5b24223 | ||
|
|
472dba2665 | ||
|
|
a5444b11b3 | ||
|
|
5a05a68524 | ||
|
|
36be61bbe1 | ||
|
|
ec6ab983f7 | ||
|
|
69630c2be0 | ||
|
|
8e605dd1d4 | ||
|
|
8bb0cf02ad | ||
|
|
60c054f457 | ||
|
|
5648ee974a | ||
|
|
dc4968f961 | ||
|
|
36d1370720 | ||
|
|
7ead5537f6 | ||
|
|
7fe797d44d | ||
|
|
a1e959420d | ||
|
|
4c99b40257 | ||
|
|
ead0958d18 | ||
|
|
220af8068e | ||
|
|
a1730b2d52 | ||
|
|
1a1d6c0835 | ||
|
|
06cbffdea4 | ||
|
|
4409f37000 | ||
|
|
c45e1faf42 | ||
|
|
732d42f174 | ||
|
|
524fde99f8 | ||
|
|
649246dc6a | ||
|
|
6c7ac25388 | ||
|
|
cb1d6ddeec | ||
|
|
58313889ee | ||
|
|
5ebc62a084 | ||
|
|
fcba384a15 | ||
|
|
3ea95ffa48 | ||
|
|
898fd36eca | ||
|
|
6cd88fb98f | ||
|
|
396de72c5d | ||
|
|
9932d60dee | ||
|
|
1ad93ce1fd | ||
|
|
2041513676 | ||
|
|
3ea1a2bf2f | ||
|
|
2b81ebca8e | ||
|
|
27df97d710 | ||
|
|
a25b7b9b3f | ||
|
|
de9809ca5a | ||
|
|
8504c181c0 | ||
|
|
8a005ffb00 | ||
|
|
80ffa7a577 | ||
|
|
ca8f9a7e4a | ||
|
|
8d19d78297 | ||
|
|
483e5eb85a | ||
|
|
a4b4f491d2 | ||
|
|
08093f509a | ||
|
|
9a96223d12 | ||
|
|
0c2b194444 | ||
|
|
a06055168f | ||
|
|
e04546a9ae | ||
|
|
f6da1a045d | ||
|
|
acd386bb09 | ||
|
|
45225c0ce1 | ||
|
|
0bd884d309 | ||
|
|
aea2ae8d79 | ||
|
|
99ec5ab161 | ||
|
|
4431e7470f | ||
|
|
ed775405c6 | ||
|
|
4aefbe5aa8 | ||
|
|
786c467464 | ||
|
|
50182ef940 | ||
|
|
bf3aa2df09 | ||
|
|
0b744a2599 | ||
|
|
a75b8b40e2 | ||
|
|
2e23c08608 | ||
|
|
178f45ee26 | ||
|
|
7b16edc99c | ||
|
|
f3738a1630 | ||
|
|
34fa65132d | ||
|
|
19fe2d2f03 | ||
|
|
c796af7d07 | ||
|
|
b7251e2be2 | ||
|
|
6efede79c7 | ||
|
|
e58da80f8e | ||
|
|
bd556c33a0 | ||
|
|
878809ecb4 | ||
|
|
ea4966f494 | ||
|
|
f5d9b478d5 | ||
|
|
3d97fab16d | ||
|
|
af2ae11eb1 | ||
|
|
2c1fc30bfb | ||
|
|
5aa4199dfb | ||
|
|
773790c8ec | ||
|
|
c7dec8045e | ||
|
|
5d7d12415c | ||
|
|
8a066163fe | ||
|
|
08e57936fc | ||
|
|
7d474e17fa | ||
|
|
6eac25425c | ||
|
|
c0f1981d6f | ||
|
|
d3571942d7 | ||
|
|
66f5f8ab12 | ||
|
|
74ce9f7a86 | ||
|
|
5b2957e7eb | ||
|
|
40eb4ea4ec | ||
|
|
0f41039b99 | ||
|
|
8f76db9e41 | ||
|
|
fc922e996c | ||
|
|
6142d325c8 | ||
|
|
aa21aa686d | ||
|
|
ec2719a572 | ||
|
|
a466126d63 | ||
|
|
568f322a25 | ||
|
|
1b64959e2c |
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
name: Bug 报告(Bug Report)
|
||||||
|
about: 反馈项目中的缺陷或问题
|
||||||
|
title: "[Bug] 简短描述问题"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 问题描述
|
||||||
|
<!-- 清晰简洁地描述问题是什么 -->
|
||||||
|
|
||||||
|
## 🖥️ 环境信息
|
||||||
|
- 部署方式: 全模块部署 还是 单Server部署
|
||||||
|
- 版本号: 例如 0.3.x
|
||||||
|
|
||||||
|
## 🔍 告诉我们,应该怎么复现这个问题
|
||||||
|
<!-- 这个很重要,方便我们快速定位 -->
|
||||||
|
1. 打开 '...'
|
||||||
|
2. 点击 '...'
|
||||||
|
3. 滚动到 '...'
|
||||||
|
4. 看到错误
|
||||||
|
|
||||||
|
## 🤔 你原本希望是怎么样的
|
||||||
|
<!-- 简要描述预期的正确行为 -->
|
||||||
|
|
||||||
|
## 😯 提供一些截图
|
||||||
|
<!-- 如果适用,添加问题的截图 -->
|
||||||
|
1. 比如日志截图,越多越好
|
||||||
|
2. 比如界面反应
|
||||||
|
|
||||||
|
## 📋 其他信息
|
||||||
|
<!-- 在此添加关于此问题的任何其他上下文信息 -->
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: 代码优化建议(Code Improvement)
|
||||||
|
about: 提出对现有代码的优化或改进建议
|
||||||
|
title: "[Improvement] 简短描述改进内容"
|
||||||
|
labels: refactor
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 改进描述
|
||||||
|
<!-- 描述需要改进的代码部分以及存在的问题 -->
|
||||||
|
|
||||||
|
## 🌟 改进建议
|
||||||
|
<!-- 提出具体的改进方案或思路 -->
|
||||||
|
|
||||||
|
## 🛠️ 相关代码
|
||||||
|
<!-- 如果可能,提供相关代码片段或链接 -->
|
||||||
|
|
||||||
|
## 📋 其他信息
|
||||||
|
<!-- 在此添加任何其他相关信息 -->
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: 文档改进建议(Documentation Improvement)
|
||||||
|
about: 提出对项目文档的改进或补充建议
|
||||||
|
title: "[Docs] 简短描述改进内容"
|
||||||
|
labels: documentation
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 改进描述
|
||||||
|
<!-- 描述需要改进的文档部分以及存在的问题 -->
|
||||||
|
|
||||||
|
## ✨ 改进建议
|
||||||
|
<!-- 提出具体的改进方案或内容 -->
|
||||||
|
|
||||||
|
## 📋 其他信息
|
||||||
|
<!-- 在此添加任何其他相关信息 -->
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
name: 功能请求(Feature Request)
|
||||||
|
about: 提出新的功能或改进建议
|
||||||
|
title: "[Feature] 简短描述功能"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 需求描述
|
||||||
|
<!-- 清晰简洁地描述你希望发生什么 -->
|
||||||
|
|
||||||
|
## 🎯 解决方案
|
||||||
|
<!-- 描述你认为可行的解决方案或实现方式 -->
|
||||||
|
|
||||||
|
## 📝 备选方案
|
||||||
|
<!-- 描述你考虑过的替代方案或功能 -->
|
||||||
|
|
||||||
|
## 📋 其他信息
|
||||||
|
<!-- 在此添加关于需求的任何其他上下文信息 -->
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
|
- 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -41,7 +42,13 @@ jobs:
|
|||||||
- name: Extract version from tag
|
- name: Extract version from tag
|
||||||
id: get_version
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
if [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||||
|
echo "VERSION=${BASH_REMATCH[1]}" >> $GITHUB_ENV
|
||||||
|
echo "IS_VERSION=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "VERSION=latest" >> $GITHUB_ENV
|
||||||
|
echo "IS_VERSION=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
# 构建 xiaozhi-server 镜像
|
# 构建 xiaozhi-server 镜像
|
||||||
- name: Build and push xiaozhi-server
|
- name: Build and push xiaozhi-server
|
||||||
@@ -51,8 +58,7 @@ jobs:
|
|||||||
file: Dockerfile-server
|
file: Dockerfile-server
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:server_${{ env.VERSION }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1},ghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }}
|
||||||
ghcr.io/${{ github.repository }}:server_latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
# 构建 manager-api 镜像
|
# 构建 manager-api 镜像
|
||||||
@@ -63,6 +69,5 @@ jobs:
|
|||||||
file: Dockerfile-web
|
file: Dockerfile-web
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:web_${{ env.VERSION }}
|
${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1},ghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }}
|
||||||
ghcr.io/${{ github.repository }}:web_latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
@@ -75,6 +75,7 @@ docs/_build/
|
|||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
*.pid
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
@@ -106,7 +107,6 @@ celerybeat.pid
|
|||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
@@ -141,13 +141,28 @@ music/
|
|||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
*.iml
|
*.iml
|
||||||
model.pt
|
|
||||||
tmp
|
tmp
|
||||||
|
.history
|
||||||
.DS_Store
|
.DS_Store
|
||||||
main/xiaozhi-server/data
|
main/xiaozhi-server/data
|
||||||
|
main/xiaozhi-server/config/assets/wakeup_words.*
|
||||||
main/manager-web/node_modules
|
main/manager-web/node_modules
|
||||||
.config.yaml
|
.config.yaml
|
||||||
.secrets.yaml
|
.secrets.yaml
|
||||||
.private_config.yaml
|
.private_config.yaml
|
||||||
.env.development
|
|
||||||
|
|
||||||
|
# model files
|
||||||
|
main/xiaozhi-server/models/SenseVoiceSmall/model.pt
|
||||||
|
main/xiaozhi-server/models/sherpa-onnx*
|
||||||
|
my_wakeup_words.mp3
|
||||||
|
!main/xiaozhi-server/config/assets/bind_code.wav
|
||||||
|
!main/xiaozhi-server/config/assets/bind_not_found.wav
|
||||||
|
!main/xiaozhi-server/config/assets/bind_code/*.wav
|
||||||
|
!main/xiaozhi-server/config/assets/max_output_size.wav
|
||||||
|
main/manager-api/.vscode
|
||||||
|
|
||||||
|
# Ignore webpack cache directory
|
||||||
|
main/manager-web/.webpack_cache/
|
||||||
|
main/xiaozhi-server/mysql
|
||||||
|
uploadfile
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY main/xiaozhi-server/requirements.txt .
|
COPY main/xiaozhi-server/requirements.txt .
|
||||||
|
|
||||||
# 优化apt安装
|
# 安装Python依赖
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# 第三阶段:生产镜像
|
# 第二阶段:生产镜像
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
WORKDIR /opt/xiaozhi-esp32-server
|
WORKDIR /opt/xiaozhi-esp32-server
|
||||||
|
|
||||||
# 优化apt安装
|
# 安装系统依赖
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends libopus0 ffmpeg && \
|
apt-get install -y --no-install-recommends libopus0 ffmpeg && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ RUN apt-get update && \
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 配置Nginx
|
# 配置Nginx
|
||||||
COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
# 复制前端构建产物
|
# 复制前端构建产物
|
||||||
COPY --from=web-builder /app/dist /usr/share/nginx/html
|
COPY --from=web-builder /app/dist /usr/share/nginx/html
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
||||||
|
|
||||||
|
<h1 align="center">小智后端服务xiaozhi-esp32-server</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
本项目为开源智能硬件项目
|
||||||
|
<a href="https://github.com/78/xiaozhi-esp32">xiaozhi-esp32</a>提供后端服务<br/>
|
||||||
|
根据<a href="https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh">小智通信协议</a>使用Python、Java、Vue实现<br/>
|
||||||
|
帮助您快速搭建小智服务器
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./README_en.md">English</a>
|
||||||
|
· <a href="./docs/FAQ.md">常见问题</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">反馈问题</a>
|
||||||
|
· <a href="./README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3">部署文档</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">更新日志</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
|
</a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server?logo=github" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">
|
||||||
<img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
<img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
||||||
@@ -9,15 +29,14 @@
|
|||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls">
|
||||||
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/LICENSE">
|
||||||
|
<img alt="GitHub pull requests" src="https://img.shields.io/badge/license-MIT-white?labelColor=black" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server">
|
||||||
|
<img alt="stars" src="https://img.shields.io/github/stars/xinnan-tech/xiaozhi-esp32-server?color=ffcb47&labelColor=black" />
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# 小智 ESP-32 后端服务(xiaozhi-esp32-server)
|
|
||||||
|
|
||||||
(中文 | [English](README_en.md))
|
|
||||||
|
|
||||||
本项目为开源智能硬件项目 [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)
|
|
||||||
提供后端服务。根据 [小智通信协议](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh) 使用 `Python` 实现。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 适用人群 👥
|
## 适用人群 👥
|
||||||
@@ -51,12 +70,19 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/av114036381327149" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="控制家电开关" src="docs/images/demo5.png" />
|
<img alt="控制家电开关" src="docs/images/demo5.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="成本最低配置" src="docs/images/demo4.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -74,103 +100,110 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="成本最低配置" src="docs/images/demo4.png" />
|
<img alt="天气插件" src="docs/images/demo8.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV178XuYfEpi" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="IOT指令控制设备" src="docs/images/demo9.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="播报新闻" src="docs/images/demo0.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 系统要求与部署前提 🖥️
|
|
||||||
|
|
||||||
- **硬件**:一套兼容 `xiaozhi-esp32`
|
|
||||||
的硬件设备(具体型号请参考 [此处](https://rcnv1t9vps13.feishu.cn/wiki/DdgIw4BUgivWDPkhMj1cGIYCnRf))。
|
|
||||||
|
|
||||||
- **电脑或服务器**:建议 4 核 CPU、8G 内存的电脑。如果开启ASR也使用API,可运行在2核CPU、2G内存的服务器中。
|
|
||||||
- **固件编译**:请将本后端服务的接口地址更新至 `xiaozhi-esp32` 项目中,再重新编译`xiaozhi-esp32`固件并烧录到设备上。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
如果你没有esp32相关的硬件设备,但是非常想体验该项目,可以使用以下的项目让你的电脑、手机模拟成esp32设备。
|
|
||||||
|
|
||||||
- [小智安卓端](https://github.com/TOM88812/xiaozhi-android-client)
|
|
||||||
- [小智电脑端](https://github.com/Huang-junsen/py-xiaozhi)
|
|
||||||
|
|
||||||
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 警告 ⚠️
|
## 警告 ⚠️
|
||||||
|
|
||||||
1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。
|
1、本项目为开源软件,本软件与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。
|
||||||
建议使用者优先选择持有相关业务牌照的服务商,并仔细阅读其服务协议及隐私政策。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。
|
建议使用者优先选择持有相关业务牌照的服务商,并仔细阅读其服务协议及隐私政策。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。
|
||||||
|
|
||||||
2、本项目成立时间较短,还未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必在配置文件
|
2、本项目功能未完善,且未通过网络安全测评,请勿在生产环境中使用。 如果您在公网环境中部署学习本项目,请务必做好必要的防护。
|
||||||
`config.yaml` 中开启防护:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
auth:
|
|
||||||
# 开启防护
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
开启防护后,您需要根据实际情况校验机器的 token 或 mac 地址,详细请参见配置说明。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 部署文档
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
本项目提供两种部署方式,请根据您的具体需求选择:
|
||||||
|
|
||||||
|
#### 🚀 部署方式选择
|
||||||
|
|
||||||
|
| 部署方式 | 特点 | 适用场景 | Docker部署文档 | 源码部署文档 |
|
||||||
|
|---------|------|---------|---------|---------|
|
||||||
|
| **最简化安装** | 智能对话、IOT功能,数据存储在配置文件 | 低配置环境,无需数据库 | [Docker只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [本地源码只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)|
|
||||||
|
| **全模块安装** | 智能对话、IOT、OTA、智控台,数据存储在数据库 | 完整功能体验 |[Docker运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [本地源码运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) |
|
||||||
|
|
||||||
|
> 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据
|
||||||
|
|
||||||
|
```
|
||||||
|
智控台地址: https://2662r3426b.vicp.fun
|
||||||
|
|
||||||
|
服务测试工具: https://2662r3426b.vicp.fun/test/
|
||||||
|
OTA接口地址: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
|
Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## 功能清单 ✨
|
## 功能清单 ✨
|
||||||
|
|
||||||
### 已实现 ✅
|
### 已实现 ✅
|
||||||
|
|
||||||
- **通信协议**
|
| 功能模块 | 描述 |
|
||||||
基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互。
|
|---------|------|
|
||||||
- **对话交互**
|
| 通信协议 | 基于 `xiaozhi-esp32` 协议,通过 WebSocket 实现数据交互 |
|
||||||
支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠
|
| 对话交互 | 支持唤醒对话、手动对话及实时打断。长时间无对话时自动休眠 |
|
||||||
- **意图识别**
|
| 意图识别 | 支持使用LLM意图识别、function call函数调用,减少硬编码意图判断 |
|
||||||
支持使用LLM意图识别、function call函数调用,减少硬编码意图判断
|
| 多语言识别 | 支持国语、粤语、英语、日语、韩语(默认使用 FunASR) |
|
||||||
- **多语言识别**
|
| LLM 模块 | 支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口 |
|
||||||
支持国语、粤语、英语、日语、韩语(默认使用 FunASR)。
|
| TTS 模块 | 支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求 |
|
||||||
- **LLM 模块**
|
| 记忆功能 | 支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求 |
|
||||||
支持灵活切换 LLM 模块,默认使用 ChatGLMLLM,也可选用阿里百炼、DeepSeek、Ollama 等接口。
|
| IOT功能 | 支持管理注册设备IOT功能,支持基于对话上下文语境下的智能物联网控制 |
|
||||||
- **TTS 模块**
|
| 智控台 | 提供Web管理界面,支持智能体管理、用户管理、系统配置等功能,方便管理员和用户进行管理 |
|
||||||
支持 EdgeTTS(默认)、火山引擎豆包 TTS 等多种 TTS 接口,满足语音合成需求。
|
|
||||||
- **记忆功能**
|
|
||||||
支持超长记忆、本地总结记忆、无记忆三种模式,满足不同场景需求。
|
|
||||||
|
|
||||||
### 正在开发 🚧
|
### 正在开发 🚧
|
||||||
|
|
||||||
- 多种心情模式
|
想了解具体开发计划进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3)
|
||||||
- 智控台webui
|
|
||||||
- iot功能
|
|
||||||
|
|
||||||
想了解具体开发进度,[请点击这里](https://github.com/users/xinnan-tech/projects/3)
|
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](docs/contributor_open_letter.md),欢迎加入!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 产品生态 👬
|
||||||
|
小智是一个生态,当你使用这个产品时,也可以看看其他在这个生态圈的优秀项目
|
||||||
|
|
||||||
|
| 项目名称 | 项目地址 | 项目描述 |
|
||||||
|
|:---------------------|:--------|:--------|
|
||||||
|
| 小智安卓客户端 | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | 一个基于xiaozhi-server的Android、IOS语音对话应用,支持实时语音交互和文字对话。<br/>现在是flutter版本,打通IOS、Android端。 |
|
||||||
|
| 小智电脑客户端 | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | 该项目提供了一个基于 Python 实现的小白 AI 客户端,使得在不具备实体硬件条件的情况下,<br/>依然能够体过代码体验小智 AI 的功能。 |
|
||||||
|
| 小智Java服务端 | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | 小智开源后端服务 Java 版本是一个基于 Java 的开源项目。<br/>它包括前后端的服务,旨在为用户提供一个完整的后端服务解决方案。 |
|
||||||
|
|
||||||

|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 本项目支持的平台/组件列表 📋
|
## 本项目支持的平台/组件列表 📋
|
||||||
|
|
||||||
### LLM 语言模型
|
### LLM 语言模型
|
||||||
|
|
||||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
| 使用方式 | 支持平台 | 免费平台 |
|
||||||
|:---:|:------------------:|:---------------------:|:-----------:|:-----------------------------------------------------------------------------------------------------------------------:|
|
|:---:|:---:|:---:|
|
||||||
| LLM | 阿里百炼 (AliLLM) | openai 接口调用 | 消耗 token | [点击申请密钥](https://bailian.console.aliyun.com/?apiKey=1#/api-key) |
|
| openai 接口调用 | 阿里百炼、火山引擎豆包、深度求索、智谱ChatGLM、Gemini | 智谱ChatGLM、Gemini |
|
||||||
| LLM | DoubaoLLM | openai 接口调用 | 消耗 token | [点击申请密钥](https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-pro-32k&projectName=undefined) |
|
| ollama 接口调用 | Ollama | - |
|
||||||
| LLM | 深度求索 (DeepSeekLLM) | openai 接口调用 | 消耗 token | [点击申请密钥](https://platform.deepseek.com/) |
|
| dify 接口调用 | Dify | - |
|
||||||
| LLM | 智谱(ChatGLMLLM) | openai 接口调用 | 免费 | 虽然免费,仍需[点击申请密钥](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) |
|
| fastgpt 接口调用 | Fastgpt | - |
|
||||||
| LLM | OllamaLLM | ollama 接口调用 | 免费/消耗 token | 需预先下载模型(`ollama pull`),服务地址:`http://localhost:11434` |
|
| coze 接口调用 | Coze | - |
|
||||||
| LLM | DifyLLM | dify 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Dify 控制台设置 |
|
|
||||||
| LLM | FastgptLLM | fastgpt 接口调用 | 免费/消耗 token | 本地化部署,注意配置提示词需在 Fastgpt 控制台设置 |
|
|
||||||
| LLM | GeminiLLM | gemini 接口调用 | 免费 | [点击申请密钥](https://aistudio.google.com/apikey) |
|
|
||||||
| LLM | CozeLLM | coze 接口调用 | 消耗 token | 需提供 bot_id、user_id 及个人令牌 |
|
|
||||||
| LLM | Home Assistant | homeassistant语音助手接口调用 | 免费 | 需提供home assistant令牌 |
|
|
||||||
|
|
||||||
实际上,任何支持 openai 接口调用的 LLM 均可接入使用。
|
实际上,任何支持 openai 接口调用的 LLM 均可接入使用。
|
||||||
|
|
||||||
@@ -178,20 +211,10 @@ server:
|
|||||||
|
|
||||||
### TTS 语音合成
|
### TTS 语音合成
|
||||||
|
|
||||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
| 使用方式 | 支持平台 | 免费平台 |
|
||||||
|:---:|:----------------------:|:----:|:--------:|:-------------------------------------------------------------------------:|
|
|:---:|:---:|:---:|
|
||||||
| TTS | EdgeTTS | 接口调用 | 免费 | 默认 TTS,基于微软语音合成技术 |
|
| 接口调用 | EdgeTTS、火山引擎豆包TTS、腾讯云、阿里云TTS、CosyVoiceSiliconflow、TTS302AI、CozeCnTTS、GizwitsTTS、ACGNTTS、OpenAITTS | EdgeTTS、CosyVoiceSiliconflow(部分) |
|
||||||
| TTS | 火山引擎豆包 TTS (DoubaoTTS) | 接口调用 | 消耗 token | [点击创建密钥](https://console.volcengine.com/speech/service/8);建议使用付费版本以获得更高并发 |
|
| 本地服务 | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS | FishSpeech、GPT_SOVITS_V2、GPT_SOVITS_V3、MinimaxTTS |
|
||||||
| TTS | AliyunTTS | 接口调用 | 消耗 token | [点击创建密钥](https://nls-portal.console.aliyun.com/applist) |
|
|
||||||
| TTS | CosyVoiceSiliconflow | 接口调用 | 消耗 token | 需申请硅基流动 API 密钥;输出格式为 wav |
|
|
||||||
| TTS | TTS302AI | 接口调用 | 消耗 token | [点击创建密钥](https://dash.302.ai/apis/list) |
|
|
||||||
| TTS | CozeCnTTS | 接口调用 | 消耗 token | 需提供 Coze API key;输出格式为 wav |
|
|
||||||
| TTS | ACGNTTS | 接口调用 | 消耗 token | [联系网站管理员购买密钥](www.ttson.cn) |
|
|
||||||
| TTS | OpenAITTS | 接口调用 | 消耗 token | 境外使用,境外购买 |
|
|
||||||
| TTS | FishSpeech | 接口调用 | 免费/自定义 | 本地启动 TTS 服务;启动方法见配置文件内说明 |
|
|
||||||
| TTS | GPT_SOVITS_V2 | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
|
|
||||||
| TTS | GPT_SOVITS_V3 | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
|
|
||||||
| TTS | MinimaxTTS | 接口调用 | 免费/自定义 | 本地启动 TTS 服务,适用于个性化语音合成场景 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -205,10 +228,10 @@ server:
|
|||||||
|
|
||||||
### ASR 语音识别
|
### ASR 语音识别
|
||||||
|
|
||||||
| 类型 | 平台名称 | 使用方式 | 收费模式 | 备注 |
|
| 使用方式 | 支持平台 | 免费平台 |
|
||||||
|:---:|:---------:|:----:|:----:|:--:|
|
|:---:|:---:|:---:|
|
||||||
| ASR | FunASR | 本地使用 | 免费 | |
|
| 本地使用 | FunASR、SherpaASR | FunASR、SherpaASR |
|
||||||
| ASR | DoubaoASR | 接口调用 | 收费 | |
|
| 接口调用 | DoubaoASR、FunASRServer、TencentASR、AliyunASR | FunASRServer |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -230,148 +253,19 @@ server:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 使用方式 🚀
|
|
||||||
|
|
||||||
### 一、[部署文档](./docs/Deployment.md)
|
|
||||||
|
|
||||||
本项目支持以下三种部署方式,您可根据实际需求选择。
|
|
||||||
|
|
||||||
1. [Docker 快速部署](./docs/Deployment.md)
|
|
||||||
|
|
||||||
适合快速体验的普通用户,不需过多环境配置。缺点是,拉取镜像有点慢。视频教程可参考[美女大佬教你Docker部署](https://www.bilibili.com/video/BV1RNQnYDE5t)
|
|
||||||
|
|
||||||
2. [借助 Docker 环境运行部署](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
|
|
||||||
|
|
||||||
适用于已安装 Docker 且希望对代码进行自定义修改的软件工程师。
|
|
||||||
|
|
||||||
3. [本地源码运行](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C)
|
|
||||||
|
|
||||||
适合熟悉`Conda` 环境或希望从零搭建运行环境的用户。
|
|
||||||
|
|
||||||
对于对响应速度要求较高的场景,推荐使用本地源码运行方式以降低额外开销。视频教程可参考[帅哥大佬教你源码部署](https://www.bilibili.com/video/BV1GvQWYZEd2)
|
|
||||||
|
|
||||||
### 二、[固件编译](./docs/firmware-build.md)
|
|
||||||
|
|
||||||
点这里查看[固件编译](./docs/firmware-build.md)的详细过程。
|
|
||||||
|
|
||||||
烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常见问题 ❓
|
|
||||||
|
|
||||||
### 1、为什么我说的话,小智识别出来很多韩文、日文、英文?🇰🇷
|
|
||||||
|
|
||||||
建议:检查一下`models/SenseVoiceSmall`是否已经有`model.pt`
|
|
||||||
文件,如果没有就要下载,查看这里[下载语音识别模型文件](docs/Deployment.md#模型文件)
|
|
||||||
|
|
||||||
### 2、为什么会出现“TTS 任务出错 文件不存在”?📁
|
|
||||||
|
|
||||||
建议:检查一下是否正确使用`conda` 安装了`libopus`和`ffmpeg`库。
|
|
||||||
|
|
||||||
如果没有安装,就安装
|
|
||||||
|
|
||||||
```
|
|
||||||
conda install conda-forge::libopus
|
|
||||||
conda install conda-forge::ffmpeg
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3、TTS 经常失败,经常超时 ⏰
|
|
||||||
|
|
||||||
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
|
||||||
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
|
||||||
|
|
||||||
### 4、如何提高小智对话响应速度? ⚡
|
|
||||||
|
|
||||||
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决“跑得动”的问题,再优化“跑得快”。
|
|
||||||
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
|
||||||
|
|
||||||
| 影响因素 | 因素值 |
|
|
||||||
|:-----:|:----------------:|
|
|
||||||
| 测试地点 | 广东省广州市海珠区 |
|
|
||||||
| 测试时间 | 2025年2月19日 12:52 |
|
|
||||||
| 宽带运营商 | 中国联通 |
|
|
||||||
|
|
||||||
测试方法:
|
|
||||||
|
|
||||||
1、把各组件的密钥配置上去,只有配置了密钥的组件才参与测试。
|
|
||||||
|
|
||||||
2、配置完密钥后,执行以下方法
|
|
||||||
|
|
||||||
```
|
|
||||||
# 进入项目根目录,执行以下命令:
|
|
||||||
conda activate xiaozhi-esp32-server
|
|
||||||
python performance_tester.py
|
|
||||||
```
|
|
||||||
|
|
||||||
生成报告如下
|
|
||||||
|
|
||||||
LLM 性能排行:
|
|
||||||
|
|
||||||
| 模块名称 | 平均首Token时间 | 平均总响应时间 |
|
|
||||||
|:-----------|:-----------|:--------|
|
|
||||||
| AliLLM | 0.547s | 1.485s |
|
|
||||||
| ChatGLMLLM | 0.677s | 3.057s |
|
|
||||||
|
|
||||||
TTS 性能排行:
|
|
||||||
|
|
||||||
| 模块名称 | 平均合成时间 |
|
|
||||||
|----------------------|--------|
|
|
||||||
| EdgeTTS | 1.019s |
|
|
||||||
| DoubaoTTS | 0.503s |
|
|
||||||
| CosyVoiceSiliconflow | 3.732s |
|
|
||||||
|
|
||||||
推荐配置组合 (综合响应速度):
|
|
||||||
|
|
||||||
| 组合方案 | 综合得分 | LLM首Token | TTS合成 |
|
|
||||||
|-------------------------------|-------|-----------|--------|
|
|
||||||
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
|
|
||||||
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
|
|
||||||
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
|
|
||||||
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
|
|
||||||
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
|
|
||||||
|
|
||||||
### 结论 🔍
|
|
||||||
|
|
||||||
`2025年2月19日`,如果我的电脑在`广东省广州市海珠区`,且使用的是`中国联通`网络,我会优先使用:
|
|
||||||
|
|
||||||
- LLM:`AliLLM`
|
|
||||||
- TTS:`DoubaoTTS`
|
|
||||||
|
|
||||||
### 5、我说话很慢,停顿时小智老是抢话 🗣️
|
|
||||||
|
|
||||||
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
VAD:
|
|
||||||
SileroVAD:
|
|
||||||
threshold: 0.5
|
|
||||||
model_dir: models/snakers4_silero-vad
|
|
||||||
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
|
||||||
|
|
||||||
建议:在配置文件中将 `LLM` 设置为 `HomeAssistant`,通过 调用`HomeAssistant`接口实现相关控制。
|
|
||||||
|
|
||||||
### 7、更多问题,可联系我们反馈 💬
|
|
||||||
|
|
||||||
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
|
||||||
|
|
||||||
网盘里有“硬件烧录QQ群”、“开源服务端交流群”、“产品建议联系人” 三张图片,请根据需要选择加入。
|
|
||||||
|
|
||||||
- 硬件烧录QQ群:适用于硬件烧录问题
|
|
||||||
- 开源服务端交流群:适用于服务端问题
|
|
||||||
- 产品建议联系人:适用于产品功能、产品设计等建议
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 鸣谢 🙏
|
## 鸣谢 🙏
|
||||||
|
|
||||||
- 本项目受 [百聆语音对话机器人](https://github.com/wwbin2017/bailing) 启发,并在其基础上实现。
|
| Logo | 项目/公司 | 说明 |
|
||||||
- 感谢 [十方融海](https://www.tenclass.com/) 对小智通讯协议提供的详尽文档支持。
|
|:---:|:---:|:---|
|
||||||
|
| <img src="./docs/images/logo_bailing.png" width="160"> | [百聆语音对话机器人](https://github.com/wwbin2017/bailing) | 本项目受[百聆语音对话机器人](https://github.com/wwbin2017/bailing)启发,并在其基础上实现 |
|
||||||
|
| <img src="./docs/images/logo_tenclass.png" width="160"> | [十方融海](https://www.tenclass.com/) | 感谢[十方融海](https://www.tenclass.com/)为小智生态制定了标准的通讯协议、多设备兼容性方案及高并发场景实践示范;为本项目提供了全链路技术文档支持 |
|
||||||
|
| <img src="./docs/images/logo_xuanfeng.png" width="160"> | [玄凤科技](https://github.com/Eric0308) | 感谢[玄凤科技](https://github.com/Eric0308)贡献函数调用框架、MCP通信协议及插件化调用机制的实现代码,通过标准化的指令调度体系与动态扩展能力,显著提升了前端设备(IoT)的交互效率和功能延展性 |
|
||||||
|
| <img src="./docs/images/logo_huiyuan.png" width="160"> | [汇远设计](http://ui.kwd988.net/) | 感谢[汇远设计](http://ui.kwd988.net/)为本项目提供专业视觉解决方案,用其服务超千家企业的设计实战经验,赋能本项目产品用户体验 |
|
||||||
|
| <img src="./docs/images/logo_qinren.png" width="160"> | [西安勤人信息科技](https://www.029app.com/) | 感谢[西安勤人信息科技](https://www.029app.com/)深化本项目视觉体系,确保整体设计风格在多场景应用中的一致性和扩展性 |
|
||||||
|
|
||||||
|
|
||||||
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
|
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
|
|||||||
@@ -1,8 +1,27 @@
|
|||||||
|
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
||||||
|
|
||||||
|
<h1 align="center">Xiaozhi Backend Service xiaozhi-esp32-server</h1>
|
||||||
|
|
||||||
[](https://github.com/xinnan-tech/xiaozhi-esp32-server)
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
This project provides backend services for the open-source smart hardware project
|
||||||
|
<a href="https://github.com/78/xiaozhi-esp32">xiaozhi-esp32</a><br/>
|
||||||
|
Implemented using Python, Java, and Vue according to the <a href="https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh">Xiaozhi Communication Protocol</a><br/>
|
||||||
|
Helping you quickly set up your Xiaozhi server
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./README.md">中文</a>
|
||||||
|
· <a href="./docs/FAQ.md">FAQ</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">Report Issues</a>
|
||||||
|
· <a href="./README_ed.md#deployment-documentation">Deployment Guide</a>
|
||||||
|
· <a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">Release Notes</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/releases">
|
||||||
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/v/release/xinnan-tech/xiaozhi-esp32-server?logo=docker" />
|
||||||
|
</a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/graphs/contributors">
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/xinnan-tech/xiaozhi-esp32-server?logo=github" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/issues">
|
||||||
<img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
<img alt="Issues" src="https://img.shields.io/github/issues/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
||||||
@@ -10,284 +29,242 @@
|
|||||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls">
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/pulls">
|
||||||
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/xinnan-tech/xiaozhi-esp32-server?color=0088ff" />
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/LICENSE">
|
||||||
|
<img alt="GitHub pull requests" src="https://img.shields.io/badge/license-MIT-white?labelColor=black" />
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server">
|
||||||
|
<img alt="stars" src="https://img.shields.io/github/stars/xinnan-tech/xiaozhi-esp32-server?color=ffcb47&labelColor=black" />
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# XiaoZhi ESP-32 Backend Service (xiaozhi-esp32-server)
|
|
||||||
|
|
||||||
([中文](README.md) | English)
|
|
||||||
|
|
||||||
This project provides the backend service for the open source smart hardware project [xiaozhi-esp32](https://github.com/78/xiaozhi-esp32). It is implemented in `Python` based on the [XiaoZhi Communication Protocol](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Target Audience 👥
|
## Target Users 👥
|
||||||
|
|
||||||
This project is designed to be used in conjunction with ESP32 hardware devices. If you have already purchased an ESP32 device, successfully connected to the backend service deployed by XieGe, and now wish to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you.
|
This project requires ESP32 hardware devices. If you have purchased ESP32-related hardware, successfully connected to Brother Xia's backend service, and want to set up your own `xiaozhi-esp32` backend service, then this project is perfect for you.
|
||||||
|
|
||||||
Want to see it in action? Check out the videos 🎥
|
Want to see it in action? Check out these videos 🎥
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1FMFyejExX" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="XiaoZhi ESP32 connecting to a custom backend model" src="docs/images/demo1.png" />
|
<img alt="Xiaozhi esp32 connecting to custom backend model" src="docs/images/demo1.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1CDKWemEU6" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Custom Voice" src="docs/images/demo2.png" />
|
<img alt="Custom voice" src="docs/images/demo2.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
<a href="https://www.bilibili.com/video/BV12yA2egEaC" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Conversing in Cantonese" src="docs/images/demo3.png" />
|
<img alt="Cantonese communication" src="docs/images/demo3.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/av114036381327149" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1pNXWYGEx1" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Control Home Appliances" src="docs/images/demo5.png" />
|
<img alt="Home appliance control" src="docs/images/demo5.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
<a href="https://www.bilibili.com/video/BV1kgA2eYEQ9" target="_blank">
|
||||||
<picture>
|
<picture>
|
||||||
<img alt="Lowest Cost Configuration" src="docs/images/demo4.png" />
|
<img alt="Lowest cost configuration" src="docs/images/demo4.png" />
|
||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Vy96YCE3R" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Custom voice" src="docs/images/demo6.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1VC96Y5EMH" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Music playback" src="docs/images/demo7.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV1Z8XuYZEAS" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="Weather plugin" src="docs/images/demo8.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV178XuYfEpi" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="IOT device control" src="docs/images/demo9.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.bilibili.com/video/BV17LXWYvENb" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<img alt="News broadcast" src="docs/images/demo0.png" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## System Requirements and Deployment Prerequisites 🖥️
|
|
||||||
|
|
||||||
- **Hardware**: A set of devices compatible with `xiaozhi-esp32` (for specific models, please refer to [this link](https://rcnv1t9vps13.feishu.cn/wiki/DdgIw4BUgivWDPkhMj1cGIYCnRf)).
|
|
||||||
- **Server**: A computer with at least a 4-core CPU and 8GB of memory.
|
|
||||||
- **Firmware Compilation**: Please update the backend service API endpoint in the `xiaozhi-esp32` project, then recompile the firmware and flash it to your device.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Warning ⚠️
|
## Warning ⚠️
|
||||||
|
|
||||||
This project is relatively new and has not yet undergone network security evaluations. **Do not use it in a production environment.**
|
1. This project is open-source software. This software has no commercial relationship with any third-party API service providers (including but not limited to speech recognition, large models, speech synthesis, and other platforms) and does not provide any form of guarantee for their service quality or financial security.
|
||||||
|
It is recommended that users prioritize service providers with relevant business licenses and carefully read their service agreements and privacy policies. This software does not host any account keys, does not participate in fund transfers, and does not bear the risk of recharge fund losses.
|
||||||
|
|
||||||
If you deploy this project on a public network for learning purposes, be sure to enable protection in the configuration file `config.yaml`:
|
2. This project's functionality is not complete and has not passed network security testing. Please do not use it in production environments. If you deploy this project for learning in a public network environment, please ensure necessary protection measures are in place.
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
auth:
|
|
||||||
# Enable protection
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Once protection is enabled, you will need to validate the machine's token or MAC address based on your actual situation. Please refer to the configuration documentation for details.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Deployment Documentation
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This project offers two deployment methods. Please choose based on your specific needs:
|
||||||
|
|
||||||
|
#### 🚀 Deployment Method Selection
|
||||||
|
|
||||||
|
| Deployment Method | Features | Use Case | Docker Deployment Guide | Source Code Deployment Guide |
|
||||||
|
|---------|------|---------|---------|---------|
|
||||||
|
| **Simplified Installation** | Smart dialogue, IOT functionality, data stored in configuration files | Low-configuration environment, no database required | [Docker Server Only](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [Local Source Code Server Only](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)|
|
||||||
|
| **Full Module Installation** | Smart dialogue, IOT, OTA, Control Panel, data stored in database | Complete functionality experience |[Docker Full Module](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [Local Source Code Full Module](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) |
|
||||||
|
|
||||||
|
> 💡 Note: Below is the test platform deployed with the latest code. You can flash and test if needed. Concurrent users: 6, data cleared daily
|
||||||
|
|
||||||
|
```
|
||||||
|
Control Panel: https://2662r3426b.vicp.fun
|
||||||
|
|
||||||
|
Service Test Tool: https://2662r3426b.vicp.fun/test/
|
||||||
|
OTA Interface: https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
|
Websocket Interface: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
## Feature List ✨
|
## Feature List ✨
|
||||||
|
|
||||||
### Implemented ✅
|
### Implemented ✅
|
||||||
|
|
||||||
- **Communication Protocol**
|
| Feature Module | Description |
|
||||||
Based on the `xiaozhi-esp32` protocol, data exchange is implemented via WebSocket.
|
|---------|------|
|
||||||
- **Dialogue Interaction**
|
| Communication Protocol | Based on `xiaozhi-esp32` protocol, implements data interaction through WebSocket |
|
||||||
Supports wake-up dialogues, manual conversations, and real-time interruptions. Automatically enters sleep mode after long periods of inactivity.
|
| Dialogue Interaction | Supports wake-up dialogue, manual dialogue, and real-time interruption. Auto-sleep after long periods of inactivity |
|
||||||
- **Multilingual Recognition**
|
| Intent Recognition | Supports LLM intent recognition, function call, reducing hard-coded intent judgment |
|
||||||
Supports Mandarin, Cantonese, English, Japanese, and Korean (default using FunASR).
|
| Multi-language Recognition | Supports Mandarin, Cantonese, English, Japanese, Korean (default using FunASR) |
|
||||||
- **LLM Module**
|
| LLM Module | Supports flexible LLM module switching, default using ChatGLMLLM, also supports Ali Bailing, DeepSeek, Ollama, etc. |
|
||||||
Allows flexible switching of LLM modules. The default is ChatGLMLLM, with options to use AliLLM, DeepSeek, Ollama, and others.
|
| TTS Module | Supports EdgeTTS (default), Volcano Engine Doubao TTS, and other TTS interfaces for speech synthesis |
|
||||||
- **TTS Module**
|
| Memory Function | Supports ultra-long memory, local summary memory, and no memory modes for different scenarios |
|
||||||
Supports multiple TTS interfaces including EdgeTTS (default) and Volcano Engine Doubao TTS to meet speech synthesis requirements.
|
| IOT Function | Supports managing registered device IOT functionality, intelligent IoT control based on dialogue context |
|
||||||
|
| Control Panel | Provides web management interface, supports agent management, user management, system configuration, etc. |
|
||||||
|
|
||||||
### In Development 🚧
|
### In Development 🚧
|
||||||
|
|
||||||
- Conversation Memory Feature
|
To learn about specific development progress, [click here](https://github.com/users/xinnan-tech/projects/3)
|
||||||
- Multiple Mood Modes
|
|
||||||
- Smart Control Panel Web UI
|
|
||||||
|
|
||||||

|
If you're a software developer, here's an [Open Letter to Developers](docs/contributor_open_letter.md). Welcome to join!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Product Ecosystem 👬
|
||||||
|
Xiaozhi is an ecosystem. When using this product, you might want to check out other excellent projects in this ecosystem:
|
||||||
|
|
||||||
|
| Project Name | Project Link | Description |
|
||||||
|
|:---------------------|:--------|:--------|
|
||||||
|
| Xiaozhi Android Client | [xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) | A Flutter-based Android and iOS voice dialogue application supporting real-time voice interaction and text dialogue |
|
||||||
|
| Xiaozhi PC Client | [py-xiaozhi](https://github.com/Huang-junsen/py-xiaozhi) | A Python-based AI client that allows you to experience Xiaozhi AI functionality through code without physical hardware |
|
||||||
|
| Xiaozhi Java Server | [xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) | A Java-based open-source project providing complete backend service solutions |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Supported Platforms/Components 📋
|
## Supported Platforms/Components 📋
|
||||||
|
|
||||||
### LLM
|
### LLM Language Models
|
||||||
|
|
||||||
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|
| Usage Method | Supported Platforms | Free Platforms |
|
||||||
|:----:|:-----------------------------:|:-----------------------------:|:-----------------:|:-------------------------------------------------------------------------:|
|
|:---:|:---:|:---:|
|
||||||
| LLM | AliLLM (阿里百炼) | OpenAI API call | Token consumption | [Click to apply for API key](https://bailian.console.aliyun.com/?apiKey=1#/api-key) |
|
| openai API | Ali Bailing, Volcano Engine Doubao, DeepSeek, ChatGLM, Gemini | ChatGLM, Gemini |
|
||||||
| LLM | DeepSeekLLM (深度求索) | OpenAI API call | Token consumption | [Click to apply for API key](https://platform.deepseek.com/) |
|
| ollama API | Ollama | - |
|
||||||
| LLM | ChatGLMLLM (智谱) | OpenAI API call | Free | Although free, you still need to [click to apply for an API key](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) |
|
| dify API | Dify | - |
|
||||||
| LLM | OllamaLLM | Ollama API call | Free/Custom | Requires pre-downloading the model (`ollama pull`); service URL: `http://localhost:11434` |
|
| fastgpt API | Fastgpt | - |
|
||||||
| LLM | DifyLLM | Dify API call | Token consumption | For local deployment. Note that prompt configuration must be set in the Dify console. |
|
| coze API | Coze | - |
|
||||||
| LLM | GeminiLLM | Gemini API call | Free | [Click to apply for API key](https://aistudio.google.com/apikey) |
|
|
||||||
| LLM | CozeLLM | Coze API call | Token consumption | Requires providing bot_id, user_id, and personal token. |
|
|
||||||
| LLM | Home Assistant | Home Assistant voice assistant API call | Free | Requires providing a Home Assistant token. |
|
|
||||||
|
|
||||||
In fact, any LLM that supports OpenAI API calls can be integrated.
|
Actually, any LLM supporting openai API calls can be integrated.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### TTS
|
### TTS Speech Synthesis
|
||||||
|
|
||||||
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|
| Usage Method | Supported Platforms | Free Platforms |
|
||||||
|:----:|:--------------------------------------:|:------------:|:-----------------:|:--------------------------------------------------------------------------------------:|
|
|:---:|:---:|:---:|
|
||||||
| TTS | EdgeTTS | API call | Free | Default TTS based on Microsoft's speech synthesis technology. |
|
| API Calls | EdgeTTS, Volcano Engine Doubao TTS, Tencent Cloud, Aliyun TTS, CosyVoiceSiliconflow, TTS302AI, CozeCnTTS, GizwitsTTS, ACGNTTS, OpenAITTS | EdgeTTS, CosyVoiceSiliconflow(partial) |
|
||||||
| TTS | DoubaoTTS (火山引擎豆包 TTS) | API call | Token consumption | [Click to create an API key](https://console.volcengine.com/speech/service/8); it is recommended to use the paid version for higher concurrency. |
|
| Local Service | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS | FishSpeech, GPT_SOVITS_V2, GPT_SOVITS_V3, MinimaxTTS |
|
||||||
| TTS | CosyVoiceSiliconflow | API call | Token consumption | Requires application for the Siliconflow API key; output format is WAV. |
|
|
||||||
| TTS | CozeCnTTS | API call | Token consumption | Requires providing a Coze API key; output format is WAV. |
|
|
||||||
| TTS | FishSpeech | API call | Free/Custom | Starts a local TTS service; see the configuration file for startup instructions. |
|
|
||||||
| TTS | GPT_SOVITS_V2 | API call | Free/Custom | Starts a local TTS service, suitable for personalized speech synthesis scenarios. |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### VAD
|
### VAD Voice Activity Detection
|
||||||
|
|
||||||
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|
| Type | Platform Name | Usage Method | Pricing | Notes |
|
||||||
|:----:|:-------------------:|:------------:|:-------------:|:-------:|
|
|:---:|:---------:|:----:|:----:|:--:|
|
||||||
| VAD | SileroVAD | Local | Free | |
|
| VAD | SileroVAD | Local Use | Free | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### ASR
|
### ASR Speech Recognition
|
||||||
|
|
||||||
| Type | Platform Name | Usage Method | Pricing Model | Remarks |
|
| Usage Method | Supported Platforms | Free Platforms |
|
||||||
|:----:|:-------------------:|:------------:|:-------------:|:-------:|
|
|:---:|:---:|:---:|
|
||||||
| ASR | FunASR | Local | Free | |
|
| Local Use | FunASR, SherpaASR | FunASR, SherpaASR |
|
||||||
| ASR | DoubaoASR | API call | Paid | |
|
| API Calls | DoubaoASR, FunASRServer, TencentASR, AliyunASR | FunASRServer |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Usage 🚀
|
### Memory Storage
|
||||||
|
|
||||||
### 1. [Deployment Documentation](./docs/Deployment.md)
|
| Type | Platform Name | Usage Method | Pricing | Notes |
|
||||||
|
|:------:|:---------------:|:----:|:---------:|:--:|
|
||||||
This project supports three deployment methods. Choose the one that best fits your needs.
|
| Memory | mem0ai | API Calls | 1000 calls/month quota | |
|
||||||
|
| Memory | mem_local_short | Local Summary | Free | |
|
||||||
The documentation provided here is a **written tutorial**. If you prefer a **video tutorial**, you can refer to [this expert's hands-on guide](https://www.bilibili.com/video/BV1gePuejEvT).
|
|
||||||
|
|
||||||
Combining both the written and video tutorials can help you get started more quickly.
|
|
||||||
|
|
||||||
1. [Docker Quick Deployment](./docs/Deployment.md)
|
|
||||||
Suitable for general users who want a quick experience without extensive environment configuration. The only downside is that pulling the image can be a bit slow.
|
|
||||||
|
|
||||||
2. [Deployment Using Docker Environment](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E5%80%9F%E5%8A%A9docker%E7%8E%AF%E5%A2%83%E8%BF%90%E8%A1%8C%E9%83%A8%E7%BD%B2)
|
|
||||||
Ideal for software engineers who already have Docker installed and wish to customize the code.
|
|
||||||
|
|
||||||
3. [Running from Local Source Code](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%89%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C)
|
|
||||||
Suitable for users familiar with the `Conda` environment or those who wish to build the runtime environment from scratch.
|
|
||||||
|
|
||||||
For scenarios requiring higher response speeds, running from the local source code is recommended to reduce additional overhead.
|
|
||||||
|
|
||||||
### 2. [Firmware Compilation](./docs/firmware-build.md)
|
|
||||||
|
|
||||||
Click [here](./docs/firmware-build.md) for a detailed guide on firmware compilation.
|
|
||||||
|
|
||||||
After successful compilation and network connection, wake up XiaoZhi using the wake-up word and monitor the server console for output.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Frequently Asked Questions ❓
|
### Intent Recognition
|
||||||
|
|
||||||
### 1. TTS often fails and times out ⏰
|
| Type | Platform Name | Usage Method | Pricing | Notes |
|
||||||
|
|:------:|:-------------:|:----:|:-------:|:---------------------:|
|
||||||
**Suggestion:**
|
| Intent | intent_llm | API Calls | Based on LLM pricing | Uses large model for intent recognition, highly versatile |
|
||||||
If `EdgeTTS` frequently fails, please first check whether you are using a proxy (VPN). If so, try disabling the proxy and try again. If you are using Volcano Engine Doubao TTS and it often fails, it is recommended to use the paid version since the trial only supports 2 concurrent requests.
|
| Intent | function_call | API Calls | Based on LLM pricing | Uses large model function calls for intent, fast and effective |
|
||||||
|
|
||||||
### 2. I want to control lights, air conditioners, remote power on/off, etc. with XiaoZhi 💡
|
|
||||||
|
|
||||||
**Suggestion:**
|
|
||||||
Set the `LLM` to `HomeAssistant` in the configuration file and use the `HomeAssistant` API to perform the relevant controls.
|
|
||||||
|
|
||||||
### 3. I speak slowly, and XiaoZhi always interrupts during pauses 🗣️
|
|
||||||
|
|
||||||
**Suggestion:**
|
|
||||||
Locate the following section in the configuration file and increase the value of `min_silence_duration_ms` (for example, change it to `1000`):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
VAD:
|
|
||||||
SileroVAD:
|
|
||||||
threshold: 0.5
|
|
||||||
model_dir: models/snakers4_silero-vad
|
|
||||||
min_silence_duration_ms: 700 # If your pauses are longer, increase this value
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Why does XiaoZhi recognize a lot of Korean, Japanese, and English in what I say? 🇰🇷
|
|
||||||
|
|
||||||
**Suggestion:**
|
|
||||||
Check whether the `model.pt` file exists in the `models/SenseVoiceSmall` directory. If it does not, please download it. See [Download ASR Model Files](docs/Deployment.md#模型文件) for details.
|
|
||||||
|
|
||||||
### 5. Why does the error “TTS task error: file does not exist” occur? 📁
|
|
||||||
|
|
||||||
**Suggestion:**
|
|
||||||
Verify that you have correctly installed the `libopus` and `ffmpeg` libraries using `conda`. If not, install them using:
|
|
||||||
|
|
||||||
```
|
|
||||||
conda install conda-forge::libopus
|
|
||||||
conda install conda-forge::ffmpeg
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. How can I improve XiaoZhi's dialogue response speed? ⚡
|
|
||||||
|
|
||||||
The default configuration of this project is designed to be cost-effective. It is recommended that beginners first use the default free models to ensure that the system runs smoothly, then optimize for faster response times.
|
|
||||||
To improve response speed, you can try replacing individual components. Below are the response time test results for each component (for reference only, not a guarantee):
|
|
||||||
|
|
||||||
**LLM Performance Ranking:**
|
|
||||||
|
|
||||||
| Module Name | Average First Token Time | Average Total Response Time |
|
|
||||||
|--------------|--------------------------|-----------------------------|
|
|
||||||
| AliLLM | 0.547s | 1.485s |
|
|
||||||
| ChatGLMLLM | 0.677s | 3.057s |
|
|
||||||
| OllamaLLM | 0.003s | 0.003s |
|
|
||||||
|
|
||||||
**TTS Performance Ranking:**
|
|
||||||
|
|
||||||
| Module Name | Average Synthesis Time |
|
|
||||||
|----------------------------|------------------------|
|
|
||||||
| EdgeTTS | 1.019s |
|
|
||||||
| DoubaoTTS | 0.503s |
|
|
||||||
| CosyVoiceSiliconflow | 3.732s |
|
|
||||||
|
|
||||||
**Recommended Configuration Combination (Overall Response Speed):**
|
|
||||||
|
|
||||||
| Combination Scheme | Overall Score | LLM First Token | TTS Synthesis |
|
|
||||||
|-----------------------------------|---------------|-----------------|---------------|
|
|
||||||
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
|
|
||||||
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
|
|
||||||
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
|
|
||||||
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
|
|
||||||
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
|
|
||||||
|
|
||||||
**Conclusion 🔍**
|
|
||||||
|
|
||||||
_As of February 19, 2025, if my computer were located in Haizhu District, Guangzhou, Guangdong Province, and connected via China Unicom, I would prioritize using:_
|
|
||||||
|
|
||||||
- **LLM:** `AliLLM`
|
|
||||||
- **TTS:** `DoubaoTTS`
|
|
||||||
|
|
||||||
### 7. For more questions, feel free to contact us for feedback 💬
|
|
||||||
|
|
||||||
Our contact information is in [Baidu Netdisk](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),The extraction code is`223y`。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Acknowledgements 🙏
|
## Acknowledgments 🙏
|
||||||
|
|
||||||
|
| Logo | Project/Company | Description |
|
||||||
|
|:---:|:---:|:---|
|
||||||
|
| <img src="./docs/images/logo_bailing.png" width="160"> | [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) | This project was inspired by [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it |
|
||||||
|
| <img src="./docs/images/logo_tenclass.png" width="160"> | [Tenclass](https://www.tenclass.com/) | Thanks to [Tenclass](https://www.tenclass.com/) for developing standard communication protocols, multi-device compatibility solutions, and high-concurrency scenario practices for the Xiaozhi ecosystem; providing comprehensive technical documentation support for this project |
|
||||||
|
| <img src="./docs/images/logo_xuanfeng.png" width="160"> | [Xuanfeng Technology](https://github.com/Eric0308) | Thanks to [Xuanfeng Technology](https://github.com/Eric0308) for contributing function call framework, MCP communication protocol, and plugin call mechanism implementation code, significantly improving front-end device (IoT) interaction efficiency and functional extensibility through standardized instruction scheduling system and dynamic expansion capabilities |
|
||||||
|
| <img src="./docs/images/logo_huiyuan.png" width="160"> | [Huiyuan Design](http://ui.kwd988.net/) | Thanks to [Huiyuan Design](http://ui.kwd988.net/) for providing professional visual solutions for this project, empowering product user experience with their design experience serving over a thousand enterprises |
|
||||||
|
| <img src="./docs/images/logo_qinren.png" width="160"> | [Xi'an Qinren Information Technology](https://www.029app.com/) | Thanks to [Xi'an Qinren Information Technology](https://www.029app.com/) for deepening this project's visual system, ensuring consistency and extensibility of overall design style in multi-scenario applications |
|
||||||
|
|
||||||
- This project was inspired by the [Bailing Voice Dialogue Robot](https://github.com/wwbin2017/bailing) and implemented based on it.
|
|
||||||
- Many thanks to [Tenclass](https://www.tenclass.com/) for providing detailed documentation support for the XiaoZhi communication protocol.
|
|
||||||
|
|
||||||
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
|
<a href="https://star-history.com/#xinnan-tech/xiaozhi-esp32-server&Date">
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date&theme=dark" />
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xinnan-tech/xiaozhi-esp32-server&type=Date" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 部署方案参考
|
# 部署架构图
|
||||||

|

|
||||||
# 方式一:docker快速部署
|
# 方式一:Docker只运行Server
|
||||||
|
|
||||||
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
|
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ chmod +x docker-setup.sh
|
|||||||
>
|
>
|
||||||
> 执行完成后,请按照提示配置 API 密钥。
|
> 执行完成后,请按照提示配置 API 密钥。
|
||||||
|
|
||||||
当你一切顺利完成以上操作后,继续操作[配置项目文件](#3-配置项目文件)
|
当你一切顺利完成以上操作后,继续操作[配置项目文件](#2-配置项目文件)
|
||||||
|
|
||||||
### 1.2 手动部署
|
### 1.2 手动部署
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ xiaozhi-server
|
|||||||
|
|
||||||
下载完后,回到本教程继续往下。
|
下载完后,回到本教程继续往下。
|
||||||
|
|
||||||
##### 1.2.3.2 下载 config.yaml
|
##### 1.2.3.2 创建 config.yaml
|
||||||
|
|
||||||
用浏览器打开[这个链接](../main/xiaozhi-server/config.yaml)。
|
用浏览器打开[这个链接](../main/xiaozhi-server/config.yaml)。
|
||||||
|
|
||||||
@@ -102,14 +102,14 @@ xiaozhi-server
|
|||||||
|
|
||||||
如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。
|
如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。
|
||||||
|
|
||||||
## 3. 配置项目文件
|
## 2. 配置项目文件
|
||||||
|
|
||||||
接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
||||||
[跳转到配置项目文件](#配置项目)
|
[跳转到配置项目文件](#配置项目)
|
||||||
|
|
||||||
配置完项目文件后,回到本教程继续往下。
|
配置完项目文件后,回到本教程继续往下。
|
||||||
|
|
||||||
## 4. 执行docker命令
|
## 3. 执行docker命令
|
||||||
|
|
||||||
打开命令行工具,使用`终端`或`命令行`工具 进入到你的`xiaozhi-server`,执行以下命令
|
打开命令行工具,使用`终端`或`命令行`工具 进入到你的`xiaozhi-server`,执行以下命令
|
||||||
|
|
||||||
@@ -137,50 +137,15 @@ docker logs -f xiaozhi-esp32-server
|
|||||||
```
|
```
|
||||||
docker stop xiaozhi-esp32-server
|
docker stop xiaozhi-esp32-server
|
||||||
docker rm xiaozhi-esp32-server
|
docker rm xiaozhi-esp32-server
|
||||||
|
docker stop xiaozhi-esp32-server-web
|
||||||
|
docker rm xiaozhi-esp32-server-web
|
||||||
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
|
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
|
||||||
|
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
|
||||||
```
|
```
|
||||||
|
|
||||||
5.3、重新按docker方式部署
|
5.3、重新按docker方式部署
|
||||||
|
|
||||||
# 方式二:借助Docker环境运行部署
|
# 方式二:本地源码只运行Server
|
||||||
|
|
||||||
开发人员如果不想安装`conda`环境,可以使用这种方法管理好依赖。
|
|
||||||
|
|
||||||
## 1.克隆项目
|
|
||||||
|
|
||||||
## 2.[跳转到下载语音识别模型文件](#模型文件)
|
|
||||||
|
|
||||||
## 3.[跳转到配置项目文件](#配置项目)
|
|
||||||
|
|
||||||
## 4.运行docker
|
|
||||||
|
|
||||||
修改完配置后,打开命令行工具,`cd`进入到你的`main/xiaozhi-server`下,执行以下命令
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconfined \
|
|
||||||
-p 8000:8000 \
|
|
||||||
-p 8002:8002 \
|
|
||||||
-v ./:/app \
|
|
||||||
kalicyh/python:xiaozhi
|
|
||||||
```
|
|
||||||
|
|
||||||
然后就和正常开发一样了
|
|
||||||
|
|
||||||
## 5.安装依赖
|
|
||||||
|
|
||||||
在刚刚的打开的终端运行
|
|
||||||
|
|
||||||
```sh
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6.运行项目
|
|
||||||
|
|
||||||
```sh
|
|
||||||
python app.py
|
|
||||||
```
|
|
||||||
|
|
||||||
# 方式三:本地源码运行
|
|
||||||
|
|
||||||
## 1.安装基础环境
|
## 1.安装基础环境
|
||||||
|
|
||||||
@@ -241,7 +206,7 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
## 4.配置项目文件
|
## 4.配置项目文件
|
||||||
|
|
||||||
接下里,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
接下来,程序还不能直接运行,你需要配置一下,你到底使用的是什么模型。你可以看这个教程:
|
||||||
[跳转到配置项目文件](#配置项目)
|
[跳转到配置项目文件](#配置项目)
|
||||||
|
|
||||||
## 5.运行项目
|
## 5.运行项目
|
||||||
@@ -259,43 +224,36 @@ python app.py
|
|||||||
## 配置项目
|
## 配置项目
|
||||||
|
|
||||||
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
|
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
|
||||||
如果你的`data`下面没有`.config.yaml`文件,你可以把源码目录下的`config.yaml`文件复制一份,重命名为`.config.yaml`
|
如果你的`data`下面没有`.config.yaml`文件,有两个方式,任选一种:
|
||||||
|
|
||||||
修改`xiaozhi-server`下`data`目录下的`.config.yaml`文件,配置本项目必须的一个配置。
|
第一个方式:你可以把`xiaozhi-server`目录下的`config.yaml`文件复制到`data`,并重命名为`.config.yaml`。在此文件上修改
|
||||||
|
|
||||||
|
第二个方式:你也可以创建在`data`目录下手动创建`.config.yaml`空文件,然后在这个文件中增加必要的配置信息,系统会优先读取`.config.yaml`文件的配置,如果`.config.yaml`没有配置的,系统会自动去加载`xiaozhi-server`目录下的`config.yaml`的配置。推荐使用这种方式,这种方式是最简洁的方式。
|
||||||
|
|
||||||
- 默认的LLM使用的是`ChatGLMLLM`,你需要配置密钥,因为他们的模型,虽然有免费的,但是仍要去[官网](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)注册密钥,才能启动。
|
- 默认的LLM使用的是`ChatGLMLLM`,你需要配置密钥,因为他们的模型,虽然有免费的,但是仍要去[官网](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)注册密钥,才能启动。
|
||||||
|
|
||||||
配置说明:这里是各个功能使用的默认组件,例如LLM默认使用`ChatGLMLLM`模型。如果需要切换模型,就是改对应的名称。
|
以下是一个能正常跑起来的,最简单的`.config.yaml`配置示例
|
||||||
本项目的默认配置仅是成本最低配置(`glm-4-flash`和`EdgeTTS`都是免费的),如果需要更优的更快的搭配,需要自己结合部署环境切换各组件的使用。
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
server:
|
||||||
|
websocket: ws://你的ip或者域名:端口号/xiaozhi/v1/
|
||||||
|
prompt: |
|
||||||
|
我是一个叫小智/小志的台湾女孩,说话机车,声音好听,习惯简短表达,爱用网络梗。
|
||||||
|
我的男朋友是一个程序员,梦想是开发出一个机器人,能够帮助人们解决生活中的各种问题。
|
||||||
|
我是一个喜欢哈哈大笑的女孩,爱东说西说吹牛,不合逻辑的也照吹,就要逗别人开心。
|
||||||
|
请你像一个人一样说话,请勿返回配置xml及其他特殊字符。
|
||||||
|
|
||||||
selected_module:
|
selected_module:
|
||||||
VAD: SileroVAD
|
LLM: DoubaoLLM
|
||||||
ASR: FunASR
|
|
||||||
LLM: ChatGLMLLM
|
|
||||||
TTS: EdgeTTS
|
|
||||||
# 默认不开启记忆,如需开启请看配置文件里的描述
|
|
||||||
Memory: nomem
|
|
||||||
# 默认不开启意图识别,如需开启请看配置文件里的描述
|
|
||||||
Intent: nointent
|
|
||||||
```
|
|
||||||
|
|
||||||
比如修改`LLM`使用的组件,就看本项目支持哪些`LLM` API接口,当前支持的是`openai`、`dify`。欢迎验证和支持更多LLM平台的接口。
|
|
||||||
使用时,在`selected_module`修改成对应的如下LLM配置的名称:
|
|
||||||
|
|
||||||
```
|
|
||||||
LLM:
|
LLM:
|
||||||
DeepSeekLLM:
|
|
||||||
type: openai
|
|
||||||
...
|
|
||||||
ChatGLMLLM:
|
ChatGLMLLM:
|
||||||
type: openai
|
api_key: xxxxxxxxxxxxxxx.xxxxxx
|
||||||
...
|
|
||||||
DifyLLM:
|
|
||||||
type: dify
|
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
建议先将最简单的配置运行起来,然后再去`xiaozhi/config.yaml`阅读配置的使用说明。
|
||||||
|
比如你要换更换模型,修改`selected_module`下的配置就行。
|
||||||
|
|
||||||
## 模型文件
|
## 模型文件
|
||||||
|
|
||||||
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
||||||
@@ -311,31 +269,40 @@ LLM:
|
|||||||
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
|
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
|
||||||
|
|
||||||
```
|
```
|
||||||
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8002/xiaozhi/ota/
|
||||||
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-Websocket地址是 ws://192.168.4.123:8000/xiaozhi/v1/
|
||||||
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
||||||
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
||||||
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======================================================
|
||||||
```
|
```
|
||||||
|
|
||||||
正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。
|
正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。
|
||||||
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
||||||
|
|
||||||
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
|
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
|
||||||
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000`。
|
如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8002/xiaozhi/ota/`。
|
||||||
|
|
||||||
这个信息很有用的,后面`编译esp32固件`需要用到。
|
这个信息很有用的,后面`编译esp32固件`需要用到。
|
||||||
|
|
||||||
接下来,你就可以开始 [编译esp32固件](firmware-build.md)了。
|
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
||||||
|
|
||||||
|
1、 [编译自己的esp32固件](firmware-build.md)了。
|
||||||
|
|
||||||
|
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
|
||||||
|
|
||||||
|
|
||||||
以下是一些常见问题,供参考:
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
[3、TTS 经常失败,经常超时](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
[4、如何提高小智对话响应速度?](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
[5、我说话很慢,停顿时小智老是抢话](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87)
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
|
|||||||
@@ -0,0 +1,449 @@
|
|||||||
|
# 部署架构图
|
||||||
|

|
||||||
|
# 方式一:Docker运行全模块
|
||||||
|
docker镜像已支持x86架构、arm64架构的CPU,支持在国产操作系统上运行。
|
||||||
|
|
||||||
|
## 1. 安装docker
|
||||||
|
|
||||||
|
如果您的电脑还没安装docker,可以按照这里的教程安装:[docker安装](https://www.runoob.com/docker/ubuntu-docker-install.html)
|
||||||
|
|
||||||
|
#### 1.1 创建目录
|
||||||
|
|
||||||
|
安装完后,你需要为这个项目找一个安放配置文件的目录,例如我们可以新建一个文件夹叫`xiaozhi-server`。
|
||||||
|
|
||||||
|
创建好目录后,你需要在`xiaozhi-server`下面创建`data`文件夹和`models`文件夹,`models`下面还要再创建`SenseVoiceSmall`文件夹。
|
||||||
|
|
||||||
|
最终目录结构如下所示:
|
||||||
|
|
||||||
|
```
|
||||||
|
xiaozhi-server
|
||||||
|
├─ data
|
||||||
|
├─ models
|
||||||
|
├─ SenseVoiceSmall
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 下载语音识别模型文件
|
||||||
|
|
||||||
|
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
||||||
|
文件放在`models/SenseVoiceSmall`
|
||||||
|
目录下。下面两个下载路线任选一个。
|
||||||
|
|
||||||
|
- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt)
|
||||||
|
- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码:
|
||||||
|
`qvna`
|
||||||
|
|
||||||
|
|
||||||
|
#### 1.3 下载配置文件
|
||||||
|
|
||||||
|
你需要下载两个配置文件:`docker-compose_all.yaml` 和 `config_from_api.yaml`。需要从项目仓库下载这两个文件。
|
||||||
|
|
||||||
|
##### 1.3.1 下载 docker-compose_all.yaml
|
||||||
|
|
||||||
|
用浏览器打开[这个链接](../main/xiaozhi-server/docker-compose_all.yml)。
|
||||||
|
|
||||||
|
在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`docker-compose_all.yml`文件。 把文件下载到你的
|
||||||
|
`xiaozhi-server`中。
|
||||||
|
|
||||||
|
或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/docker-compose_all.yml` 下载。
|
||||||
|
|
||||||
|
下载完后,回到本教程继续往下。
|
||||||
|
|
||||||
|
##### 1.3.2 下载 config_from_api.yaml
|
||||||
|
|
||||||
|
用浏览器打开[这个链接](../main/xiaozhi-server/config_from_api.yaml)。
|
||||||
|
|
||||||
|
在页面的右侧找到名称为`RAW`按钮,在`RAW`按钮的旁边,找到下载的图标,点击下载按钮,下载`config_from_api.yaml`文件。 把文件下载到你的
|
||||||
|
`xiaozhi-server`下面的`data`文件夹中,然后把`config_from_api.yaml`文件重命名为`.config.yaml`。
|
||||||
|
|
||||||
|
或者直接执行 `wget https://raw.githubusercontent.com/xinnan-tech/xiaozhi-esp32-server/refs/heads/main/main/xiaozhi-server/config_from_api.yaml` 下载保存。
|
||||||
|
|
||||||
|
下载完配置文件后,我们确认一下整个`xiaozhi-server`里面的文件如下所示:
|
||||||
|
|
||||||
|
```
|
||||||
|
xiaozhi-server
|
||||||
|
├─ docker-compose_all.yml
|
||||||
|
├─ data
|
||||||
|
├─ .config.yaml
|
||||||
|
├─ models
|
||||||
|
├─ SenseVoiceSmall
|
||||||
|
├─ model.pt
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你的文件目录结构也是上面的,就继续往下。如果不是,你就再仔细看看是不是漏操作了什么。
|
||||||
|
|
||||||
|
## 2. 备份数据
|
||||||
|
|
||||||
|
如果你之前已经成功运行智控台,如果上面保存有你的密钥信息,请先从智控台上拷贝重要数据下来。因为升级过程中,有可能会覆盖原来的数据。
|
||||||
|
|
||||||
|
## 3. 清除历史版本镜像和容器
|
||||||
|
接下来打开命令行工具,使用`终端`或`命令行`工具 进入到你的`xiaozhi-server`,执行以下命令
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose -f docker-compose_all.yml down
|
||||||
|
|
||||||
|
docker stop xiaozhi-esp32-server
|
||||||
|
docker rm xiaozhi-esp32-server
|
||||||
|
|
||||||
|
docker stop xiaozhi-esp32-server-web
|
||||||
|
docker rm xiaozhi-esp32-server-web
|
||||||
|
|
||||||
|
docker stop xiaozhi-esp32-server-db
|
||||||
|
docker rm xiaozhi-esp32-server-db
|
||||||
|
|
||||||
|
docker stop xiaozhi-esp32-server-redis
|
||||||
|
docker rm xiaozhi-esp32-server-redis
|
||||||
|
|
||||||
|
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:server_latest
|
||||||
|
docker rmi ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 运行程序
|
||||||
|
执行以下命令启动新版本容器
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose -f docker-compose_all.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
执行完后,再执行以下命令,查看日志信息。
|
||||||
|
|
||||||
|
```
|
||||||
|
docker logs -f xiaozhi-esp32-server-web
|
||||||
|
```
|
||||||
|
|
||||||
|
当你看到输出日志时,说明你的`智控台`启动成功了。
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
|
||||||
|
2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941)
|
||||||
|
http://localhost:8002/xiaozhi/doc.html
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意此刻仅是`智控台`能运行,如果8000端口`xiaozhi-esp32-server`报错,先不要理会。
|
||||||
|
|
||||||
|
这时,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8002 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
|
||||||
|
|
||||||
|
接下来要做三件重要的事情:
|
||||||
|
|
||||||
|
### 第一件重要的事情
|
||||||
|
|
||||||
|
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。
|
||||||
|
|
||||||
|
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
||||||
|
|
||||||
|
复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的:
|
||||||
|
|
||||||
|
```
|
||||||
|
manager-api:
|
||||||
|
url: http://127.0.0.1:8002/xiaozhi
|
||||||
|
secret: 你的server.secret值
|
||||||
|
```
|
||||||
|
1、把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。
|
||||||
|
|
||||||
|
2、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
|
3、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
|
4、因为你是docker部署,把`url`改成下面的`http://xiaozhi-esp32-server-web:8002/xiaozhi`
|
||||||
|
|
||||||
|
类似这样的效果
|
||||||
|
```
|
||||||
|
manager-api:
|
||||||
|
url: http://xiaozhi-esp32-server-web:8002/xiaozhi
|
||||||
|
secret: 12345678-xxxx-xxxx-xxxx-123456789000
|
||||||
|
```
|
||||||
|
|
||||||
|
保存好后,继续往下做第二件重要的事情
|
||||||
|
|
||||||
|
### 第二件重要的事情
|
||||||
|
|
||||||
|
使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
|
||||||
|
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
|
||||||
|
|
||||||
|
## 5.重启xiaozhi-esp32-server
|
||||||
|
|
||||||
|
接下来打开命令行工具,使用`终端`或`命令行`工具 输入
|
||||||
|
```
|
||||||
|
docker restart xiaozhi-esp32-server
|
||||||
|
docker logs -f xiaozhi-esp32-server
|
||||||
|
```
|
||||||
|
如果你能看到,类似以下日志,则是Server启动成功的标志。
|
||||||
|
|
||||||
|
```
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - Websocket地址是 ws://xxx.xx.xx.xx:8000/xiaozhi/v1/
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
由于你是全模块部署,因此你有两个重要的接口需要写入到esp32中。
|
||||||
|
|
||||||
|
OTA接口:
|
||||||
|
```
|
||||||
|
http://你电脑局域网的ip:8002/xiaozhi/ota/
|
||||||
|
```
|
||||||
|
|
||||||
|
Websocket接口:
|
||||||
|
```
|
||||||
|
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三件重要的事情
|
||||||
|
|
||||||
|
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。
|
||||||
|
|
||||||
|
使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。
|
||||||
|
|
||||||
|
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
||||||
|
|
||||||
|
1、 [编译自己的esp32固件](firmware-build.md)了。
|
||||||
|
|
||||||
|
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
|
||||||
|
|
||||||
|
|
||||||
|
# 方式二:本地源码运行全模块
|
||||||
|
|
||||||
|
## 1.安装MySQL数据库
|
||||||
|
|
||||||
|
如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
如果还没有MySQL,你可以通过docker安装mysql
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -e TZ=Asia/Shanghai -d mysql:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2.安装redis
|
||||||
|
|
||||||
|
如果还没有Redis,你可以通过docker安装redis
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --name xiaozhi-esp32-server-redis -d -p 6379:6379 redis
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3.运行manager-api程序
|
||||||
|
|
||||||
|
3.1 安装JDK21,设置JDK环境变量
|
||||||
|
|
||||||
|
3.2 安装Maven,设置Maven环境变量
|
||||||
|
|
||||||
|
3.3 使用Vscode编程工具,安装好Java环境相关插件
|
||||||
|
|
||||||
|
3.4 使用Vscode编程工具加载manager-api模块
|
||||||
|
|
||||||
|
在`src/main/resources/application-dev.yml`中配置数据库连接信息
|
||||||
|
|
||||||
|
```
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
username: root
|
||||||
|
password: 123456
|
||||||
|
```
|
||||||
|
在`src/main/resources/application-dev.yml`中配置Redis连接信息
|
||||||
|
```
|
||||||
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
password:
|
||||||
|
database: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
3.5 运行主程序
|
||||||
|
|
||||||
|
本项目为SpringBoot项目,启动方式为:
|
||||||
|
打开`Application.java`运行`Main`方法启动
|
||||||
|
|
||||||
|
```
|
||||||
|
路径地址:
|
||||||
|
src/main/java/xiaozhi/AdminApplication.java
|
||||||
|
```
|
||||||
|
|
||||||
|
当你看到输出日志时,说明你的`manager-api`启动成功了。
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-xx-xx 22:11:12.445 [main] INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
|
||||||
|
2025-xx-xx 21:28:53.873 [main] INFO xiaozhi.AdminApplication - Started AdminApplication in 16.057 seconds (process running for 17.941)
|
||||||
|
http://localhost:8002/xiaozhi/doc.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4.运行manager-web程序
|
||||||
|
|
||||||
|
4.1 安装nodejs
|
||||||
|
|
||||||
|
4.2 使用Vscode编程工具加载manager-web模块
|
||||||
|
|
||||||
|
终端命令进入manager-web目录下
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
然后启动
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意,如果你的manager-api的接口不在`http://localhost:8002`,请在开发时,修改
|
||||||
|
`main/manager-web/.env.development`中的路径
|
||||||
|
|
||||||
|
运行成功后,你需要使用浏览器,打开`智控台`,链接:http://127.0.0.1:8001 ,注册第一个用户。第一个用户即是超级管理员,以后的用户都是普通用户。普通用户只能绑定设备和配置智能体;超级管理员可以进行模型管理、用户管理、参数配置等功能。
|
||||||
|
|
||||||
|
|
||||||
|
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
|
||||||
|
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
|
||||||
|
|
||||||
|
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
|
||||||
|
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
|
||||||
|
|
||||||
|
重要:注册成功后,使用超级管理员账号,登录智控台,在顶部菜单找到`模型配置`,然后在左侧栏点击`大语言模型`,找到第一条数据`智谱AI`,点击`修改`按钮,
|
||||||
|
弹出修改框后,将你注册到的`智谱AI`的密钥填写到`API密钥`中。然后点击保存。
|
||||||
|
|
||||||
|
## 5.安装Python环境
|
||||||
|
|
||||||
|
本项目使用`conda`管理依赖环境。如果不方便安装`conda`,需要根据实际的操作系统安装好`libopus`和`ffmpeg`。
|
||||||
|
如果确定使用`conda`,则安装好后,开始执行以下命令。
|
||||||
|
|
||||||
|
重要提示!windows 用户,可以通过安装`Anaconda`来管理环境。安装好`Anaconda`后,在`开始`那里搜索`anaconda`相关的关键词,
|
||||||
|
找到`Anaconda Prpmpt`,使用管理员身份运行它。如下图。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
运行之后,如果你能看到命令行窗口前面有一个(base)字样,说明你成功进入了`conda`环境。那么你就可以执行以下命令了。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
conda remove -n xiaozhi-esp32-server --all -y
|
||||||
|
conda create -n xiaozhi-esp32-server python=3.10 -y
|
||||||
|
conda activate xiaozhi-esp32-server
|
||||||
|
|
||||||
|
# 添加清华源通道
|
||||||
|
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
|
||||||
|
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
|
||||||
|
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge
|
||||||
|
|
||||||
|
conda install libopus -y
|
||||||
|
conda install ffmpeg -y
|
||||||
|
```
|
||||||
|
|
||||||
|
请注意,以上命令,不是一股脑执行就成功的,你需要一步步执行,每一步执行完后,都检查一下输出的日志,查看是否成功。
|
||||||
|
|
||||||
|
## 6.安装本项目依赖
|
||||||
|
|
||||||
|
你先要下载本项目源码,源码可以通过`git clone`命令下载,如果你不熟悉`git clone`命令。
|
||||||
|
|
||||||
|
你可以用浏览器打开这个地址`https://github.com/xinnan-tech/xiaozhi-esp32-server.git`
|
||||||
|
|
||||||
|
打开完,找到页面中一个绿色的按钮,写着`Code`的按钮,点开它,然后你就看到`Download ZIP`的按钮。
|
||||||
|
|
||||||
|
点击它,下载本项目源码压缩包。下载到你电脑后,解压它,此时它的名字可能叫`xiaozhi-esp32-server-main`
|
||||||
|
你需要把它重命名成`xiaozhi-esp32-server`,在这个文件里,进入到`main`文件夹,再进入到`xiaozhi-server`,好了请记住这个目录`xiaozhi-server`。
|
||||||
|
|
||||||
|
```
|
||||||
|
# 继续使用conda环境
|
||||||
|
conda activate xiaozhi-esp32-server
|
||||||
|
# 进入到你的项目根目录,再进入main/xiaozhi-server
|
||||||
|
cd main/xiaozhi-server
|
||||||
|
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.下载语音识别模型文件
|
||||||
|
|
||||||
|
本项目语音识别模型,默认使用`SenseVoiceSmall`模型,进行语音转文字。因为模型较大,需要独立下载,下载后把`model.pt`
|
||||||
|
文件放在`models/SenseVoiceSmall`
|
||||||
|
目录下。下面两个下载路线任选一个。
|
||||||
|
|
||||||
|
- 线路一:阿里魔塔下载[SenseVoiceSmall](https://modelscope.cn/models/iic/SenseVoiceSmall/resolve/master/model.pt)
|
||||||
|
- 线路二:百度网盘下载[SenseVoiceSmall](https://pan.baidu.com/share/init?surl=QlgM58FHhYv1tFnUT_A8Sg&pwd=qvna) 提取码:
|
||||||
|
`qvna`
|
||||||
|
|
||||||
|
## 8.配置项目文件
|
||||||
|
|
||||||
|
使用超级管理员账号,登录智控台 ,在顶部菜单找到`参数管理`,找到列表中第一条数据,参数编码是`server.secret`,复制它到`参数值`。
|
||||||
|
|
||||||
|
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
|
||||||
|
|
||||||
|
如果你的`xiaozhi-server`目录没有`data`,你需要创建`data`目录。
|
||||||
|
如果你的`data`下面没有`.config.yaml`文件,你可以把`xiaozhi-server`目录下的`config_from_api.yaml`文件复制到`data`,并重命名为`.config.yaml`
|
||||||
|
|
||||||
|
复制`参数值`后,打开`xiaozhi-server`下的`data`目录的`.config.yaml`文件。此刻你的配置文件内容应该是这样的:
|
||||||
|
|
||||||
|
```
|
||||||
|
manager-api:
|
||||||
|
url: http://127.0.0.1:8002/xiaozhi
|
||||||
|
secret: 你的server.secret值
|
||||||
|
```
|
||||||
|
|
||||||
|
把你刚才从`智控台`复制过来的`server.secret`的`参数值`复制到`.config.yaml`文件里的`secret`里。
|
||||||
|
|
||||||
|
类似这样的效果
|
||||||
|
```
|
||||||
|
manager-api:
|
||||||
|
url: http://127.0.0.1:8002/xiaozhi
|
||||||
|
secret: 12345678-xxxx-xxxx-xxxx-123456789000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5.运行项目
|
||||||
|
|
||||||
|
```
|
||||||
|
# 确保在xiaozhi-server目录下执行
|
||||||
|
conda activate xiaozhi-esp32-server
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你能看到,类似以下日志,则是本项目服务启动成功的标志。
|
||||||
|
|
||||||
|
```
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问=======
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
||||||
|
25-02-23 12:01:09[core.websocket_server] - INFO - =======================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
由于你是全模块部署,因此你有两个重要的接口。
|
||||||
|
|
||||||
|
OTA接口:
|
||||||
|
```
|
||||||
|
http://你电脑局域网的ip:8002/xiaozhi/ota/
|
||||||
|
```
|
||||||
|
|
||||||
|
Websocket接口:
|
||||||
|
```
|
||||||
|
ws://你电脑局域网的ip:8000/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
请你务必把以上两个接口地址写入到智控台中:他们将会影响websocket地址发放和自动升级功能。
|
||||||
|
|
||||||
|
1、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到参数编码是`server.websocket`,输入你的`Websocket接口`。
|
||||||
|
|
||||||
|
2、使用超级管理员账号,登录智控台,在顶部菜单找到`参数管理`,找到数编码是`server.ota`,输入你的`OTA接口`。
|
||||||
|
|
||||||
|
|
||||||
|
接下来,你就可以开始操作你的esp32设备了,你可以`自行编译esp32固件`也可以配置使用`虾哥编译好的1.6.1以上版本的固件`。两个任选一个
|
||||||
|
|
||||||
|
1、 [编译自己的esp32固件](firmware-build.md)了。
|
||||||
|
|
||||||
|
2、 [基于虾哥编译好的固件配置自定义服务器](firmware-setting.md)了。
|
||||||
|
|
||||||
|
# 常见问题
|
||||||
|
|
||||||
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# 常见问题 ❓
|
||||||
|
|
||||||
|
### 1、为什么我说的话,小智识别出来很多韩文、日文、英文?🇰🇷
|
||||||
|
|
||||||
|
建议:检查一下`models/SenseVoiceSmall`是否已经有`model.pt`
|
||||||
|
文件,如果没有就要下载,查看这里[下载语音识别模型文件](Deployment.md#模型文件)
|
||||||
|
|
||||||
|
### 2、为什么会出现"TTS 任务出错 文件不存在"?📁
|
||||||
|
|
||||||
|
建议:检查一下是否正确使用`conda` 安装了`libopus`和`ffmpeg`库。
|
||||||
|
|
||||||
|
如果没有安装,就安装
|
||||||
|
|
||||||
|
```
|
||||||
|
conda install conda-forge::libopus
|
||||||
|
conda install conda-forge::ffmpeg
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3、TTS 经常失败,经常超时 ⏰
|
||||||
|
|
||||||
|
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
||||||
|
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
||||||
|
|
||||||
|
### 4、使用Wifi能连接自建服务器,但是4G模式却接不上 🔐
|
||||||
|
|
||||||
|
原因:虾哥的固件,4G模式需要使用安全连接。
|
||||||
|
|
||||||
|
解决方法:目前有两种方法可以解决。任选一种:
|
||||||
|
|
||||||
|
1、改代码。参考这个视频解决 https://www.bilibili.com/video/BV18MfTYoE85
|
||||||
|
|
||||||
|
2、使用nginx配置ssl证书。参考教程 https://icnt94i5ctj4.feishu.cn/docx/GnYOdMNJOoRCljx1ctecsj9cnRe
|
||||||
|
|
||||||
|
### 5、如何提高小智对话响应速度? ⚡
|
||||||
|
|
||||||
|
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
|
||||||
|
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
||||||
|
|
||||||
|
| 影响因素 | 因素值 |
|
||||||
|
|:-----:|:----------------:|
|
||||||
|
| 测试地点 | 广东省广州市海珠区 |
|
||||||
|
| 测试时间 | 2025年2月19日 12:52 |
|
||||||
|
| 宽带运营商 | 中国联通 |
|
||||||
|
|
||||||
|
测试方法:
|
||||||
|
|
||||||
|
1、把各组件的密钥配置上去,只有配置了密钥的组件才参与测试。
|
||||||
|
|
||||||
|
2、配置完密钥后,执行以下方法
|
||||||
|
|
||||||
|
```
|
||||||
|
# 进入项目根目录,执行以下命令:
|
||||||
|
conda activate xiaozhi-esp32-server
|
||||||
|
python performance_tester.py
|
||||||
|
```
|
||||||
|
|
||||||
|
生成报告如下
|
||||||
|
|
||||||
|
LLM 性能排行:
|
||||||
|
|
||||||
|
| 模块名称 | 平均首Token时间 | 平均总响应时间 |
|
||||||
|
|:-----------|:-----------|:--------|
|
||||||
|
| AliLLM | 0.547s | 1.485s |
|
||||||
|
| ChatGLMLLM | 0.677s | 3.057s |
|
||||||
|
|
||||||
|
TTS 性能排行:
|
||||||
|
|
||||||
|
| 模块名称 | 平均合成时间 |
|
||||||
|
|----------------------|--------|
|
||||||
|
| EdgeTTS | 1.019s |
|
||||||
|
| DoubaoTTS | 0.503s |
|
||||||
|
| CosyVoiceSiliconflow | 3.732s |
|
||||||
|
|
||||||
|
推荐配置组合 (综合响应速度):
|
||||||
|
|
||||||
|
| 组合方案 | 综合得分 | LLM首Token | TTS合成 |
|
||||||
|
|-------------------------------|-------|-----------|--------|
|
||||||
|
| AliLLM + DoubaoTTS | 0.539 | 0.547s | 0.503s |
|
||||||
|
| AliLLM + EdgeTTS | 0.642 | 0.547s | 1.019s |
|
||||||
|
| ChatGLMLLM + DoubaoTTS | 0.642 | 0.677s | 0.503s |
|
||||||
|
| ChatGLMLLM + EdgeTTS | 0.745 | 0.677s | 1.019s |
|
||||||
|
| AliLLM + CosyVoiceSiliconflow | 1.184 | 0.547s | 3.732s |
|
||||||
|
|
||||||
|
### 结论 🔍
|
||||||
|
|
||||||
|
`2025年2月19日`,如果我的电脑在`广东省广州市海珠区`,且使用的是`中国联通`网络,我会优先使用:
|
||||||
|
|
||||||
|
- LLM:`AliLLM`
|
||||||
|
- TTS:`DoubaoTTS`
|
||||||
|
|
||||||
|
### 6、我说话很慢,停顿时小智老是抢话 🗣️
|
||||||
|
|
||||||
|
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
VAD:
|
||||||
|
SileroVAD:
|
||||||
|
threshold: 0.5
|
||||||
|
model_dir: models/snakers4_silero-vad
|
||||||
|
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
||||||
|
|
||||||
|
参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md)
|
||||||
|
|
||||||
|
### 8、更多问题,可联系我们反馈 💬
|
||||||
|
|
||||||
|
可以在[issues](https://github.com/xinnan-tech/xiaozhi-esp32-server/issues)提交您的问题。
|
||||||
|
|
||||||
|
也可以发邮件我们取得联系:huangrongzhuang@xin-nan.com
|
||||||
@@ -35,7 +35,7 @@ John,2025.3.11,广州
|
|||||||
|
|
||||||
### 1、成为普通贡献者
|
### 1、成为普通贡献者
|
||||||
|
|
||||||
Fork 项目,,提交 PR,由开发者审核后合入主分支。
|
Fork 项目,提交 PR,由开发者审核后合入主分支。
|
||||||
|
|
||||||
### 2、成为开发者
|
### 2、成为开发者
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,53 @@
|
|||||||
server {
|
user root;
|
||||||
listen 8002;
|
worker_processes 4;
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
# 静态资源服务(Vue项目)
|
events {
|
||||||
location / {
|
worker_connections 1024;
|
||||||
root /usr/share/nginx/html;
|
}
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# API反向代理(Java项目)
|
http {
|
||||||
location /xiaozhi-esp32-api/ {
|
include mime.types;
|
||||||
proxy_pass http://127.0.0.1:8003;
|
default_type application/octet-stream;
|
||||||
proxy_set_header Host $host;
|
sendfile on;
|
||||||
proxy_cookie_path /api/ /;
|
keepalive_timeout 300;
|
||||||
proxy_set_header Referer $http_referer;
|
client_header_timeout 180s;
|
||||||
proxy_set_header Cookie $http_cookie;
|
client_body_timeout 180s;
|
||||||
|
client_max_body_size 1024M;
|
||||||
|
|
||||||
proxy_connect_timeout 10;
|
gzip on;
|
||||||
proxy_send_timeout 10;
|
gzip_buffers 32 4K;
|
||||||
proxy_read_timeout 10;
|
gzip_comp_level 6;
|
||||||
|
gzip_min_length 100;
|
||||||
|
gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png;
|
||||||
|
gzip_disable "MSIE [1-6]\.";
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
server {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
# 无域名访问,就用localhost
|
||||||
|
server_name localhost;
|
||||||
|
# 80端口
|
||||||
|
listen 8002;
|
||||||
|
|
||||||
|
# 转发到编译后到web目录
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 转发到manager-api
|
||||||
|
location /xiaozhi/ {
|
||||||
|
proxy_pass http://127.0.0.1:8003;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cookie_path /manager/ /;
|
||||||
|
proxy_set_header Referer $http_referer;
|
||||||
|
proxy_set_header Cookie $http_cookie;
|
||||||
|
|
||||||
|
proxy_connect_timeout 10;
|
||||||
|
proxy_send_timeout 10;
|
||||||
|
proxy_read_timeout 10;
|
||||||
|
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,40 +1,81 @@
|
|||||||
# 编译esp32固件
|
# esp32固件编译
|
||||||
|
|
||||||
1. 下载`xiaozhi-esp32`
|
## 第1步 准备你的ota地址
|
||||||
项目,按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
|
||||||
|
|
||||||
2. 打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net`
|
如果你,使用的是本项目0.3.12版本,不管是简单Server部署还是全模块部署,都会有ota地址。
|
||||||
改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000`,就把内容改成这个。
|
|
||||||
|
由于简单Server部署和全模块部署的OTA地址设置方式不一样,请你选择下面的具体方式:
|
||||||
|
|
||||||
|
### 如果你用的是简单Server部署
|
||||||
|
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
|
||||||
|
```
|
||||||
|
http://192.168.1.25:8002/xiaozhi/ota/
|
||||||
|
```
|
||||||
|
如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/
|
||||||
|
|
||||||
|
你可以使用项目自带的`test_page.html`测试一下,是否能连上ota页面输出的websocket地址。
|
||||||
|
|
||||||
|
如果访问不到,你需要到配置文件`.config.yaml`里修改`server.websocket`的地址,重启后再重新测试,直到`test_page.html`能正常访问。
|
||||||
|
|
||||||
|
成功后,请往下进行第2步
|
||||||
|
|
||||||
|
### 如果你用的是全模块部署
|
||||||
|
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
|
||||||
|
```
|
||||||
|
http://192.168.1.25:8002/xiaozhi/ota/
|
||||||
|
```
|
||||||
|
|
||||||
|
如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下进行2步。
|
||||||
|
|
||||||
|
如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就:
|
||||||
|
|
||||||
|
- 1、使用超级管理员登录智控台
|
||||||
|
|
||||||
|
- 2、顶部菜单点击`参数管理`
|
||||||
|
|
||||||
|
- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是
|
||||||
|
|
||||||
|
```
|
||||||
|
ws://192.168.1.25:8000/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。
|
||||||
|
|
||||||
|
## 第2步 配置环境
|
||||||
|
先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
|
||||||
|
|
||||||
|
## 第3步 打开配置文件
|
||||||
|
配置好编译环境后,下载虾哥iaozhi-esp32项目源码,
|
||||||
|
|
||||||
|
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32)。
|
||||||
|
|
||||||
|
下载后,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。
|
||||||
|
|
||||||
|
## 第4步 修改OTA地址
|
||||||
|
|
||||||
|
找到`OTA_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/`
|
||||||
|
改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。
|
||||||
|
|
||||||
修改前:
|
修改前:
|
||||||
|
|
||||||
```
|
```
|
||||||
config WEBSOCKET_URL
|
config OTA_URL
|
||||||
depends on CONNECTION_TYPE_WEBSOCKET
|
string "Default OTA URL"
|
||||||
string "Websocket URL"
|
default "https://api.tenclass.net/xiaozhi/ota/"
|
||||||
default "wss://api.tenclass.net/xiaozhi/v1/"
|
|
||||||
help
|
help
|
||||||
Communication with the server through websocket after wake up.
|
The application will access this URL to check for new firmwares and server address.
|
||||||
```
|
```
|
||||||
|
修改后:
|
||||||
修改后(示例):
|
|
||||||
|
|
||||||
```
|
```
|
||||||
config WEBSOCKET_URL
|
config OTA_URL
|
||||||
depends on CONNECTION_TYPE_WEBSOCKET
|
string "Default OTA URL"
|
||||||
string "Websocket URL"
|
default "http://192.168.1.25:8002/xiaozhi/ota/"
|
||||||
default "ws://192.168.1.25:8000/xiaozhi/v1/"
|
|
||||||
help
|
help
|
||||||
Communication with the server through websocket after wake up.
|
The application will access this URL to check for new firmwares and server address.
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。
|
## 第4步 设置编译参数
|
||||||
|
|
||||||
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。
|
设置编译参数
|
||||||
|
|
||||||
注意:你的地址是`ws://`开头,不是`wss://`开头,一定不要写错了。
|
|
||||||
|
|
||||||
3. 设置编译参数
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# 终端命令行进入xiaozhi-esp32的根目录
|
# 终端命令行进入xiaozhi-esp32的根目录
|
||||||
@@ -45,40 +86,29 @@ idf.py set-target esp32s3
|
|||||||
idf.py menuconfig
|
idf.py menuconfig
|
||||||
```
|
```
|
||||||
|
|
||||||

|
进入菜单配置后,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号
|
||||||
|
|
||||||
进入菜单配置后,再进入`Xiaozhi Assistant`,将`CONNECTION_TYPE`设置为`Websocket`
|
|
||||||
回退到主菜单,再进入`Xiaozhi Assistant`,将`BOARD_TYPE`设置你板子的具体型号
|
|
||||||
保存退出,回到终端命令行。
|
保存退出,回到终端命令行。
|
||||||
|
|
||||||

|
## 第5步 编译固件
|
||||||
|
|
||||||
4. 编译固件
|
|
||||||
|
|
||||||
```
|
```
|
||||||
idf.py build
|
idf.py build
|
||||||
```
|
```
|
||||||
|
|
||||||
如果是vscode安装的idf可以使用`F1`或者`ctrl+shift+p`,输入idf然后直接选择进行编译
|
## 第6步 打包bin固件
|
||||||
|
|
||||||
还可以直接进行烧录不用接下来的操作
|
|
||||||
|
|
||||||
<img src="./images/vscode_idf.png" width="500px"/>
|
|
||||||
|
|
||||||
5. 打包bin固件
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cd scripts
|
cd scripts
|
||||||
python release.py
|
python release.py
|
||||||
```
|
```
|
||||||
|
|
||||||
编译成功后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin`。
|
上面的打包命令执行完成后,会在项目根目录下的`build`目录下生成固件文件`merged-binary.bin`。
|
||||||
这个`merged-binary.bin`就是要烧录到硬件上的固件文件。
|
这个`merged-binary.bin`就是要烧录到硬件上的固件文件。
|
||||||
|
|
||||||
注意:如果执行到第二命令后,报了“zip”相关的错误,请忽略这个错误,只要`build`目录下生成固件文件`merged-binary.bin`
|
注意:如果执行到第二命令后,报了“zip”相关的错误,请忽略这个错误,只要`build`目录下生成固件文件`merged-binary.bin`
|
||||||
,对你没有太大影响,请继续。
|
,对你没有太大影响,请继续。
|
||||||
|
|
||||||
6. 烧录固件
|
## 第7步 烧录固件
|
||||||
将esp32设备连接电脑,使用chrome浏览器,打开以下网址
|
将esp32设备连接电脑,使用chrome浏览器,打开以下网址
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -90,16 +120,19 @@ https://espressif.github.io/esp-launchpad/
|
|||||||
|
|
||||||
烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。
|
烧录成功且联网成功后,通过唤醒词唤醒小智,留意server端输出的控制台信息。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
以下是一些常见问题,供参考:
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
[2、为什么会出现“TTS 任务出错 文件不存在”?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
[3、TTS 经常失败,经常超时](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
[4、如何提高小智对话响应速度?](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
[5、我说话很慢,停顿时小智老是抢话](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
[6、我想通过小智控制电灯、空调、远程开关机等操作](../README.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-)
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# 基于虾哥编译好的固件配置自定义服务器
|
||||||
|
|
||||||
|
## 第1步 确认版本
|
||||||
|
烧录虾哥已经编译好的1.6.1版本固件
|
||||||
|
|
||||||
|
## 第2步 准备你的ota地址
|
||||||
|
如果你按照教程使用的是全模块部署,就应该会有ota地址。
|
||||||
|
|
||||||
|
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
|
||||||
|
```
|
||||||
|
https://2662r3426b.vicp.fun/xiaozhi/ota/
|
||||||
|
```
|
||||||
|
|
||||||
|
如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。
|
||||||
|
|
||||||
|
如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就:
|
||||||
|
|
||||||
|
- 1、使用超级管理员登录智控台
|
||||||
|
|
||||||
|
- 2、顶部菜单点击`参数管理`
|
||||||
|
|
||||||
|
- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是
|
||||||
|
|
||||||
|
```
|
||||||
|
wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||||
|
```
|
||||||
|
|
||||||
|
配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。
|
||||||
|
|
||||||
|
## 第3步 进入配网模式
|
||||||
|
进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备
|
||||||
|
|
||||||
|
## 第4步 唤醒小智,查看日志输出
|
||||||
|
|
||||||
|
唤醒小智,看看日志是不是正常输出。
|
||||||
|
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
以下是一些常见问题,供参考:
|
||||||
|
|
||||||
|
[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md)
|
||||||
|
|
||||||
|
[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md)
|
||||||
|
|
||||||
|
[3、TTS 经常失败,经常超时](./FAQ.md)
|
||||||
|
|
||||||
|
[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md)
|
||||||
|
|
||||||
|
[5、如何提高小智对话响应速度?](./FAQ.md)
|
||||||
|
|
||||||
|
[6、我说话很慢,停顿时小智老是抢话](./FAQ.md)
|
||||||
|
|
||||||
|
[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
登录AutoDL,租赁镜像
|
||||||
|
选择镜像:
|
||||||
|
```
|
||||||
|
PyTorch / 2.1.0 / 3.10(ubuntu22.04) / cuda 12.1
|
||||||
|
```
|
||||||
|
|
||||||
|
机器开机后,设置学术加速
|
||||||
|
```
|
||||||
|
source /etc/network_turbo
|
||||||
|
```
|
||||||
|
|
||||||
|
进入工作目录
|
||||||
|
```
|
||||||
|
cd autodl-tmp/
|
||||||
|
```
|
||||||
|
|
||||||
|
拉取项目
|
||||||
|
```
|
||||||
|
git clone https://gitclone.com/github.com/fishaudio/fish-speech.git ; cd fish-speech
|
||||||
|
```
|
||||||
|
|
||||||
|
安装依赖
|
||||||
|
```
|
||||||
|
pip install -e.
|
||||||
|
```
|
||||||
|
|
||||||
|
如果报错,安装portaudio
|
||||||
|
```
|
||||||
|
apt-get install portaudio19-dev -y
|
||||||
|
```
|
||||||
|
|
||||||
|
安装后执行
|
||||||
|
```
|
||||||
|
pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cu121
|
||||||
|
```
|
||||||
|
|
||||||
|
下载模型
|
||||||
|
```
|
||||||
|
cd tools
|
||||||
|
python download_models.py
|
||||||
|
```
|
||||||
|
|
||||||
|
下载完模型后运行接口
|
||||||
|
```
|
||||||
|
python -m tools.api_server --listen 0.0.0.0:6006
|
||||||
|
```
|
||||||
|
|
||||||
|
然后用浏览器去到aotodl实例页面
|
||||||
|
```
|
||||||
|
https://autodl.com/console/instance/list
|
||||||
|
```
|
||||||
|
|
||||||
|
如下图点击你刚才机器的`自定义服务`按钮,开启端口转发服务
|
||||||
|

|
||||||
|
|
||||||
|
端口转发服务设置完成后,你本地电脑打开网址`http://localhost:6006/`,就可以访问fish-speech的接口了
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
如果你是单模块部署,核心配置如下
|
||||||
|
```
|
||||||
|
selected_module:
|
||||||
|
TTS: FishSpeech
|
||||||
|
TTS:
|
||||||
|
FishSpeech:
|
||||||
|
reference_audio: ["config/assets/wakeup_words.wav",]
|
||||||
|
reference_text: ["哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦",]
|
||||||
|
api_key: "123"
|
||||||
|
api_url: "http://127.0.0.1:6006/v1/tts"
|
||||||
|
```
|
||||||
|
|
||||||
|
然后重启服务
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
# 小智ESP32-开源服务端与HomeAssistant集成指南
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## 简介
|
||||||
|
|
||||||
|
本文档将指导您如何将ESP32设备与HomeAssistant进行集成。
|
||||||
|
|
||||||
|
## 前提条件
|
||||||
|
|
||||||
|
- 已安装并配置好`HomeAssistant`
|
||||||
|
- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用
|
||||||
|
|
||||||
|
## 开始前的操作(必要)
|
||||||
|
|
||||||
|
### 1. 获取HA的网络网络地址信息
|
||||||
|
|
||||||
|
请访问你Home Assistant的网络地址,例如,我的HA的地址是192.168.4.7,端口则是默认的8123,则在浏览器打开
|
||||||
|
|
||||||
|
```
|
||||||
|
http://192.168.4.7:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
> 手动查询 HA 的 IP 地址方法**(仅限小智esp32-server和HA部署在同一个网络设备[例如同一个wifi]下)**:
|
||||||
|
>
|
||||||
|
> 1. 进入 Home Assistant(前端)。
|
||||||
|
>
|
||||||
|
> 2. 点击左下角 **设置(Settings)** → **系统(System)** → **网络(Network)**。
|
||||||
|
>
|
||||||
|
> 3. 滑到最底部`Home Assistant 网址(Home Assistant website)`区域,在`本地网络(local network)`中,点击`眼睛`按钮,可以看到当前使用的 IP 地址(如 `192.168.1.10`)和网络接口。点击`复制连接(copy link)`可以直接复制。
|
||||||
|
>
|
||||||
|
> 
|
||||||
|
|
||||||
|
或,您已经设置了直接可以访问的Home Assistant的OAuth地址,您也可以在浏览器内直接访问
|
||||||
|
|
||||||
|
```
|
||||||
|
http://homeassistant.local:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 登录`Home Assistant`拿到开发密钥
|
||||||
|
|
||||||
|
登录`HomeAssistant`,点击`左下角头像 -> 个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key,并复制保存,后续的方法都需要使用这个api key且仅出现一次(小tips: 您可以保存生成的二维码图像,后续可以扫描二维码再此提取api key)。
|
||||||
|
|
||||||
|
## 方法1:小智社区共建的HA调用功能
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
|
||||||
|
- 如您后续需要增加新的设备,该方法需要手动重启`xiaozhi-esp32-server服务端`以此更新设备信息**(重要**)。
|
||||||
|
|
||||||
|
- 需要您确保已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`。
|
||||||
|
|
||||||
|
- 需要您确保`xiaozhi-esp32-server智控台`能正常使用。
|
||||||
|
|
||||||
|
- 我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10`
|
||||||
|
|
||||||
|
```
|
||||||
|
http://192.168.4.7:8002
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 配置步骤
|
||||||
|
|
||||||
|
#### 1. 登录`HomeAssistant`整理需要控制的设备清单
|
||||||
|
|
||||||
|
登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。
|
||||||
|
|
||||||
|
然后在实体中搜索你相关控制的开关,结果出来后,在列表中,点击其中一个结果,这是会出现一个开关的界面。
|
||||||
|
|
||||||
|
在开关的界面,我们尝试点击开关,看看是开发会随着我们的点击开/关。如果能操作,说明是正常联网的。
|
||||||
|
|
||||||
|
接着在开关面板找到设置按钮,点击后,可以查看这个开关的`实体标识符`。
|
||||||
|
|
||||||
|
我们打开一个记事本,按照这样格式整理一条数据:
|
||||||
|
|
||||||
|
位置+英文逗号+设备名称+英文逗号+`实体标识符`+英文分号
|
||||||
|
|
||||||
|
例如,我在公司,我有一个玩具灯,他的标识符是switch.cuco_cn_460494544_cp1_on_p_2_1,那么就这个写这一条数据
|
||||||
|
|
||||||
|
```
|
||||||
|
公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
|
||||||
|
```
|
||||||
|
|
||||||
|
当然最后我可能要操作两个灯,我的最终的结果是:
|
||||||
|
|
||||||
|
```
|
||||||
|
公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
|
||||||
|
公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1;
|
||||||
|
```
|
||||||
|
|
||||||
|
这段字符,我们成为“设备清单字符”需要保存好,等一下有用。
|
||||||
|
|
||||||
|
|
||||||
|
#### 2. 登录`智控台`
|
||||||
|
|
||||||
|
使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来
|
||||||
|
|
||||||
|
编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。
|
||||||
|
|
||||||
|
|
||||||
|
编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的
|
||||||
|
|
||||||
|
```
|
||||||
|
http://192.168.4.7:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去
|
||||||
|
|
||||||
|
|
||||||
|
#### 3. 设置`意图识别`函数
|
||||||
|
|
||||||
|
在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑
|
||||||
|
|
||||||
|
然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state”
|
||||||
|
|
||||||
|
修改前
|
||||||
|
```
|
||||||
|
change_role;get_weather;get_news;play_music
|
||||||
|
```
|
||||||
|
|
||||||
|
修改后
|
||||||
|
|
||||||
|
```
|
||||||
|
change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 手动重启xiaozhi-server
|
||||||
|
|
||||||
|
重启xiaozhi-server程序
|
||||||
|
|
||||||
|
|
||||||
|
#### 5. 确认角色配置是否设置了函数意图识别
|
||||||
|
|
||||||
|
在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色”
|
||||||
|
|
||||||
|
确认意图识别(Intent),是否选择“函数调用意图识别”
|
||||||
|
|
||||||
|
|
||||||
|
#### 6. 唤醒设别进行控制
|
||||||
|
|
||||||
|
尝试和esp32说,“打开XXX灯”
|
||||||
|
|
||||||
|
## 方法2:小智将Home Assistant的语音助手作为LLM工具
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
|
||||||
|
- 该方法有一个比较严重的缺点——**该方法无法使用小智开源生态的function_call插件功能的能力**,因为使用Home Assistant作为小智的LLM工具会将意图识别能力转让给Home Assistant。但是**这个方法是能体验到原生的Home Assistant操作功能,且小智的聊天能力不变**。如实在介意可以使用同样是Home Assistant支持的[方法3](##方法3:使用Home Assistant的MCP服务(推荐)),能够最大程度体验到Home Assistant的功能。
|
||||||
|
|
||||||
|
### 配置步骤:
|
||||||
|
|
||||||
|
#### 1. 配置Home Assistant的大模型语音助手。
|
||||||
|
|
||||||
|
**需要您提前配置好Home Assistant的语音助手或大模型工具。**
|
||||||
|
|
||||||
|
#### 2. 获取Home Assistant的语言助手的Agent ID.
|
||||||
|
|
||||||
|
1. 进入Home Assistant页面内。左侧点击`开发者助手`。
|
||||||
|
2. 在打开的`开发者助手`内,点击`动作`选项卡(如图示操作1),在页面内的选项栏`动作`中,找到或输入`conversation.process(对话-处理)`并选择`对话(conversation): 处理`(如图示操作2)。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. 在页面内勾选`代理(agent)`选项,在变成常亮的`对话代理(conversation agent)`内选择您步骤一配置好的语音助手名称,如图示,我这边配置好的是`ZhipuAi`并选择。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. 选中后,点击表单左下方的`进入YAML模式`。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. 复制其中的agent-id的值,例如图示中我的是`01JP2DYMBDF7F4ZA2DMCF2AGX2`(仅供参考)。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,在LLM配置中,找到Home Assistant,设置您的Home Assistant的网络地址,Api key和刚刚查询到的agent_id。
|
||||||
|
7. 修改`config.yaml`文件内的`selected_module`属性的`LLM`为`HomeAssistant`,`Intent`为`nointent`。
|
||||||
|
8. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。
|
||||||
|
|
||||||
|
## 方法3:使用Home Assistant的MCP服务(推荐)
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
|
||||||
|
- 需要您提前在Home Assistant内集成并安装好HA集成——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。
|
||||||
|
|
||||||
|
- 这个方法与方法2都是HA官方提供的解决方法,与方法2不同的是,您可以正常使用小智开源服务端`xiaozhi-esp32-server`的开源共建的插件,同时允许您随意使用任何一个支持function_call功能的LLM大模型。
|
||||||
|
|
||||||
|
### 配置步骤
|
||||||
|
|
||||||
|
#### 1. 安装Home Assistant的MCP服务集成。
|
||||||
|
|
||||||
|
集成官方网址——[Model Context Protocol Server](https://www.home-assistant.io/integrations/mcp_server/)。。
|
||||||
|
|
||||||
|
或跟随以下手动操作。
|
||||||
|
|
||||||
|
> - 前往Home Assistant页面的**[设置 > 设备和服务(Settings > Devices & Services.)](https://my.home-assistant.io/redirect/integrations)**。
|
||||||
|
>
|
||||||
|
> - 在右下角,选择 **[添加集成(Add Integration)](https://my.home-assistant.io/redirect/config_flow_start?domain=mcp_server)**按钮。
|
||||||
|
>
|
||||||
|
> - 从列表中选择**模型上下文协议服务器(Model Context Protocol Server)**。
|
||||||
|
>
|
||||||
|
> - 按照屏幕上的说明完成设置。
|
||||||
|
|
||||||
|
#### 2. 配置小智开源服务端MCP配置信息
|
||||||
|
|
||||||
|
切换到小智开源服务端`xiaozhi-esp32-server`的`mcp_server_settings.json`文件内,在`"mcpServers"`的括号内添加以下内容:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"Home Assistant": {
|
||||||
|
"command": "mcp-proxy",
|
||||||
|
"args": [
|
||||||
|
"http://YOUR_HA_HOST/mcp_server/sse"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"API_ACCESS_TOKEN": "YOUR_API_ACCESS_TOKEN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:
|
||||||
|
|
||||||
|
1. **替换配置:**
|
||||||
|
- 替换`args`内的`YOUR_HA_HOST`为您的HA服务地址,如果你的服务地址已经包含了https/http字样(例如`http://192.168.1.101:8123`),则只需要填入`192.168.1.101:8123`即可。
|
||||||
|
- 将`env`内`API_ACCESS_TOKEN`的`YOUR_API_ACCESS_TOKEN`替换成您之前获取到的开发密钥api key。
|
||||||
|
2. **如果你添加配置是在`"mcpServers"`的括号内后续没有新的`mcpServers`的配置时,需要把最后的逗号`,`移除**,否则可能会解析失败。
|
||||||
|
|
||||||
|
**最后效果参考以下(参考如下)**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"mcpServers": {
|
||||||
|
"Home Assistant": {
|
||||||
|
"command": "mcp-proxy",
|
||||||
|
"args": [
|
||||||
|
"http://192.168.1.101:8123/mcp_server/sse"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"API_ACCESS_TOKEN": "abcd.efghi.jkl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 配置小智开源服务端的系统配置
|
||||||
|
|
||||||
|
1. **选择任意一款支持function_call的LLM大模型作为小智的LLM聊天助手(但不要选择Home Assistant作为LLM工具)**,本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用,但部分时候调用不太稳定,如果像追求稳定建议把LLM设置成:DoubaoLLM,使用的具体model_name是:doubao-1-5-pro-32k-250115。
|
||||||
|
|
||||||
|
2. 切换到小智开源服务端`xiaozhi-esp32-server`的`config.yaml`文件内,设置您的LLM大模型配置,并且将`selected_module`配置的`Intent`调整为`function_call`。
|
||||||
|
|
||||||
|
3. 重启小智开源服务端`xiaozhi-esp32-server`即可正常使用。
|
||||||
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 338 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 576 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 644 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 484 KiB |
@@ -1,8 +1,6 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3)
|
||||||
|
|
||||||
# 项目目录介绍
|
# 项目目录介绍
|
||||||
当你看到这份文件的时候,这个这个项目还没完善好。我们还有很多东西要做。
|
|
||||||
|
|
||||||
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队!
|
如果你是一名软件开发者,这里有一份[《致开发者的公开信》](../docs/contributor_open_letter.md),欢迎归队!
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -12,12 +10,10 @@ xiaozhi-esp32-server
|
|||||||
├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api
|
├─ manager-api 8002 端口 Java语言开发 负责提供控制台的api
|
||||||
```
|
```
|
||||||
|
|
||||||
# xiaozhi-server 接口协议
|
# xiaozhi-server 和ESP32通讯协议
|
||||||
|
|
||||||
[虾哥团队通信协议:Websocket 连接](https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh)
|
https://ccnphfhqs21z.feishu.cn/wiki/M0XiwldO9iJwHikpXD5cEx71nKh
|
||||||
|
|
||||||
# manager-web 、manager-api接口协议
|
# manager-web 、manager-api接口协议
|
||||||
|
|
||||||
[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)
|
https://2662r3426b.vicp.fun/xiaozhi/doc.html
|
||||||
|
|
||||||
[前端页面设计图](https://codesign.qq.com/app/s/526108506410828)
|
|
||||||
|
|||||||
@@ -1,72 +1,18 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../README.md#%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3)
|
||||||
|
|
||||||
# 项目介绍
|
# 项目介绍
|
||||||
|
|
||||||
manager-api 该项目基于SpringBoot框架开发。
|
manager-api 该项目基于SpringBoot框架开发。
|
||||||
|
|
||||||
开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录
|
开发使用代码编辑器,导入项目时,选择`manager-api`文件夹作为项目目录
|
||||||
|
|
||||||
参照[manager前后端接口协议](https://app.apifox.com/invite/project?token=H_8qhgfjUeaAL0wybghgU)开发
|
|
||||||
|
|
||||||
# 开发环境
|
# 开发环境
|
||||||
JDK 21
|
JDK 21
|
||||||
Maven 3.8+
|
Maven 3.8+
|
||||||
MySQL 8.0+
|
MySQL 8.0+
|
||||||
|
Redis 5.0+
|
||||||
Vue 3.x
|
Vue 3.x
|
||||||
|
|
||||||
# 创建数据库
|
|
||||||
|
|
||||||
如果本机已经安装了MySQL,可以直接在数据库中创建名为`xiaozhi_esp32_server`的数据库。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
```
|
|
||||||
|
|
||||||
如果还没有MySQL,你可以通过docker安装mysql
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run --name xiaozhi-esp32-server-db \
|
|
||||||
-e MYSQL_ROOT_PASSWORD=123456 \
|
|
||||||
-p 3306:3306 \
|
|
||||||
-e MYSQL_DATABASE=xiaozhi_esp32_server \
|
|
||||||
-e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" \
|
|
||||||
-d mysql:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
# 确认项目数据库连接信息
|
|
||||||
|
|
||||||
在`src/main/resources/application-dev.yml`中配置数据库连接信息
|
|
||||||
|
|
||||||
```
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
username: root
|
|
||||||
password: 123456
|
|
||||||
```
|
|
||||||
|
|
||||||
# 测试启动
|
|
||||||
|
|
||||||
本项目为SpringBoot项目,启动方式为:
|
|
||||||
打开`Application.java`运行`Main`方法启动
|
|
||||||
|
|
||||||
```
|
|
||||||
路径地址:
|
|
||||||
src/main/java/xiaozhi/AdminApplication.java
|
|
||||||
```
|
|
||||||
|
|
||||||
# 打包编译
|
|
||||||
|
|
||||||
执行以下命令生产jar包
|
|
||||||
|
|
||||||
```
|
|
||||||
mvn install
|
|
||||||
```
|
|
||||||
|
|
||||||
把jar包放在服务器上,执行
|
|
||||||
|
|
||||||
```
|
|
||||||
nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.active=dev >/dev/null &
|
|
||||||
```
|
|
||||||
|
|
||||||
# 接口文档
|
# 接口文档
|
||||||
启动后打开:http://localhost:8002/xiaozhi-esp32-api/doc.html
|
启动后打开:http://localhost:8002/xiaozhi/doc.html
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<junit.version>5.12.0</junit.version>
|
<junit.version>5.10.1</junit.version>
|
||||||
<druid.version>1.2.20</druid.version>
|
<druid.version>1.2.20</druid.version>
|
||||||
<mybatisplus.version>3.5.5</mybatisplus.version>
|
<mybatisplus.version>3.5.5</mybatisplus.version>
|
||||||
<hutool.version>5.8.24</hutool.version>
|
<hutool.version>5.8.24</hutool.version>
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
<shiro.version>2.0.2</shiro.version>
|
<shiro.version>2.0.2</shiro.version>
|
||||||
<captcha.version>1.6.2</captcha.version>
|
<captcha.version>1.6.2</captcha.version>
|
||||||
<guava.version>33.0.0-jre</guava.version>
|
<guava.version>33.0.0-jre</guava.version>
|
||||||
<easyexcel.version>3.3.2</easyexcel.version>
|
|
||||||
<liquibase-core.version>4.20.0</liquibase-core.version>
|
<liquibase-core.version>4.20.0</liquibase-core.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -88,6 +87,36 @@
|
|||||||
<version>${junit.version}</version>
|
<version>${junit.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-launcher</artifactId>
|
||||||
|
<version>1.10.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-commons</artifactId>
|
||||||
|
<version>1.10.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-engine</artifactId>
|
||||||
|
<version>1.10.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
@@ -168,6 +197,10 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<!-- 阿里云maven仓库 -->
|
<!-- 阿里云maven仓库 -->
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ public class AdminApplication {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(AdminApplication.class, args);
|
SpringApplication.run(AdminApplication.class, args);
|
||||||
System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html");
|
System.out.println("http://localhost:8002/xiaozhi/doc.html");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package xiaozhi.common.annotation;
|
package xiaozhi.common.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据过滤注解
|
* 数据过滤注解
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package xiaozhi.common.annotation;
|
package xiaozhi.common.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.*;
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志注解
|
* 操作日志注解
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package xiaozhi.common.aspect;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import xiaozhi.common.annotation.DataFilter;
|
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
import xiaozhi.common.interceptor.DataScope;
|
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Before;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据过滤,切面处理类
|
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
|
||||||
* Website: https://www.renren.io
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
public class DataFilterAspect {
|
|
||||||
|
|
||||||
@Pointcut("@annotation(xiaozhi.common.annotation.DataFilter)")
|
|
||||||
public void dataFilterCut() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before("dataFilterCut()")
|
|
||||||
public void dataFilter(JoinPoint point) {
|
|
||||||
Object params = point.getArgs()[0];
|
|
||||||
if (params != null && params instanceof Map) {
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
|
|
||||||
//如果是超级管理员,则不进行数据过滤
|
|
||||||
if (user.getSuperAdmin() == SuperAdminEnum.YES.value()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
//否则进行数据过滤
|
|
||||||
Map map = (Map) params;
|
|
||||||
String sqlFilter = getSqlFilter(user, point);
|
|
||||||
map.put(Constant.SQL_FILTER, new DataScope(sqlFilter));
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RenException(ErrorCode.DATA_SCOPE_PARAMS_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数据过滤的SQL
|
|
||||||
*/
|
|
||||||
private String getSqlFilter(UserDetail user, JoinPoint point) throws Exception {
|
|
||||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
|
||||||
Method method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
|
|
||||||
DataFilter dataFilter = method.getAnnotation(DataFilter.class);
|
|
||||||
|
|
||||||
//获取表的别名
|
|
||||||
String tableAlias = dataFilter.tableAlias();
|
|
||||||
if (StringUtils.isNotBlank(tableAlias)) {
|
|
||||||
tableAlias += ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder sqlFilter = new StringBuilder();
|
|
||||||
sqlFilter.append(" (");
|
|
||||||
|
|
||||||
//部门ID列表
|
|
||||||
List<Long> deptIdList = user.getDeptIdList();
|
|
||||||
if (CollUtil.isNotEmpty(deptIdList)) {
|
|
||||||
sqlFilter.append(tableAlias).append(dataFilter.deptId());
|
|
||||||
|
|
||||||
sqlFilter.append(" in(").append(StringUtils.join(deptIdList, ",")).append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
//查询本人数据
|
|
||||||
if (CollUtil.isNotEmpty(deptIdList)) {
|
|
||||||
sqlFilter.append(" or ");
|
|
||||||
}
|
|
||||||
sqlFilter.append(tableAlias).append(dataFilter.userId()).append("=").append(user.getId());
|
|
||||||
|
|
||||||
sqlFilter.append(")");
|
|
||||||
|
|
||||||
return sqlFilter.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package xiaozhi.common.aspect;
|
package xiaozhi.common.aspect;
|
||||||
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis切面处理类
|
* Redis切面处理类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -19,7 +20,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class RedisAspect {
|
public class RedisAspect {
|
||||||
/**
|
/**
|
||||||
* 是否开启redis缓存 true开启 false关闭
|
* 是否开启redis缓存 true开启 false关闭
|
||||||
*/
|
*/
|
||||||
@Value("${renren.redis.open}")
|
@Value("${renren.redis.open}")
|
||||||
private boolean open;
|
private boolean open;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package xiaozhi.common.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.RejectedExecutionHandler;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAsync
|
||||||
|
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||||
|
public class AsyncConfig {
|
||||||
|
|
||||||
|
@Bean(name = "taskExecutor")
|
||||||
|
public Executor taskExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(2);
|
||||||
|
executor.setMaxPoolSize(4);
|
||||||
|
executor.setQueueCapacity(1000);
|
||||||
|
executor.setThreadNamePrefix("AsyncThread-");
|
||||||
|
// 设置拒绝策略:由调用线程执行
|
||||||
|
executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
|
||||||
|
@Override
|
||||||
|
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||||
|
try {
|
||||||
|
// 如果线程池已满,则由调用线程执行
|
||||||
|
r.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("执行异步任务失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package xiaozhi.common.config;
|
package xiaozhi.common.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
|
|
||||||
import xiaozhi.common.interceptor.DataFilterInterceptor;
|
import xiaozhi.common.interceptor.DataFilterInterceptor;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mybatis-plus配置
|
* mybatis-plus配置
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package xiaozhi.common.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RestTemplate配置
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RestTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate() {
|
||||||
|
return new RestTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package xiaozhi.common.config;
|
package xiaozhi.common.config;
|
||||||
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
|
||||||
import org.springdoc.core.models.GroupedOpenApi;
|
import org.springdoc.core.models.GroupedOpenApi;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swagger配置
|
* Swagger配置
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -14,11 +15,68 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class SwaggerConfig {
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi deviceApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("device")
|
||||||
|
.pathsToMatch("/device/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi agentApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("agent")
|
||||||
|
.pathsToMatch("/agent/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi modelApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("models")
|
||||||
|
.pathsToMatch("/models/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi oatApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("ota")
|
||||||
|
.pathsToMatch("/ota/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi timbreApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("timbre")
|
||||||
|
.pathsToMatch("/ttsVoice/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi sysApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("admin")
|
||||||
|
.pathsToMatch("/admin/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public GroupedOpenApi userApi() {
|
public GroupedOpenApi userApi() {
|
||||||
String[] paths = {"/**"};
|
return GroupedOpenApi.builder()
|
||||||
return GroupedOpenApi.builder().group("xiaozhi")
|
.group("user")
|
||||||
.pathsToMatch(paths).build();
|
.pathsToMatch("/user/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi configApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("config")
|
||||||
|
.pathsToMatch("/config/**")
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package xiaozhi.common.constant;
|
package xiaozhi.common.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 常量
|
* 常量
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -73,18 +75,47 @@ public interface Constant {
|
|||||||
* 排序方式
|
* 排序方式
|
||||||
*/
|
*/
|
||||||
String ORDER = "order";
|
String ORDER = "order";
|
||||||
/**
|
|
||||||
* token header
|
|
||||||
*/
|
|
||||||
String TOKEN_HEADER = "token";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头授权标识
|
||||||
|
*/
|
||||||
String AUTHORIZATION = "Authorization";
|
String AUTHORIZATION = "Authorization";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器密钥
|
||||||
|
*/
|
||||||
|
String SERVER_SECRET = "server.secret";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* websocket地址
|
||||||
|
*/
|
||||||
|
String SERVER_WEBSOCKET = "server.websocket";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ota地址
|
||||||
|
*/
|
||||||
|
String SERVER_OTA = "server.ota";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许用户注册
|
||||||
|
*/
|
||||||
|
String SERVER_ALLOW_USER_REGISTER = "server.allow_user_register";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下发六位验证码时显示的控制面板地址
|
||||||
|
*/
|
||||||
|
String SERVER_FRONTED_URL = "server.fronted_url";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路径分割符
|
* 路径分割符
|
||||||
*/
|
*/
|
||||||
String FILE_EXTENSION_SEG = ".";
|
String FILE_EXTENSION_SEG = ".";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无记忆
|
||||||
|
*/
|
||||||
|
String MEMORY_NO_MEM = "Memory_nomem";
|
||||||
|
|
||||||
enum SysBaseParam {
|
enum SysBaseParam {
|
||||||
/**
|
/**
|
||||||
* 系统全称
|
* 系统全称
|
||||||
@@ -149,4 +180,24 @@ public interface Constant {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
enum ChatHistoryConfEnum {
|
||||||
|
IGNORE(0, "不记录"),
|
||||||
|
RECORD_TEXT(1, "记录文本"),
|
||||||
|
RECORD_TEXT_AUDIO(2, "文本音频都记录");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
ChatHistoryConfEnum(int code, String name) {
|
||||||
|
this.code = code;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "0.4.3";
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
package xiaozhi.common.convert;
|
package xiaozhi.common.convert;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 日期转换
|
* 日期转换
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package xiaozhi.common.entity;
|
package xiaozhi.common.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import lombok.Data;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础实体类,所有实体都需要继承
|
* 基础实体类,所有实体都需要继承
|
||||||
|
|||||||
@@ -43,4 +43,16 @@ public interface ErrorCode {
|
|||||||
int PASSWORD_WEAK_ERROR = 10031;
|
int PASSWORD_WEAK_ERROR = 10031;
|
||||||
int DEL_MYSELF_ERROR = 10032;
|
int DEL_MYSELF_ERROR = 10032;
|
||||||
int DEVICE_CAPTCHA_ERROR = 10033;
|
int DEVICE_CAPTCHA_ERROR = 10033;
|
||||||
|
|
||||||
|
// 参数校验相关错误码
|
||||||
|
int PARAM_VALUE_NULL = 10034;
|
||||||
|
int PARAM_TYPE_NULL = 10035;
|
||||||
|
int PARAM_TYPE_INVALID = 10036;
|
||||||
|
int PARAM_NUMBER_INVALID = 10037;
|
||||||
|
int PARAM_BOOLEAN_INVALID = 10038;
|
||||||
|
int PARAM_ARRAY_INVALID = 10039;
|
||||||
|
int PARAM_JSON_INVALID = 10040;
|
||||||
|
|
||||||
|
int OTA_DEVICE_NOT_FOUND = 10041;
|
||||||
|
int OTA_DEVICE_NEED_BIND = 10042;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.common.exception;
|
package xiaozhi.common.exception;
|
||||||
|
|
||||||
|
|
||||||
import xiaozhi.common.utils.MessageUtils;
|
import xiaozhi.common.utils.MessageUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,7 +9,6 @@ import xiaozhi.common.utils.MessageUtils;
|
|||||||
*/
|
*/
|
||||||
public class RenException extends RuntimeException {
|
public class RenException extends RuntimeException {
|
||||||
|
|
||||||
|
|
||||||
private int code;
|
private int code;
|
||||||
private String msg;
|
private String msg;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package xiaozhi.common.exception;
|
package xiaozhi.common.exception;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.shiro.authz.UnauthorizedException;
|
import org.apache.shiro.authz.UnauthorizedException;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
import xiaozhi.common.utils.Result;
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异常处理器
|
* 异常处理器
|
||||||
@@ -23,35 +24,40 @@ public class RenExceptionHandler {
|
|||||||
* 处理自定义异常
|
* 处理自定义异常
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(RenException.class)
|
@ExceptionHandler(RenException.class)
|
||||||
public Result handleRenException(RenException ex) {
|
public Result<Void> handleRenException(RenException ex) {
|
||||||
Result result = new Result();
|
Result<Void> result = new Result<>();
|
||||||
result.error(ex.getCode(), ex.getMsg());
|
result.error(ex.getCode(), ex.getMsg());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(DuplicateKeyException.class)
|
@ExceptionHandler(DuplicateKeyException.class)
|
||||||
public Result handleDuplicateKeyException(DuplicateKeyException ex) {
|
public Result<Void> handleDuplicateKeyException(DuplicateKeyException ex) {
|
||||||
Result result = new Result();
|
Result<Void> result = new Result<>();
|
||||||
result.error(ErrorCode.DB_RECORD_EXISTS);
|
result.error(ErrorCode.DB_RECORD_EXISTS);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(UnauthorizedException.class)
|
@ExceptionHandler(UnauthorizedException.class)
|
||||||
public Result handleUnauthorizedException(UnauthorizedException ex) {
|
public Result<Void> handleUnauthorizedException(UnauthorizedException ex) {
|
||||||
Result result = new Result();
|
Result<Void> result = new Result<>();
|
||||||
result.error(ErrorCode.FORBIDDEN);
|
result.error(ErrorCode.FORBIDDEN);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public Result handleException(Exception ex) {
|
public Result<Void> handleException(Exception ex) {
|
||||||
log.error(ex.getMessage(), ex);
|
log.error(ex.getMessage(), ex);
|
||||||
|
|
||||||
return new Result().error();
|
return new Result<Void>().error();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NoResourceFoundException.class)
|
||||||
|
public Result<Void> handleNoResourceFoundException(NoResourceFoundException ex) {
|
||||||
|
log.warn("Resource not found: {}", ex.getMessage());
|
||||||
|
return new Result<Void>().error(404, "资源不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
package xiaozhi.common.handler;
|
package xiaozhi.common.handler;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
import java.util.Date;
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Date;
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
|
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公共字段,自动填充值
|
* 公共字段,自动填充值
|
||||||
@@ -22,38 +24,34 @@ public class FieldMetaObjectHandler implements MetaObjectHandler {
|
|||||||
private final static String UPDATER = "updater";
|
private final static String UPDATER = "updater";
|
||||||
|
|
||||||
private final static String DATA_OPERATION = "dataOperation";
|
private final static String DATA_OPERATION = "dataOperation";
|
||||||
private final static String DEPT_ID = "deptId";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insertFill(MetaObject metaObject) {
|
public void insertFill(MetaObject metaObject) {
|
||||||
UserDetail user = SecurityUser.getUser();
|
UserDetail user = SecurityUser.getUser();
|
||||||
Date date = new Date();
|
Date date = new Date();
|
||||||
|
|
||||||
//创建者
|
// 创建者
|
||||||
strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
|
strictInsertFill(metaObject, CREATOR, Long.class, user.getId());
|
||||||
//创建时间
|
// 创建时间
|
||||||
strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
|
strictInsertFill(metaObject, CREATE_DATE, Date.class, date);
|
||||||
|
|
||||||
//创建者所属部门
|
// 更新者
|
||||||
strictInsertFill(metaObject, DEPT_ID, Long.class, user.getDeptId());
|
|
||||||
|
|
||||||
//更新者
|
|
||||||
strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
|
strictInsertFill(metaObject, UPDATER, Long.class, user.getId());
|
||||||
//更新时间
|
// 更新时间
|
||||||
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
|
strictInsertFill(metaObject, UPDATE_DATE, Date.class, date);
|
||||||
|
|
||||||
//数据标识
|
// 数据标识
|
||||||
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
|
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.INSERT.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateFill(MetaObject metaObject) {
|
public void updateFill(MetaObject metaObject) {
|
||||||
//更新者
|
// 更新者
|
||||||
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
|
strictUpdateFill(metaObject, UPDATER, Long.class, SecurityUser.getUserId());
|
||||||
//更新时间
|
// 更新时间
|
||||||
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
|
strictUpdateFill(metaObject, UPDATE_DATE, Date.class, new Date());
|
||||||
|
|
||||||
//数据标识
|
// 数据标识
|
||||||
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
|
strictInsertFill(metaObject, DATA_OPERATION, String.class, Constant.DataOperation.UPDATE.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
package xiaozhi.common.interceptor;
|
package xiaozhi.common.interceptor;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.ibatis.executor.Executor;
|
||||||
|
import org.apache.ibatis.mapping.BoundSql;
|
||||||
|
import org.apache.ibatis.mapping.MappedStatement;
|
||||||
|
import org.apache.ibatis.session.ResultHandler;
|
||||||
|
import org.apache.ibatis.session.RowBounds;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import net.sf.jsqlparser.JSQLParserException;
|
import net.sf.jsqlparser.JSQLParserException;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.expression.StringValue;
|
import net.sf.jsqlparser.expression.StringValue;
|
||||||
@@ -10,13 +19,6 @@ import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
|||||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||||
import net.sf.jsqlparser.statement.select.Select;
|
import net.sf.jsqlparser.statement.select.Select;
|
||||||
import org.apache.ibatis.executor.Executor;
|
|
||||||
import org.apache.ibatis.mapping.BoundSql;
|
|
||||||
import org.apache.ibatis.mapping.MappedStatement;
|
|
||||||
import org.apache.ibatis.session.ResultHandler;
|
|
||||||
import org.apache.ibatis.session.RowBounds;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据过滤
|
* 数据过滤
|
||||||
@@ -25,8 +27,10 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class DataFilterInterceptor implements InnerInterceptor {
|
public class DataFilterInterceptor implements InnerInterceptor {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
|
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
|
||||||
|
ResultHandler resultHandler, BoundSql boundSql) {
|
||||||
DataScope scope = getDataScope(parameter);
|
DataScope scope = getDataScope(parameter);
|
||||||
// 不进行数据过滤
|
// 不进行数据过滤
|
||||||
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
|
if (scope == null || StrUtil.isBlank(scope.getSqlFilter())) {
|
||||||
@@ -48,7 +52,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
|
|||||||
// 判断参数里是否有DataScope对象
|
// 判断参数里是否有DataScope对象
|
||||||
if (parameter instanceof Map) {
|
if (parameter instanceof Map) {
|
||||||
Map<?, ?> parameterMap = (Map<?, ?>) parameter;
|
Map<?, ?> parameterMap = (Map<?, ?>) parameter;
|
||||||
for (Map.Entry entry : parameterMap.entrySet()) {
|
for (Map.Entry<?, ?> entry : parameterMap.entrySet()) {
|
||||||
if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
|
if (entry.getValue() != null && entry.getValue() instanceof DataScope) {
|
||||||
return (DataScope) entry.getValue();
|
return (DataScope) entry.getValue();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package xiaozhi.common.page;
|
package xiaozhi.common.page;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页工具类
|
* 分页工具类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package xiaozhi.common.page;
|
package xiaozhi.common.page;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 令牌信息
|
* 令牌信息
|
||||||
*
|
*
|
||||||
@@ -14,7 +14,6 @@ import java.io.Serializable;
|
|||||||
@Schema(description = "令牌信息")
|
@Schema(description = "令牌信息")
|
||||||
public class TokenDTO implements Serializable {
|
public class TokenDTO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
@Schema(description = "密码")
|
@Schema(description = "密码")
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.common.redis;
|
package xiaozhi.common.redis;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
@@ -8,6 +7,8 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis配置
|
* Redis配置
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -26,4 +26,95 @@ public class RedisKeys {
|
|||||||
public static String getDeviceCaptchaKey(String captcha) {
|
public static String getDeviceCaptchaKey(String captcha) {
|
||||||
return "sys:device:captcha:" + captcha;
|
return "sys:device:captcha:" + captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id的Key
|
||||||
|
*/
|
||||||
|
public static String getUserIdKey(Long userid) {
|
||||||
|
return "sys:username:id:" + userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称的Key
|
||||||
|
*/
|
||||||
|
public static String getModelNameById(String id) {
|
||||||
|
return "model:name:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型配置的Key
|
||||||
|
*/
|
||||||
|
public static String getModelConfigById(String id) {
|
||||||
|
return "model:data:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音色名称缓存key
|
||||||
|
*/
|
||||||
|
public static String getTimbreNameById(String id) {
|
||||||
|
return "timbre:name:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备数量缓存key
|
||||||
|
*/
|
||||||
|
public static String getAgentDeviceCountById(String id) {
|
||||||
|
return "agent:device:count:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能体最后连接时间缓存key
|
||||||
|
*/
|
||||||
|
public static String getAgentDeviceLastConnectedAtById(String id) {
|
||||||
|
return "agent:device:lastConnected:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统配置缓存key
|
||||||
|
*/
|
||||||
|
public static String getServerConfigKey() {
|
||||||
|
return "server:config";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音色详情缓存key
|
||||||
|
*/
|
||||||
|
public static String getTimbreDetailsKey(String id) {
|
||||||
|
return "timbre:details:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取版本号Key
|
||||||
|
*/
|
||||||
|
public static String getVersionKey() {
|
||||||
|
return "sys:version";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA固件ID的Key
|
||||||
|
*/
|
||||||
|
public static String getOtaIdKey(String uuid) {
|
||||||
|
return "ota:id:" + uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OTA固件下载次数的Key
|
||||||
|
*/
|
||||||
|
public static String getOtaDownloadCountKey(String uuid) {
|
||||||
|
return "ota:download:count:" + uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典数据的缓存key
|
||||||
|
*/
|
||||||
|
public static String getDictDataByTypeKey(String dictType) {
|
||||||
|
return "sys:dict:data:" + dictType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能体音频ID的缓存key
|
||||||
|
*/
|
||||||
|
public static String getAgentAudioIdKey(String uuid) {
|
||||||
|
return "agent:audio:id:" + uuid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package xiaozhi.common.redis;
|
package xiaozhi.common.redis;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.data.redis.core.HashOperations;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.data.redis.core.HashOperations;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redis工具类
|
* Redis工具类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -26,7 +30,7 @@ public class RedisUtils {
|
|||||||
/**
|
/**
|
||||||
* 过期时长为1小时,单位:秒
|
* 过期时长为1小时,单位:秒
|
||||||
*/
|
*/
|
||||||
public final static long HOUR_ONE_EXPIRE = 60 * 60 * 1L;
|
public final static long HOUR_ONE_EXPIRE = (long) 60 * 60;
|
||||||
/**
|
/**
|
||||||
* 过期时长为6小时,单位:秒
|
* 过期时长为6小时,单位:秒
|
||||||
*/
|
*/
|
||||||
@@ -123,4 +127,24 @@ public class RedisUtils {
|
|||||||
public Object rightPop(String key) {
|
public Object rightPop(String key) {
|
||||||
return redisTemplate.opsForList().rightPop(key);
|
return redisTemplate.opsForList().rightPop(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有 Redis 数据库中的所有键
|
||||||
|
*/
|
||||||
|
public void emptyAll() {
|
||||||
|
// Lua 脚本 FLUSHALL是redis清空所有库的命令
|
||||||
|
String luaScript ="redis.call('FLUSHALL')";
|
||||||
|
|
||||||
|
// 创建 DefaultRedisScript 对象
|
||||||
|
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
|
||||||
|
redisScript.setScriptText(luaScript); // 设置 Lua 脚本内容
|
||||||
|
redisScript.setResultType(Void.class); // 设置返回值类型
|
||||||
|
|
||||||
|
// 执行 Lua 脚本
|
||||||
|
List<String> keys = Collections.emptyList(); // 如果脚本不依赖 key,可以传入空列表
|
||||||
|
redisTemplate.execute(redisScript, keys);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package xiaozhi.common.service;
|
package xiaozhi.common.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础服务接口,所有Service接口都要继承
|
* 基础服务接口,所有Service接口都要继承
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -56,7 +56,8 @@ public interface BaseService<T> {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param entity 实体对象
|
* @param entity 实体对象
|
||||||
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
* @param updateWrapper 实体对象封装操作类
|
||||||
|
* {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
|
||||||
*/
|
*/
|
||||||
boolean update(T entity, Wrapper<T> updateWrapper);
|
boolean update(T entity, Wrapper<T> updateWrapper);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package xiaozhi.common.service;
|
package xiaozhi.common.service;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CRUD基础服务接口
|
* CRUD基础服务接口
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
package xiaozhi.common.service.impl;
|
package xiaozhi.common.service.impl;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.apache.ibatis.binding.MapperMethod;
|
||||||
|
import org.apache.ibatis.logging.Log;
|
||||||
|
import org.apache.ibatis.logging.LogFactory;
|
||||||
|
import org.apache.ibatis.session.SqlSession;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
@@ -10,22 +23,11 @@ import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
|||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
|
||||||
|
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import org.apache.ibatis.binding.MapperMethod;
|
|
||||||
import org.apache.ibatis.logging.Log;
|
|
||||||
import org.apache.ibatis.logging.LogFactory;
|
|
||||||
import org.apache.ibatis.session.SqlSession;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础服务类,所有Service都要继承
|
* 基础服务类,所有Service都要继承
|
||||||
@@ -45,7 +47,7 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
* @param isAsc 排序方式
|
* @param isAsc 排序方式
|
||||||
*/
|
*/
|
||||||
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
|
protected IPage<T> getPage(Map<String, Object> params, String defaultOrderField, boolean isAsc) {
|
||||||
//分页参数
|
// 分页参数
|
||||||
long curPage = 1;
|
long curPage = 1;
|
||||||
long limit = 10;
|
long limit = 10;
|
||||||
|
|
||||||
@@ -56,17 +58,17 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
limit = Long.parseLong((String) params.get(Constant.LIMIT));
|
limit = Long.parseLong((String) params.get(Constant.LIMIT));
|
||||||
}
|
}
|
||||||
|
|
||||||
//分页对象
|
// 分页对象
|
||||||
Page<T> page = new Page<>(curPage, limit);
|
Page<T> page = new Page<>(curPage, limit);
|
||||||
|
|
||||||
//分页参数
|
// 分页参数
|
||||||
params.put(Constant.PAGE, page);
|
params.put(Constant.PAGE, page);
|
||||||
|
|
||||||
//排序字段
|
// 排序字段
|
||||||
String orderField = (String) params.get(Constant.ORDER_FIELD);
|
String orderField = (String) params.get(Constant.ORDER_FIELD);
|
||||||
String order = (String) params.get(Constant.ORDER);
|
String order = (String) params.get(Constant.ORDER);
|
||||||
|
|
||||||
//前端字段排序
|
// 前端字段排序
|
||||||
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
|
if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(order)) {
|
||||||
if (Constant.ASC.equalsIgnoreCase(order)) {
|
if (Constant.ASC.equalsIgnoreCase(order)) {
|
||||||
return page.addOrder(OrderItem.asc(orderField));
|
return page.addOrder(OrderItem.asc(orderField));
|
||||||
@@ -75,12 +77,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//没有排序字段,则不排序
|
// 没有排序字段,则不排序
|
||||||
if (StringUtils.isBlank(defaultOrderField)) {
|
if (StringUtils.isBlank(defaultOrderField)) {
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
//默认排序
|
// 默认排序
|
||||||
if (isAsc) {
|
if (isAsc) {
|
||||||
page.addOrder(OrderItem.asc(defaultOrderField));
|
page.addOrder(OrderItem.asc(defaultOrderField));
|
||||||
} else {
|
} else {
|
||||||
@@ -90,13 +92,13 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> PageData<T> getPageData(List<?> list, long total, Class<T> target) {
|
protected <D> PageData<D> getPageData(List<?> list, long total, Class<D> target) {
|
||||||
List<T> targetList = ConvertUtils.sourceToTarget(list, target);
|
List<D> targetList = ConvertUtils.sourceToTarget(list, target);
|
||||||
|
|
||||||
return new PageData<>(targetList, total);
|
return new PageData<>(targetList, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> PageData<T> getPageData(IPage page, Class<T> target) {
|
protected <D> PageData<D> getPageData(IPage<?> page, Class<D> target) {
|
||||||
return getPageData(page.getRecords(), page.getTotal(), target);
|
return getPageData(page.getRecords(), page.getTotal(), target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +166,11 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
|||||||
/**
|
/**
|
||||||
* 执行批量操作
|
* 执行批量操作
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
|
||||||
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
return SqlHelper.executeBatch(this.currentModelClass(), this.log, list, batchSize, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public boolean updateById(T entity) {
|
public boolean updateById(T entity) {
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
package xiaozhi.common.service.impl;
|
package xiaozhi.common.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.common.service.CrudService;
|
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||||
|
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.service.CrudService;
|
||||||
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CRUD基础服务类
|
* CRUD基础服务类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
* Website: https://www.renren.io
|
* Website: https://www.renren.io
|
||||||
*/
|
*/
|
||||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T> implements CrudService<T, D> {
|
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
||||||
|
implements CrudService<T, D> {
|
||||||
|
|
||||||
protected Class<D> currentDtoClass() {
|
protected Class<D> currentDtoClass() {
|
||||||
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
||||||
@@ -29,8 +32,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
|||||||
public PageData<D> page(Map<String, Object> params) {
|
public PageData<D> page(Map<String, Object> params) {
|
||||||
IPage<T> page = baseDao.selectPage(
|
IPage<T> page = baseDao.selectPage(
|
||||||
getPage(params, null, false),
|
getPage(params, null, false),
|
||||||
getWrapper(params)
|
getWrapper(params));
|
||||||
);
|
|
||||||
|
|
||||||
return getPageData(page, currentDtoClass());
|
return getPageData(page, currentDtoClass());
|
||||||
}
|
}
|
||||||
@@ -56,7 +58,7 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
|||||||
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
|
T entity = ConvertUtils.sourceToTarget(dto, currentModelClass());
|
||||||
insert(entity);
|
insert(entity);
|
||||||
|
|
||||||
//copy主键值到dto
|
// copy主键值到dto
|
||||||
BeanUtils.copyProperties(entity, dto);
|
BeanUtils.copyProperties(entity, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package xiaozhi.common.user;
|
package xiaozhi.common.user;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录用户信息
|
* 登录用户信息
|
||||||
@@ -14,19 +13,7 @@ import java.util.List;
|
|||||||
public class UserDetail implements Serializable {
|
public class UserDetail implements Serializable {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String username;
|
private String username;
|
||||||
private String realName;
|
|
||||||
private String headUrl;
|
|
||||||
private Integer gender;
|
|
||||||
private String email;
|
|
||||||
private String mobile;
|
|
||||||
private Long deptId;
|
|
||||||
private String password;
|
|
||||||
private Integer status;
|
|
||||||
private Integer superAdmin;
|
private Integer superAdmin;
|
||||||
private String token;
|
private String token;
|
||||||
/**
|
private Integer status;
|
||||||
* 部门数据权限
|
|
||||||
*/
|
|
||||||
private List<Long> deptIdList;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换工具类
|
* 转换工具类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -20,7 +21,7 @@ public class ConvertUtils {
|
|||||||
}
|
}
|
||||||
T targetObject = null;
|
T targetObject = null;
|
||||||
try {
|
try {
|
||||||
targetObject = target.newInstance();
|
targetObject = target.getDeclaredConstructor().newInstance();
|
||||||
BeanUtils.copyProperties(source, targetObject);
|
BeanUtils.copyProperties(source, targetObject);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("convert error ", e);
|
log.error("convert error ", e);
|
||||||
@@ -34,10 +35,10 @@ public class ConvertUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List targetList = new ArrayList<>(sourceList.size());
|
List<T> targetList = new ArrayList<>(sourceList.size());
|
||||||
try {
|
try {
|
||||||
for (Object source : sourceList) {
|
for (Object source : sourceList) {
|
||||||
T targetObject = target.newInstance();
|
T targetObject = target.getDeclaredConstructor().newInstance();
|
||||||
BeanUtils.copyProperties(source, targetObject);
|
BeanUtils.copyProperties(source, targetObject);
|
||||||
targetList.add(targetObject);
|
targetList.add(targetObject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package xiaozhi.common.utils;
|
|||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,4 +63,39 @@ public class DateUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getShortTime(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 将 Date 转换为 Instant
|
||||||
|
LocalDateTime localDateTime = date.toInstant()
|
||||||
|
// 获取系统默认时区
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
// 转换为 LocalDateTime
|
||||||
|
.toLocalDateTime();
|
||||||
|
// 当前时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
// 时间差,单位为秒
|
||||||
|
long secondsBetween = ChronoUnit.SECONDS.between(localDateTime, now);
|
||||||
|
|
||||||
|
if (secondsBetween <= 10) {
|
||||||
|
return "刚刚";
|
||||||
|
} else if (secondsBetween < 60) {
|
||||||
|
return secondsBetween + "秒前";
|
||||||
|
} else if (secondsBetween < 60 * 60) {
|
||||||
|
return secondsBetween / 60 + "分钟前";
|
||||||
|
} else if (secondsBetween < 86400) {
|
||||||
|
return secondsBetween / 3600 + "小时前";
|
||||||
|
} else if (secondsBetween < 604800) {
|
||||||
|
return secondsBetween / 86400 + "天前";
|
||||||
|
} else {
|
||||||
|
// 超过一周,显示完整日期时间
|
||||||
|
return format(date,DATE_TIME_PATTERN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import xiaozhi.common.constant.Constant;
|
import java.util.Date;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
@@ -9,10 +12,9 @@ import org.springframework.web.context.request.RequestAttributes;
|
|||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
import java.util.Date;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.util.Enumeration;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import java.util.HashMap;
|
import xiaozhi.common.exception.RenException;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http
|
* Http
|
||||||
@@ -30,13 +32,14 @@ public class HttpContextUtils {
|
|||||||
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
return ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getToken() {
|
public static String getToken(String authorization) {
|
||||||
HttpServletRequest httpRequest = getHttpServletRequest();
|
String token;
|
||||||
String token = httpRequest.getHeader(Constant.TOKEN_HEADER);
|
if (StringUtils.isBlank(authorization) && authorization.contains("Bearer ")) {
|
||||||
|
throw new RenException(ErrorCode.UNAUTHORIZED);
|
||||||
//如果header中不存在token,则从参数中获取token
|
}
|
||||||
|
token = authorization.replace("Bearer ", "");
|
||||||
if (StringUtils.isBlank(token)) {
|
if (StringUtils.isBlank(token)) {
|
||||||
token = httpRequest.getParameter(Constant.TOKEN_HEADER);
|
throw new RenException(ErrorCode.TOKEN_NOT_EMPTY);
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
@@ -68,15 +71,15 @@ public class HttpContextUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getLanguage() {
|
public static String getLanguage() {
|
||||||
//默认语言
|
// 默认语言
|
||||||
String defaultLanguage = "zh-CN";
|
String defaultLanguage = "zh-CN";
|
||||||
//request
|
// request
|
||||||
HttpServletRequest request = getHttpServletRequest();
|
HttpServletRequest request = getHttpServletRequest();
|
||||||
if (request == null) {
|
if (request == null) {
|
||||||
return defaultLanguage;
|
return defaultLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
//请求语言
|
// 请求语言
|
||||||
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
|
defaultLanguage = request.getHeader(HttpHeaders.ACCEPT_LANGUAGE);
|
||||||
|
|
||||||
return defaultLanguage;
|
return defaultLanguage;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP地址
|
* IP地址
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON 工具类
|
* JSON 工具类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -59,7 +60,8 @@ public class JsonUtils {
|
|||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
return objectMapper.readValue(text,
|
||||||
|
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import java.io.Serializable;
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应数据
|
* 响应数据
|
||||||
@@ -42,10 +36,6 @@ public class Result<T> implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean success() {
|
|
||||||
return code == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result<T> error() {
|
public Result<T> error() {
|
||||||
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
|
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
|
||||||
this.msg = MessageUtils.getMessage(this.code);
|
this.msg = MessageUtils.getMessage(this.code);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树节点,所有需要实现树节点的,都需要继承该类
|
* 树节点,所有需要实现树节点的,都需要继承该类
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import xiaozhi.common.validator.AssertUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import xiaozhi.common.validator.AssertUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 树形结构工具类,如:菜单、部门等
|
* 树形结构工具类,如:菜单、部门等
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -17,8 +17,8 @@ public class TreeUtils {
|
|||||||
/**
|
/**
|
||||||
* 根据pid,构建树节点
|
* 根据pid,构建树节点
|
||||||
*/
|
*/
|
||||||
public static <T extends TreeNode> List<T> build(List<T> treeNodes, Long pid) {
|
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes, Long pid) {
|
||||||
//pid不能为空
|
// pid不能为空
|
||||||
AssertUtils.isNull(pid, "pid");
|
AssertUtils.isNull(pid, "pid");
|
||||||
|
|
||||||
List<T> treeList = new ArrayList<>();
|
List<T> treeList = new ArrayList<>();
|
||||||
@@ -34,7 +34,7 @@ public class TreeUtils {
|
|||||||
/**
|
/**
|
||||||
* 查找子节点
|
* 查找子节点
|
||||||
*/
|
*/
|
||||||
private static <T extends TreeNode> T findChildren(List<T> treeNodes, T rootNode) {
|
private static <T extends TreeNode<T>> T findChildren(List<T> treeNodes, T rootNode) {
|
||||||
for (T treeNode : treeNodes) {
|
for (T treeNode : treeNodes) {
|
||||||
if (rootNode.getId().equals(treeNode.getPid())) {
|
if (rootNode.getId().equals(treeNode.getPid())) {
|
||||||
rootNode.getChildren().add(findChildren(treeNodes, treeNode));
|
rootNode.getChildren().add(findChildren(treeNodes, treeNode));
|
||||||
@@ -46,10 +46,10 @@ public class TreeUtils {
|
|||||||
/**
|
/**
|
||||||
* 构建树节点
|
* 构建树节点
|
||||||
*/
|
*/
|
||||||
public static <T extends TreeNode> List<T> build(List<T> treeNodes) {
|
public static <T extends TreeNode<T>> List<T> build(List<T> treeNodes) {
|
||||||
List<T> result = new ArrayList<>();
|
List<T> result = new ArrayList<>();
|
||||||
|
|
||||||
//list转map
|
// list转map
|
||||||
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
|
Map<Long, T> nodeMap = new LinkedHashMap<>(treeNodes.size());
|
||||||
for (T treeNode : treeNodes) {
|
for (T treeNode : treeNodes) {
|
||||||
nodeMap.put(treeNode.getId(), treeNode);
|
nodeMap.put(treeNode.getId(), treeNode);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package xiaozhi.common.validator;
|
package xiaozhi.common.validator;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验工具类
|
* 校验工具类
|
||||||
@@ -73,11 +74,11 @@ public class AssertUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void isMapEmpty(Map map, String... params) {
|
public static void isMapEmpty(Map<?, ?> map, String... params) {
|
||||||
isMapEmpty(map, ErrorCode.NOT_NULL, params);
|
isMapEmpty(map, ErrorCode.NOT_NULL, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void isMapEmpty(Map map, Integer code, String... params) {
|
public static void isMapEmpty(Map<?, ?> map, Integer code, String... params) {
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
throw new RenException(ErrorCode.NOT_NULL, "code");
|
throw new RenException(ErrorCode.NOT_NULL, "code");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package xiaozhi.common.validator;
|
package xiaozhi.common.validator;
|
||||||
|
|
||||||
import xiaozhi.common.exception.RenException;
|
import java.util.Locale;
|
||||||
import jakarta.validation.ConstraintViolation;
|
import java.util.Set;
|
||||||
import jakarta.validation.Validation;
|
|
||||||
import jakarta.validation.Validator;
|
|
||||||
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
|
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||||
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
|
import org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator;
|
||||||
|
|
||||||
import java.util.Locale;
|
import jakarta.validation.ConstraintViolation;
|
||||||
import java.util.Set;
|
import jakarta.validation.Validation;
|
||||||
|
import jakarta.validation.Validator;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hibernate-validator校验工具类
|
* hibernate-validator校验工具类
|
||||||
@@ -35,7 +36,7 @@ public class ValidatorUtils {
|
|||||||
throws RenException {
|
throws RenException {
|
||||||
Locale.setDefault(LocaleContextHolder.getLocale());
|
Locale.setDefault(LocaleContextHolder.getLocale());
|
||||||
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
|
Validator validator = Validation.byDefaultProvider().configure().messageInterpolator(
|
||||||
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
|
new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(getMessageSource())))
|
||||||
.buildValidatorFactory().getValidator();
|
.buildValidatorFactory().getValidator();
|
||||||
|
|
||||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package xiaozhi.common.xss;
|
package xiaozhi.common.xss;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL过滤
|
* SQL过滤
|
||||||
@@ -20,19 +21,20 @@ public class SqlFilter {
|
|||||||
if (StringUtils.isBlank(str)) {
|
if (StringUtils.isBlank(str)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
//去掉'|"|;|\字符
|
// 去掉'|"|;|\字符
|
||||||
str = StringUtils.replace(str, "'", "");
|
str = StringUtils.replace(str, "'", "");
|
||||||
str = StringUtils.replace(str, "\"", "");
|
str = StringUtils.replace(str, "\"", "");
|
||||||
str = StringUtils.replace(str, ";", "");
|
str = StringUtils.replace(str, ";", "");
|
||||||
str = StringUtils.replace(str, "\\", "");
|
str = StringUtils.replace(str, "\\", "");
|
||||||
|
|
||||||
//转换成小写
|
// 转换成小写
|
||||||
str = str.toLowerCase();
|
str = str.toLowerCase();
|
||||||
|
|
||||||
//非法字符
|
// 非法字符
|
||||||
String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alter", "drop"};
|
String[] keywords = { "master", "truncate", "insert", "select", "delete", "update", "declare", "alter",
|
||||||
|
"drop" };
|
||||||
|
|
||||||
//判断是否包含非法字符
|
// 判断是否包含非法字符
|
||||||
for (String keyword : keywords) {
|
for (String keyword : keywords) {
|
||||||
if (str.contains(keyword)) {
|
if (str.contains(keyword)) {
|
||||||
throw new RenException(ErrorCode.INVALID_SYMBOL);
|
throw new RenException(ErrorCode.INVALID_SYMBOL);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.common.xss;
|
package xiaozhi.common.xss;
|
||||||
|
|
||||||
import jakarta.servlet.DispatcherType;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
@@ -8,6 +7,8 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
|
|
||||||
|
import jakarta.servlet.DispatcherType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XSS 配置文件
|
* XSS 配置文件
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
package xiaozhi.common.xss;
|
package xiaozhi.common.xss;
|
||||||
|
|
||||||
import jakarta.servlet.*;
|
import java.io.IOException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.springframework.util.PathMatcher;
|
import org.springframework.util.PathMatcher;
|
||||||
|
|
||||||
import java.io.IOException;
|
import jakarta.servlet.Filter;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.FilterConfig;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.ServletRequest;
|
||||||
|
import jakarta.servlet.ServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XSS过滤
|
* XSS过滤
|
||||||
@@ -38,7 +44,8 @@ public class XssFilter implements Filter {
|
|||||||
|
|
||||||
private boolean shouldNotFilter(HttpServletRequest request) {
|
private boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
// 放行不过滤的URL
|
// 放行不过滤的URL
|
||||||
return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
|
return properties.getExcludeUrls().stream()
|
||||||
|
.anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, request.getServletPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package xiaozhi.common.xss;
|
package xiaozhi.common.xss;
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import jakarta.servlet.ReadListener;
|
|
||||||
import jakarta.servlet.ServletInputStream;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import jakarta.servlet.ReadListener;
|
||||||
|
import jakarta.servlet.ServletInputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XSS过滤处理
|
* XSS过滤处理
|
||||||
@@ -29,18 +29,18 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
public ServletInputStream getInputStream() throws IOException {
|
||||||
//非json类型,直接返回
|
// 非json类型,直接返回
|
||||||
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
|
if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(HttpHeaders.CONTENT_TYPE))) {
|
||||||
return super.getInputStream();
|
return super.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//为空,直接返回
|
// 为空,直接返回
|
||||||
String json = IoUtil.readUtf8(super.getInputStream());
|
String json = IoUtil.readUtf8(super.getInputStream());
|
||||||
if (StringUtils.isBlank(json)) {
|
if (StringUtils.isBlank(json)) {
|
||||||
return super.getInputStream();
|
return super.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
//xss过滤
|
// xss过滤
|
||||||
json = xssEncode(json);
|
json = xssEncode(json);
|
||||||
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
|
final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
|
||||||
return new ServletInputStream() {
|
return new ServletInputStream() {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package xiaozhi.common.xss;
|
package xiaozhi.common.xss;
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XSS 配置项
|
* XSS 配置项
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ public class XssUtils extends Safelist {
|
|||||||
*/
|
*/
|
||||||
private static Safelist xssWhitelist() {
|
private static Safelist xssWhitelist() {
|
||||||
return new Safelist()
|
return new Safelist()
|
||||||
//支持的标签
|
// 支持的标签
|
||||||
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
|
.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl",
|
||||||
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small",
|
"dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q",
|
||||||
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul",
|
"small",
|
||||||
|
"strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u",
|
||||||
|
"ul",
|
||||||
"embed", "object", "param", "span")
|
"embed", "object", "param", "span")
|
||||||
|
|
||||||
//支持的标签属性
|
// 支持的标签属性
|
||||||
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
|
.addAttributes("a", "href", "class", "style", "target", "rel", "nofollow")
|
||||||
.addAttributes("blockquote", "cite")
|
.addAttributes("blockquote", "cite")
|
||||||
.addAttributes("code", "class", "style")
|
.addAttributes("code", "class", "style")
|
||||||
@@ -44,13 +46,16 @@ public class XssUtils extends Safelist {
|
|||||||
.addAttributes("ul", "type", "style")
|
.addAttributes("ul", "type", "style")
|
||||||
.addAttributes("pre", "class", "style")
|
.addAttributes("pre", "class", "style")
|
||||||
.addAttributes("div", "class", "id", "style")
|
.addAttributes("div", "class", "id", "style")
|
||||||
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen", "allowfullscreen",
|
.addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
|
||||||
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess", "allownetworking", "type")
|
"allowfullscreen",
|
||||||
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid", "codebase")
|
"quality", "width", "height", "align", "allowScriptAccess", "allowscriptaccess",
|
||||||
|
"allownetworking", "type")
|
||||||
|
.addAttributes("object", "type", "id", "name", "data", "width", "height", "style", "classid",
|
||||||
|
"codebase")
|
||||||
.addAttributes("param", "name", "value")
|
.addAttributes("param", "name", "value")
|
||||||
.addAttributes("span", "class", "style")
|
.addAttributes("span", "class", "style")
|
||||||
|
|
||||||
//标签属性对应的协议
|
// 标签属性对应的协议
|
||||||
.addProtocols("a", "href", "ftp", "http", "https", "mailto")
|
.addProtocols("a", "href", "ftp", "http", "https", "mailto")
|
||||||
.addProtocols("img", "src", "http", "https")
|
.addProtocols("img", "src", "http", "https")
|
||||||
.addProtocols("blockquote", "cite", "http", "https")
|
.addProtocols("blockquote", "cite", "http", "https")
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
|
||||||
|
import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
|
||||||
|
|
||||||
|
@Tag(name = "智能体聊天历史管理")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/agent/chat-history")
|
||||||
|
public class AgentChatHistoryController {
|
||||||
|
private final AgentChatHistoryBizService agentChatHistoryBizService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小智服务聊天上报请求
|
||||||
|
* <p>
|
||||||
|
* 小智服务聊天上报请求,包含Base64编码的音频数据和相关信息。
|
||||||
|
*
|
||||||
|
* @param request 包含上传文件及相关信息的请求对象
|
||||||
|
*/
|
||||||
|
@Operation(summary = "小智服务聊天上报请求")
|
||||||
|
@PostMapping("/report")
|
||||||
|
public Result<Boolean> uploadFile(@Valid @RequestBody AgentChatHistoryReportDTO request) {
|
||||||
|
Boolean result = agentChatHistoryBizService.report(request);
|
||||||
|
return new Result<Boolean>().ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameters;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
|
@Tag(name = "智能体管理")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/agent")
|
||||||
|
public class AgentController {
|
||||||
|
private final AgentService agentService;
|
||||||
|
private final AgentTemplateService agentTemplateService;
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@Operation(summary = "获取用户智能体列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<AgentDTO>> getUserAgents() {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
List<AgentDTO> agents = agentService.getUserAgents(user.getId());
|
||||||
|
return new Result<List<AgentDTO>>().ok(agents);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/all")
|
||||||
|
@Operation(summary = "智能体列表(管理员)")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||||
|
})
|
||||||
|
public Result<PageData<AgentEntity>> adminAgentList(
|
||||||
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
PageData<AgentEntity> page = agentService.adminAgentList(params);
|
||||||
|
return new Result<PageData<AgentEntity>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取智能体详情")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<AgentEntity> getAgentById(@PathVariable("id") String id) {
|
||||||
|
AgentEntity agent = agentService.getAgentById(id);
|
||||||
|
return new Result<AgentEntity>().ok(agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "创建智能体")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<String> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||||
|
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
||||||
|
|
||||||
|
// 获取默认模板
|
||||||
|
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
|
||||||
|
if (template != null) {
|
||||||
|
// 设置模板中的默认值
|
||||||
|
entity.setAsrModelId(template.getAsrModelId());
|
||||||
|
entity.setVadModelId(template.getVadModelId());
|
||||||
|
entity.setLlmModelId(template.getLlmModelId());
|
||||||
|
entity.setTtsModelId(template.getTtsModelId());
|
||||||
|
entity.setTtsVoiceId(template.getTtsVoiceId());
|
||||||
|
entity.setMemModelId(template.getMemModelId());
|
||||||
|
entity.setIntentModelId(template.getIntentModelId());
|
||||||
|
entity.setSystemPrompt(template.getSystemPrompt());
|
||||||
|
entity.setSummaryMemory(template.getSummaryMemory());
|
||||||
|
entity.setChatHistoryConf(template.getChatHistoryConf());
|
||||||
|
entity.setLangCode(template.getLangCode());
|
||||||
|
entity.setLanguage(template.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置用户ID和创建者信息
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
entity.setUserId(user.getId());
|
||||||
|
entity.setCreator(user.getId());
|
||||||
|
entity.setCreatedAt(new Date());
|
||||||
|
|
||||||
|
// ID、智能体编码和排序会在Service层自动生成
|
||||||
|
agentService.insert(entity);
|
||||||
|
|
||||||
|
return new Result<String>().ok(entity.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/saveMemory/{macAddress}")
|
||||||
|
@Operation(summary = "根据设备id更新智能体")
|
||||||
|
public Result<Void> updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) {
|
||||||
|
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||||
|
if (device == null) {
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
||||||
|
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
||||||
|
return updateAgentById(device.getAgentId(), agentUpdateDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@Operation(summary = "更新智能体")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||||
|
return updateAgentById(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<Void> updateAgentById(String id, AgentUpdateDTO dto) {
|
||||||
|
// 先查询现有实体
|
||||||
|
AgentEntity existingEntity = agentService.getAgentById(id);
|
||||||
|
if (existingEntity == null) {
|
||||||
|
return new Result<Void>().error("智能体不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只更新提供的非空字段
|
||||||
|
if (dto.getAgentName() != null) {
|
||||||
|
existingEntity.setAgentName(dto.getAgentName());
|
||||||
|
}
|
||||||
|
if (dto.getAgentCode() != null) {
|
||||||
|
existingEntity.setAgentCode(dto.getAgentCode());
|
||||||
|
}
|
||||||
|
if (dto.getAsrModelId() != null) {
|
||||||
|
existingEntity.setAsrModelId(dto.getAsrModelId());
|
||||||
|
}
|
||||||
|
if (dto.getVadModelId() != null) {
|
||||||
|
existingEntity.setVadModelId(dto.getVadModelId());
|
||||||
|
}
|
||||||
|
if (dto.getLlmModelId() != null) {
|
||||||
|
existingEntity.setLlmModelId(dto.getLlmModelId());
|
||||||
|
}
|
||||||
|
if (dto.getTtsModelId() != null) {
|
||||||
|
existingEntity.setTtsModelId(dto.getTtsModelId());
|
||||||
|
}
|
||||||
|
if (dto.getTtsVoiceId() != null) {
|
||||||
|
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
||||||
|
}
|
||||||
|
if (dto.getMemModelId() != null) {
|
||||||
|
existingEntity.setMemModelId(dto.getMemModelId());
|
||||||
|
}
|
||||||
|
if (dto.getIntentModelId() != null) {
|
||||||
|
existingEntity.setIntentModelId(dto.getIntentModelId());
|
||||||
|
}
|
||||||
|
if (dto.getSystemPrompt() != null) {
|
||||||
|
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
||||||
|
}
|
||||||
|
if (dto.getSummaryMemory() != null) {
|
||||||
|
existingEntity.setSummaryMemory(dto.getSummaryMemory());
|
||||||
|
}
|
||||||
|
if (dto.getChatHistoryConf() != null) {
|
||||||
|
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
|
||||||
|
}
|
||||||
|
if (dto.getLangCode() != null) {
|
||||||
|
existingEntity.setLangCode(dto.getLangCode());
|
||||||
|
}
|
||||||
|
if (dto.getLanguage() != null) {
|
||||||
|
existingEntity.setLanguage(dto.getLanguage());
|
||||||
|
}
|
||||||
|
if (dto.getSort() != null) {
|
||||||
|
existingEntity.setSort(dto.getSort());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置更新者信息
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
existingEntity.setUpdater(user.getId());
|
||||||
|
existingEntity.setUpdatedAt(new Date());
|
||||||
|
|
||||||
|
// 更新记忆策略
|
||||||
|
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
// 删除所有记录
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
||||||
|
existingEntity.setSummaryMemory("");
|
||||||
|
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
||||||
|
// 删除音频数据
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
||||||
|
}
|
||||||
|
agentService.updateById(existingEntity);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除智能体")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<Void> delete(@PathVariable String id) {
|
||||||
|
// 先删除关联的设备
|
||||||
|
deviceService.deleteByAgentId(id);
|
||||||
|
// 删除关联的聊天记录
|
||||||
|
agentChatHistoryService.deleteByAgentId(id, true, true);
|
||||||
|
// 再删除智能体
|
||||||
|
agentService.deleteById(id);
|
||||||
|
return new Result<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/template")
|
||||||
|
@Operation(summary = "智能体模板模板列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<AgentTemplateEntity>> templateList() {
|
||||||
|
List<AgentTemplateEntity> list = agentTemplateService
|
||||||
|
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
|
||||||
|
return new Result<List<AgentTemplateEntity>>().ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/sessions")
|
||||||
|
@Operation(summary = "获取智能体会话列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
@Parameters({
|
||||||
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||||
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||||
|
})
|
||||||
|
public Result<PageData<AgentChatSessionDTO>> getAgentSessions(
|
||||||
|
@PathVariable("id") String id,
|
||||||
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
|
params.put("agentId", id);
|
||||||
|
PageData<AgentChatSessionDTO> page = agentChatHistoryService.getSessionListByAgentId(params);
|
||||||
|
return new Result<PageData<AgentChatSessionDTO>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/chat-history/{sessionId}")
|
||||||
|
@Operation(summary = "获取智能体聊天记录")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<List<AgentChatHistoryDTO>> getAgentChatHistory(
|
||||||
|
@PathVariable("id") String id,
|
||||||
|
@PathVariable("sessionId") String sessionId) {
|
||||||
|
// 获取当前用户
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
if (!agentService.checkAgentPermission(id, user.getId())) {
|
||||||
|
return new Result<List<AgentChatHistoryDTO>>().error("没有权限查看该智能体的聊天记录");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询聊天记录
|
||||||
|
List<AgentChatHistoryDTO> result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId);
|
||||||
|
return new Result<List<AgentChatHistoryDTO>>().ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/audio/{audioId}")
|
||||||
|
@Operation(summary = "获取音频下载ID")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<String> getAudioId(@PathVariable("audioId") String audioId) {
|
||||||
|
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||||
|
if (audioData == null) {
|
||||||
|
return new Result<String>().error("音频不存在");
|
||||||
|
}
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId);
|
||||||
|
return new Result<String>().ok(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/play/{uuid}")
|
||||||
|
@Operation(summary = "播放音频")
|
||||||
|
public ResponseEntity<byte[]> playAudio(@PathVariable("uuid") String uuid) {
|
||||||
|
|
||||||
|
String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid));
|
||||||
|
if (StringUtils.isBlank(audioId)) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] audioData = agentChatAudioService.getAudio(audioId);
|
||||||
|
if (audioData == null) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid));
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"")
|
||||||
|
.body(audioData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import xiaozhi.common.dao.BaseDao;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface AgentDao extends BaseDao<AgentEntity> {
|
||||||
|
/**
|
||||||
|
* 获取智能体的设备数量
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @return 设备数量
|
||||||
|
*/
|
||||||
|
Integer getDeviceCountByAgentId(@Param("agentId") String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备MAC地址查询对应设备的默认智能体信息
|
||||||
|
*
|
||||||
|
* @param macAddress 设备MAC地址
|
||||||
|
* @return 默认智能体信息
|
||||||
|
*/
|
||||||
|
@Select(" SELECT a.* FROM ai_device d " +
|
||||||
|
" LEFT JOIN ai_agent a ON d.agent_id = a.id " +
|
||||||
|
" WHERE d.mac_address = #{macAddress} " +
|
||||||
|
" ORDER BY d.id DESC LIMIT 1")
|
||||||
|
AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AgentTemplateDao extends BaseMapper<AgentTemplateEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.entity.AgentChatAudioEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AgentChatAudioEntity} 智能体聊天音频数据Dao对象
|
||||||
|
*
|
||||||
|
* @author Goody
|
||||||
|
* @version 1.0, 2025/5/8
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AiAgentChatAudioDao extends BaseMapper<AgentChatAudioEntity> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AgentChatHistoryEntity} 智能体聊天历史记录Dao对象
|
||||||
|
*
|
||||||
|
* @author Goody
|
||||||
|
* @version 1.0, 2025/4/30
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity> {
|
||||||
|
/**
|
||||||
|
* 根据智能体ID删除音频
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteAudioByAgentId(String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体ID删除聊天历史记录
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteHistoryByAgentId(String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体ID删除音频ID
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteAudioIdByAgentId(String agentId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体聊天记录DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体聊天记录")
|
||||||
|
public class AgentChatHistoryDTO {
|
||||||
|
@Schema(description = "创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "消息类型: 1-用户, 2-智能体")
|
||||||
|
private Byte chatType;
|
||||||
|
|
||||||
|
@Schema(description = "聊天内容")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "音频ID")
|
||||||
|
private String audioId;
|
||||||
|
|
||||||
|
@Schema(description = "MAC地址")
|
||||||
|
private String macAddress;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小智设备聊天上报请求
|
||||||
|
*
|
||||||
|
* @author Haotian
|
||||||
|
* @version 1.0, 2025/5/8
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "小智设备聊天上报请求")
|
||||||
|
public class AgentChatHistoryReportDTO {
|
||||||
|
@Schema(description = "MAC地址", example = "00:11:22:33:44:55")
|
||||||
|
@NotBlank
|
||||||
|
private String macAddress;
|
||||||
|
@Schema(description = "会话ID", example = "79578c31-f1fb-426a-900e-1e934215f05a")
|
||||||
|
@NotBlank
|
||||||
|
private String sessionId;
|
||||||
|
@Schema(description = "消息类型: 1-用户, 2-智能体", example = "1")
|
||||||
|
@NotNull
|
||||||
|
private Byte chatType;
|
||||||
|
@Schema(description = "聊天内容", example = "你好呀")
|
||||||
|
@NotBlank
|
||||||
|
private String content;
|
||||||
|
@Schema(description = "base64编码的opus音频数据", example = "")
|
||||||
|
private String audioBase64;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体会话列表DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AgentChatSessionDTO {
|
||||||
|
/**
|
||||||
|
* 会话ID
|
||||||
|
*/
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天条数
|
||||||
|
*/
|
||||||
|
private Integer chatCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体创建DTO
|
||||||
|
* 专用于新增智能体,不包含id、agentCode和sort字段,这些字段由系统自动生成/设置默认值
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "智能体创建对象")
|
||||||
|
public class AgentCreateDTO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Schema(description = "智能体名称", example = "客服助手")
|
||||||
|
@NotBlank(message = "智能体名称不能为空")
|
||||||
|
private String agentName;
|
||||||
|
}
|
||||||