added pose correction
This commit is contained in:
parent
14705cb1d0
commit
01c8850a39
BIN
Icons/hairstyle.png
Normal file
BIN
Icons/hairstyle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
0
Icons/pose_body/body_front.ai
Normal file
0
Icons/pose_body/body_front.ai
Normal file
BIN
Icons/pose_body/body_front.png
Normal file
BIN
Icons/pose_body/body_front.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 388 KiB |
BIN
Icons/pose_body/body_front.psd
Normal file
BIN
Icons/pose_body/body_front.psd
Normal file
Binary file not shown.
72
Icons/pose_body/body_front.svg
Normal file
72
Icons/pose_body/body_front.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 522 KiB |
1
Icons/pose_body/koerper.ai
Normal file
1
Icons/pose_body/koerper.ai
Normal file
File diff suppressed because one or more lines are too long
1
Icons/pose_body/kopf.ai
Normal file
1
Icons/pose_body/kopf.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (kopf.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
251.0000 1385.0000 m
273.8161 1385.2928 300.7425 1372.3409 305.0000 1354.0000 c
306.8037 1346.2300 302.7450 1339.2870 306.0000 1335.0000 C
313.9992 1328.3340 322.0008 1321.6660 330.0000 1315.0000 C
354.6259 1280.1051 349.4749 1229.6209 324.0000 1201.0000 C
321.3336 1194.6673 318.6664 1188.3327 316.0000 1182.0000 C
311.0005 1176.3339 305.9995 1170.6661 301.0000 1165.0000 C
300.3334 1161.6670 299.6666 1158.3330 299.0000 1155.0000 C
261.9143 1115.4675 213.6948 1165.8901 207.0000 1201.0000 C
189.6939 1202.4396 192.8712 1218.8915 188.0000 1233.0000 c
178.8869 1259.3943 185.1491 1295.0442 200.0000 1315.0000 c
207.5650 1325.1654 225.7941 1328.9934 229.0000 1343.0000 C
218.3514 1368.6503 234.4272 1374.9075 251.0000 1385.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linke_hand.ai
Normal file
1
Icons/pose_body/linke_hand.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linke_hand.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
442.0000 690.0000 m
447.6661 689.6667 453.3339 689.3333 459.0000 689.0000 C
477.4999 677.2289 524.8600 621.0969 530.0000 598.0000 C
529.6667 597.6667 529.3333 597.3333 529.0000 597.0000 C
518.3576 598.0797 517.7607 602.9975 511.0000 607.0000 C
509.9733 601.5698 510.7002 602.6622 505.0000 602.0000 C
503.3335 603.3332 501.6665 604.6668 500.0000 606.0000 C
496.8390 624.6107 486.7534 629.4043 470.0000 634.0000 C
473.1414 621.2863 485.8802 617.7286 484.0000 603.0000 C
474.2854 600.7975 438.1487 641.2900 434.0000 651.0000 c
429.6165 661.2596 432.5661 675.8015 433.0000 687.0000 C
433.6666 687.0000 434.3334 687.0000 435.0000 687.0000 C
437.3331 687.9999 439.6669 689.0001 442.0000 690.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linke_huefte.ai
Normal file
1
Icons/pose_body/linke_huefte.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linke_huefte.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
360.0000 769.0000 m
365.3328 768.3334 370.6672 767.6666 376.0000 767.0000 C
389.3379 724.2350 395.0696 686.0585 394.0000 630.0000 C
393.3334 629.6667 392.6666 629.3333 392.0000 629.0000 C
365.5890 623.4822 335.2978 653.7018 331.0000 674.0000 c
328.6137 685.2705 333.0399 703.5525 334.0000 714.0000 c
335.5943 731.3489 330.9011 745.4335 341.0000 759.0000 c
345.3688 764.8689 353.1042 765.5366 360.0000 769.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linke_schulter.ai
Normal file
1
Icons/pose_body/linke_schulter.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linke_schulter.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
365.0000 1089.0000 m
414.9642 1090.2220 424.6863 1043.1348 416.0000 1000.0000 C
396.7569 997.0739 384.2493 1002.9651 371.0000 1007.0000 c
363.1889 1009.3788 357.2608 1006.7751 348.0000 1010.0000 c
331.1677 1015.8616 320.8307 1035.9984 316.0000 1054.0000 c
314.6460 1059.0456 311.7446 1071.7944 315.0000 1078.0000 c
320.8208 1089.0959 355.4646 1081.0881 365.0000 1089.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linker_elbogen.ai
Normal file
1
Icons/pose_body/linker_elbogen.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linker_elbogen.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
385.0000 895.0000 m
396.3322 894.0001 407.6678 892.9999 419.0000 892.0000 C
420.8288 869.2737 430.9571 857.2419 432.0000 837.0000 C
431.3334 836.6667 430.6666 836.3333 430.0000 836.0000 C
413.0667 831.5774 395.9857 830.8379 377.0000 834.0000 C
373.0961 849.4181 366.2085 875.9756 371.0000 892.0000 C
375.6662 892.9999 380.3338 894.0001 385.0000 895.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linker_fuss.ai
Normal file
1
Icons/pose_body/linker_fuss.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linker_fuss.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
277.0000 103.0000 m
291.1478 103.6525 309.8996 102.6367 315.0000 94.0000 C
315.3333 93.6667 315.6667 93.3333 316.0000 93.0000 C
315.6667 82.0011 315.3333 70.9989 315.0000 60.0000 C
317.9908 44.5742 334.5629 25.1506 331.0000 8.0000 C
330.6667 7.6667 330.3333 7.3333 330.0000 7.0000 C
328.3335 4.6669 326.6665 2.3331 325.0000 0.0000 C
313.8115 -0.3247 283.5760 -3.9736 277.0000 2.0000 C
265.8761 10.9713 267.9965 90.2803 277.0000 103.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linkes_auge.ai
Normal file
1
Icons/pose_body/linkes_auge.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linkes_auge.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
277.0000 1226.0000 m
277.0069 1235.1036 279.3300 1237.1623 283.0000 1242.0000 C
296.7917 1245.9101 303.0520 1247.9246 317.0000 1242.0000 C
316.9406 1235.8168 317.0579 1236.4457 318.0000 1231.0000 C
304.4480 1222.8409 293.8836 1225.3946 277.0000 1226.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linkes_knie.ai
Normal file
1
Icons/pose_body/linkes_knie.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linkes_knie.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
288.0000 412.0000 m
308.2769 412.4618 333.1308 414.0636 345.0000 406.0000 C
345.2706 386.2579 350.1005 351.4985 343.0000 336.0000 C
319.2309 335.8991 294.0563 328.0272 275.0000 333.0000 C
274.9537 358.2237 255.9923 384.2344 268.0000 410.0000 C
274.7488 409.6650 284.2934 409.1470 288.0000 412.0000 C
n
*U
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
273.0000 343.0000 m
273.3333 342.3334 273.6667 341.6666 274.0000 341.0000 C
273.6667 341.6666 273.3333 342.3334 273.0000 343.0000 C
n
*U
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
273.0000 340.0000 m
273.3333 339.3334 273.6667 338.6666 274.0000 338.0000 C
273.6667 338.6666 273.3333 339.3334 273.0000 340.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/linkes_ohr.ai
Normal file
1
Icons/pose_body/linkes_ohr.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (linkes_ohr.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
333.0000 1245.0000 m
339.5525 1242.9758 340.2438 1238.5036 343.0000 1233.0000 C
338.7459 1220.8678 335.5297 1211.3657 328.0000 1203.0000 C
323.4739 1203.8592 325.0245 1202.8840 323.0000 1206.0000 C
317.5339 1219.4039 329.6389 1235.0141 333.0000 1245.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/nase.ai
Normal file
1
Icons/pose_body/nase.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (nase.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
256.0000 1244.0000 m
265.8410 1243.9900 273.8552 1240.8700 277.0000 1234.0000 C
280.8103 1224.9493 281.8250 1206.4314 280.0000 1197.0000 C
272.4634 1193.3288 259.8051 1193.6213 251.0000 1196.0000 C
250.6667 1196.9999 250.3333 1198.0001 250.0000 1199.0000 C
251.3332 1213.6652 252.6668 1228.3348 254.0000 1243.0000 C
254.6666 1243.3333 255.3334 1243.6667 256.0000 1244.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechte_hand.ai
Normal file
1
Icons/pose_body/rechte_hand.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechte_hand.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
0.0000 597.0000 m
1.5180 616.6622 59.9494 684.6982 75.0000 694.0000 C
82.7011 697.4112 92.2200 692.9641 98.0000 691.0000 C
98.3333 690.3334 98.6667 689.6666 99.0000 689.0000 C
99.7940 645.7666 86.9573 641.7971 68.0000 621.0000 c
61.2553 613.6008 59.5154 607.0434 50.0000 602.0000 C
48.0002 602.9999 45.9998 604.0001 44.0000 605.0000 C
44.0000 605.3333 44.0000 605.6667 44.0000 606.0000 C
46.6189 618.8246 56.2958 624.0979 61.0000 635.0000 C
52.0009 630.3338 42.9991 625.6662 34.0000 621.0000 C
28.9112 614.2005 30.2923 606.7661 23.0000 602.0000 C
15.5871 604.6025 15.4987 605.8216 10.0000 598.0000 C
9.3334 597.6667 8.6666 597.3333 8.0000 597.0000 C
5.3336 597.0000 2.6664 597.0000 0.0000 597.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechte_huefte.ai
Normal file
1
Icons/pose_body/rechte_huefte.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechte_huefte.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
153.0000 764.0000 m
187.9691 764.8388 193.1110 742.5401 199.0000 715.0000 c
199.6438 711.9891 197.0000 705.0000 y
202.6496 697.3431 201.2605 691.1681 201.0000 681.0000 C
201.0000 673.0008 201.0000 664.9992 201.0000 657.0000 C
192.9741 634.7459 160.9609 618.7969 137.0000 624.0000 C
129.4179 677.5682 142.9846 724.8006 153.0000 764.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechte_schulter.ai
Normal file
1
Icons/pose_body/rechte_schulter.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechte_schulter.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
152.0000 1089.0000 m
167.5907 1089.1707 179.9606 1085.9658 195.0000 1083.0000 C
230.9463 1036.8340 167.6336 993.0098 121.0000 1008.0000 C
117.9419 1007.2572 116.4883 1007.7316 114.0000 1010.0000 C
104.3136 1058.0244 121.0049 1075.9437 152.0000 1089.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechter_elbogen.ai
Normal file
1
Icons/pose_body/rechter_elbogen.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechter_elbogen.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
114.0000 895.0000 m
133.9809 895.2648 145.6805 893.5518 159.0000 890.0000 C
165.3996 871.1239 154.5425 841.5726 151.0000 824.0000 C
149.3335 824.0000 147.6665 824.0000 146.0000 824.0000 C
134.4574 822.0942 106.2651 838.4984 100.0000 847.0000 C
101.0041 853.6247 110.3012 890.2737 114.0000 895.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechter_fuss.ai
Normal file
1
Icons/pose_body/rechter_fuss.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechter_fuss.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
218.0000 100.0000 m
229.6655 99.6667 241.3345 99.3333 253.0000 99.0000 C
265.7182 89.6342 260.6520 11.5479 252.0000 0.0000 C
231.8904 -0.4393 211.3800 0.0593 199.0000 8.0000 C
198.6667 8.6666 198.3333 9.3334 198.0000 10.0000 C
198.3239 31.9535 213.9041 44.1760 216.0000 64.0000 c
217.3001 76.2964 209.3088 89.4819 218.0000 100.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechtes_auge.ai
Normal file
1
Icons/pose_body/rechtes_auge.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechtes_auge.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
228.0000 1246.0000 m
239.6454 1246.2905 243.5510 1243.2936 250.0000 1239.0000 C
250.9999 1235.3337 252.0001 1231.6663 253.0000 1228.0000 C
252.3334 1227.3334 251.6666 1226.6666 251.0000 1226.0000 C
229.4227 1227.8369 227.7179 1225.0791 213.0000 1233.0000 C
213.0000 1233.9999 213.0000 1235.0001 213.0000 1236.0000 C
218.1103 1241.0864 220.8971 1242.2729 228.0000 1246.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechtes_knie.ai
Normal file
1
Icons/pose_body/rechtes_knie.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechtes_knie.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
184.0000 414.0000 m
206.0213 414.2704 245.4585 420.0122 263.0000 412.0000 C
264.2329 393.9503 259.5077 339.0758 253.0000 326.0000 C
229.6501 325.5124 197.6062 327.6801 184.0000 338.0000 C
183.8097 363.6520 178.2162 399.2486 184.0000 414.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
1
Icons/pose_body/rechtes_ohr.ai
Normal file
1
Icons/pose_body/rechtes_ohr.ai
Normal file
@ -0,0 +1 @@
|
||||
%!PS-Adobe-2.0
%%Creator: Adobe Photoshop(TM) Pen Path Export 7.0
%%Title: (rechtes_ohr.ai)
%%DocumentNeededResources: procset Adobe_packedarray 2.0 0
%%+ procset Adobe_IllustratorA_AI3 1.0 1
%%ColorUsage: Black&White
%%BoundingBox: 0 0 537 1386
%%HiResBoundingBox: 0 0 537 1386
%AI3_Cropmarks: 0 0 537 1386
%%DocumentPreview: None
%%EndComments
%%BeginProlog
%%IncludeResource: procset Adobe_packedarray 2.0 0
Adobe_packedarray /initialize get exec
%%IncludeResource: procset Adobe_IllustratorA_AI3 1.0 1
%%EndProlog
%%BeginSetup
Adobe_IllustratorA_AI3 /initialize get exec
n
%%EndSetup
0.0 0.0 0.0 1.0 k
0 i 0 J 0 j 1 w 4 M []0 d
%%Note:
%Adobe_Photoshop_Path_Begin:<< /defaultFill false >>
*u
%AI3_Note:<< /operation /xor /defaultFill false >>
1 XR
193.0000 1243.0000 m
193.6666 1243.0000 194.3334 1243.0000 195.0000 1243.0000 C
204.1588 1234.2730 208.1833 1220.0521 208.0000 1202.0000 C
205.6669 1201.6667 203.3331 1201.3333 201.0000 1201.0000 C
191.9844 1209.6934 186.5244 1225.7040 190.0000 1241.0000 C
190.9999 1241.6666 192.0001 1242.3334 193.0000 1243.0000 C
n
*U
%Adobe_Photoshop_Path_End
%%Trailer
%%EOF
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 506 KiB |
Binary file not shown.
@ -4,6 +4,7 @@ import json
|
||||
import math
|
||||
import os
|
||||
import secrets
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
@ -164,6 +165,33 @@ def resolve_pose_model_path() -> Optional[Path]:
|
||||
return None
|
||||
|
||||
|
||||
def base_pose_model_candidates() -> list[Path]:
|
||||
return [
|
||||
BASE_DIR / "yolo26n-pose.pt",
|
||||
Path.cwd() / "yolo26n-pose.pt",
|
||||
TRAINING_ROOT / "yolo26n-pose.pt",
|
||||
TRAINING_ROOT.parent / "yolo26n-pose.pt",
|
||||
TRAINING_ROOT.parent.parent / "yolo26n-pose.pt",
|
||||
Path(tempfile.gettempdir()) / "nsfwapp-ml" / "yolo26n-pose.pt",
|
||||
]
|
||||
|
||||
|
||||
def resolve_base_pose_model_path() -> Optional[Path]:
|
||||
env_path = os.environ.get("YOLO_BASE_POSE_MODEL", "").strip()
|
||||
if env_path:
|
||||
p = Path(env_path).expanduser().resolve()
|
||||
if existing_file(p):
|
||||
return p
|
||||
raise RuntimeError(f"YOLO_BASE_POSE_MODEL not found: {p}")
|
||||
|
||||
for candidate in base_pose_model_candidates():
|
||||
p = existing_file(candidate)
|
||||
if p:
|
||||
return p.resolve()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def resolve_videomae_model_path() -> Optional[Path]:
|
||||
env_path = os.environ.get("VIDEOMAE_MODEL", "").strip()
|
||||
if env_path:
|
||||
@ -203,6 +231,8 @@ _MODEL_PATH = ""
|
||||
_MODEL_ERROR = ""
|
||||
_POSE_MODEL_PATH = ""
|
||||
_POSE_MODEL_ERROR = ""
|
||||
_BASE_POSE_MODEL_PATH = ""
|
||||
_BASE_POSE_MODEL_ERROR = ""
|
||||
_VIDEOMAE_MODEL_PATH = ""
|
||||
_VIDEOMAE_MODEL_ERROR = ""
|
||||
_VIDEOMAE_DEVICE_ACTIVE = ""
|
||||
@ -211,6 +241,7 @@ _LABEL_ERROR = ""
|
||||
_DEVICE = os.environ.get("YOLO_DEVICE", "")
|
||||
_CONF = float(os.environ.get("YOLO_CONF", "0.25"))
|
||||
_POSE_CONF = float(os.environ.get("YOLO_POSE_CONF", "0.30"))
|
||||
_BASE_POSE_CONF = float(os.environ.get("YOLO_BASE_POSE_CONF", "0.10"))
|
||||
_POSE_RELIABLE_MIN_SCORE = float(os.environ.get("YOLO_POSE_RELIABLE_MIN_SCORE", "0.30"))
|
||||
_POSE_RELIABLE_MIN_KEYPOINTS = int(os.environ.get("YOLO_POSE_RELIABLE_MIN_KEYPOINTS", "6"))
|
||||
_POSE_RELIABLE_MIN_QUALITY = float(os.environ.get("YOLO_POSE_RELIABLE_MIN_QUALITY", "0.45"))
|
||||
@ -226,6 +257,7 @@ _VIDEOMAE_NUM_FRAMES = int(os.environ.get("VIDEOMAE_NUM_FRAMES", "16"))
|
||||
|
||||
model = None
|
||||
pose_model = None
|
||||
base_pose_model = None
|
||||
videomae_model = None
|
||||
videomae_processor = None
|
||||
|
||||
@ -434,6 +466,37 @@ def get_pose_model():
|
||||
return None
|
||||
|
||||
|
||||
def get_base_pose_model():
|
||||
global base_pose_model
|
||||
global _BASE_POSE_MODEL_PATH
|
||||
global _BASE_POSE_MODEL_ERROR
|
||||
|
||||
if base_pose_model is not None:
|
||||
return base_pose_model
|
||||
|
||||
try:
|
||||
path = resolve_base_pose_model_path()
|
||||
if path is None:
|
||||
base_pose_model = None
|
||||
_BASE_POSE_MODEL_PATH = ""
|
||||
_BASE_POSE_MODEL_ERROR = ""
|
||||
return None
|
||||
|
||||
loaded = YOLO(str(path))
|
||||
|
||||
base_pose_model = loaded
|
||||
_BASE_POSE_MODEL_PATH = str(path)
|
||||
_BASE_POSE_MODEL_ERROR = ""
|
||||
|
||||
return base_pose_model
|
||||
|
||||
except Exception as exc:
|
||||
base_pose_model = None
|
||||
_BASE_POSE_MODEL_PATH = ""
|
||||
_BASE_POSE_MODEL_ERROR = str(exc)
|
||||
return None
|
||||
|
||||
|
||||
def get_videomae_components():
|
||||
global videomae_model
|
||||
global videomae_processor
|
||||
@ -1453,12 +1516,20 @@ def add_pose_scene_context_scores(scores: dict[str, float], prediction: dict, pe
|
||||
add("prone_bone", 0.07)
|
||||
|
||||
|
||||
def best_pose_scene_position(prediction: dict, persons: list[dict]) -> tuple[str, float, bool, bool]:
|
||||
def best_hybrid_pose_scene_position(
|
||||
prediction: dict,
|
||||
position_persons: list[dict],
|
||||
context_persons: list[dict],
|
||||
) -> tuple[str, float, bool, bool]:
|
||||
pose_scores: dict[str, float] = {}
|
||||
context_scores: dict[str, float] = {}
|
||||
reliable_persons = reliable_pose_persons(persons)
|
||||
reliable_position_persons = reliable_pose_persons(position_persons)
|
||||
reliable_context_persons = reliable_pose_persons(context_persons)
|
||||
|
||||
for person in reliable_persons:
|
||||
if not reliable_context_persons:
|
||||
reliable_context_persons = reliable_position_persons
|
||||
|
||||
for person in reliable_position_persons:
|
||||
label = normalize_sex_position_label(person.get("label"))
|
||||
score = float(person.get("score") or 0.0)
|
||||
|
||||
@ -1471,7 +1542,7 @@ def best_pose_scene_position(prediction: dict, persons: list[dict]) -> tuple[str
|
||||
if quality > 0:
|
||||
combine_position_score(pose_scores, label, 0.04 * quality)
|
||||
|
||||
add_pose_scene_context_scores(context_scores, prediction, reliable_persons)
|
||||
add_pose_scene_context_scores(context_scores, prediction, reliable_context_persons)
|
||||
|
||||
best_position, best_score, has_pose_signal, has_context_signal = fuse_hybrid_position_scores(
|
||||
pose_scores,
|
||||
@ -1484,6 +1555,10 @@ def best_pose_scene_position(prediction: dict, persons: list[dict]) -> tuple[str
|
||||
return "", 0.0, False, has_context_signal
|
||||
|
||||
|
||||
def best_pose_scene_position(prediction: dict, persons: list[dict]) -> tuple[str, float, bool, bool]:
|
||||
return best_hybrid_pose_scene_position(prediction, persons, persons)
|
||||
|
||||
|
||||
def apply_pose_result_to_prediction(prediction: dict, result) -> dict:
|
||||
persons = pose_persons_from_result(result)
|
||||
if persons:
|
||||
@ -1505,55 +1580,92 @@ def apply_pose_result_to_prediction(prediction: dict, result) -> dict:
|
||||
return prediction
|
||||
|
||||
|
||||
def predict_pose_results(model, paths: list[str], imgsz: int, conf: float):
|
||||
return model.predict(
|
||||
source=paths,
|
||||
imgsz=imgsz,
|
||||
conf=conf,
|
||||
batch=_BATCH,
|
||||
device=_DEVICE or None,
|
||||
half=_HALF,
|
||||
verbose=False,
|
||||
)
|
||||
|
||||
|
||||
def has_reliable_pose_persons(persons: list[dict]) -> bool:
|
||||
return bool(reliable_pose_persons(persons))
|
||||
|
||||
|
||||
def apply_hybrid_pose_persons_to_prediction(
|
||||
prediction: dict,
|
||||
position_persons: list[dict],
|
||||
context_persons: list[dict],
|
||||
) -> None:
|
||||
display_persons = context_persons or position_persons
|
||||
if display_persons:
|
||||
prediction["persons"] = display_persons
|
||||
|
||||
best_position, best_score_value, has_pose_signal, has_context_signal = best_hybrid_pose_scene_position(
|
||||
prediction,
|
||||
position_persons,
|
||||
context_persons,
|
||||
)
|
||||
|
||||
if best_position:
|
||||
prediction["sexPosition"] = best_position
|
||||
prediction["sexPositionScore"] = best_score_value
|
||||
|
||||
if position_persons or context_persons:
|
||||
append_prediction_source(prediction, "yolo_pose")
|
||||
if context_persons:
|
||||
append_prediction_source(prediction, "base_pose")
|
||||
if has_pose_signal:
|
||||
append_prediction_source(prediction, "pose_position")
|
||||
if has_context_signal:
|
||||
append_prediction_source(prediction, "box_context")
|
||||
|
||||
|
||||
def apply_pose_batch_to_predictions(paths: list[str], predictions: list[dict], imgsz: int) -> None:
|
||||
global _POSE_MODEL_ERROR
|
||||
global _BASE_POSE_MODEL_ERROR
|
||||
|
||||
position_persons_by_index: dict[int, list[dict]] = {}
|
||||
context_persons_by_index: dict[int, list[dict]] = {}
|
||||
|
||||
current_pose_model = get_pose_model()
|
||||
if current_pose_model is None:
|
||||
for prediction in predictions:
|
||||
best_position, best_score_value, _has_pose_signal, has_context_signal = best_pose_scene_position(
|
||||
prediction,
|
||||
[],
|
||||
)
|
||||
if current_pose_model is not None:
|
||||
try:
|
||||
pose_results = predict_pose_results(current_pose_model, paths, imgsz, _POSE_CONF)
|
||||
for index, pose_result in enumerate(pose_results):
|
||||
position_persons_by_index[index] = pose_persons_from_result(pose_result)
|
||||
except Exception as exc:
|
||||
_POSE_MODEL_ERROR = str(exc)
|
||||
|
||||
if best_position:
|
||||
prediction["sexPosition"] = best_position
|
||||
prediction["sexPositionScore"] = best_score_value
|
||||
needs_base_pose = [
|
||||
index
|
||||
for index in range(len(paths))
|
||||
if not has_reliable_pose_persons(position_persons_by_index.get(index, []))
|
||||
]
|
||||
|
||||
if has_context_signal:
|
||||
append_prediction_source(prediction, "box_context")
|
||||
current_base_pose_model = get_base_pose_model()
|
||||
if current_base_pose_model is not None and needs_base_pose:
|
||||
try:
|
||||
base_paths = [paths[index] for index in needs_base_pose]
|
||||
base_results = predict_pose_results(current_base_pose_model, base_paths, imgsz, _BASE_POSE_CONF)
|
||||
for original_index, base_result in zip(needs_base_pose, base_results):
|
||||
context_persons_by_index[original_index] = pose_persons_from_result(base_result)
|
||||
except Exception as exc:
|
||||
_BASE_POSE_MODEL_ERROR = str(exc)
|
||||
|
||||
return
|
||||
for index, prediction in enumerate(predictions):
|
||||
position_persons = position_persons_by_index.get(index, [])
|
||||
context_persons = context_persons_by_index.get(index, [])
|
||||
|
||||
try:
|
||||
pose_results = current_pose_model.predict(
|
||||
source=paths,
|
||||
imgsz=imgsz,
|
||||
conf=_POSE_CONF,
|
||||
batch=_BATCH,
|
||||
device=_DEVICE or None,
|
||||
half=_HALF,
|
||||
verbose=False,
|
||||
apply_hybrid_pose_persons_to_prediction(
|
||||
prediction,
|
||||
position_persons,
|
||||
context_persons,
|
||||
)
|
||||
except Exception as exc:
|
||||
_POSE_MODEL_ERROR = str(exc)
|
||||
for prediction in predictions:
|
||||
best_position, best_score_value, _has_pose_signal, has_context_signal = best_pose_scene_position(
|
||||
prediction,
|
||||
[],
|
||||
)
|
||||
|
||||
if best_position:
|
||||
prediction["sexPosition"] = best_position
|
||||
prediction["sexPositionScore"] = best_score_value
|
||||
|
||||
if has_context_signal:
|
||||
append_prediction_source(prediction, "box_context")
|
||||
|
||||
return
|
||||
|
||||
for prediction, pose_result in zip(predictions, pose_results):
|
||||
apply_pose_result_to_prediction(prediction, pose_result)
|
||||
|
||||
|
||||
def pose_model_status() -> dict:
|
||||
@ -1576,6 +1688,26 @@ def pose_model_status() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def base_pose_model_status() -> dict:
|
||||
try:
|
||||
expected = resolve_base_pose_model_path()
|
||||
expected_text = str(expected) if expected else "yolo26n-pose.pt"
|
||||
exists = expected is not None
|
||||
error = _BASE_POSE_MODEL_ERROR
|
||||
except Exception as exc:
|
||||
expected_text = "yolo26n-pose.pt"
|
||||
exists = False
|
||||
error = str(exc)
|
||||
|
||||
return {
|
||||
"basePoseModelAvailable": exists,
|
||||
"basePoseModelLoaded": base_pose_model is not None,
|
||||
"basePoseModel": _BASE_POSE_MODEL_PATH or (expected_text if exists else ""),
|
||||
"basePoseModelError": error,
|
||||
"expectedBasePoseModel": expected_text,
|
||||
}
|
||||
|
||||
|
||||
def videomae_model_status() -> dict:
|
||||
try:
|
||||
expected = resolve_videomae_model_path()
|
||||
@ -1744,6 +1876,7 @@ def health():
|
||||
|
||||
status_payload.update(detector_model_status(load=False))
|
||||
status_payload.update(pose_model_status())
|
||||
status_payload.update(base_pose_model_status())
|
||||
status_payload.update(videomae_model_status())
|
||||
return status_payload
|
||||
|
||||
@ -1751,12 +1884,15 @@ def health():
|
||||
def reload_model():
|
||||
global model
|
||||
global pose_model
|
||||
global base_pose_model
|
||||
global videomae_model
|
||||
global videomae_processor
|
||||
global _MODEL_PATH
|
||||
global _MODEL_ERROR
|
||||
global _POSE_MODEL_PATH
|
||||
global _POSE_MODEL_ERROR
|
||||
global _BASE_POSE_MODEL_PATH
|
||||
global _BASE_POSE_MODEL_ERROR
|
||||
global _VIDEOMAE_MODEL_PATH
|
||||
global _VIDEOMAE_MODEL_ERROR
|
||||
global _VIDEOMAE_DEVICE_ACTIVE
|
||||
@ -1764,12 +1900,15 @@ def reload_model():
|
||||
|
||||
model = None
|
||||
pose_model = None
|
||||
base_pose_model = None
|
||||
videomae_model = None
|
||||
videomae_processor = None
|
||||
_MODEL_PATH = ""
|
||||
_MODEL_ERROR = ""
|
||||
_POSE_MODEL_PATH = ""
|
||||
_POSE_MODEL_ERROR = ""
|
||||
_BASE_POSE_MODEL_PATH = ""
|
||||
_BASE_POSE_MODEL_ERROR = ""
|
||||
_VIDEOMAE_MODEL_PATH = ""
|
||||
_VIDEOMAE_MODEL_ERROR = ""
|
||||
_VIDEOMAE_DEVICE_ACTIVE = ""
|
||||
@ -1785,5 +1924,6 @@ def reload_model():
|
||||
|
||||
status_payload.update(detector_model_status(load=True))
|
||||
status_payload.update(pose_model_status())
|
||||
status_payload.update(base_pose_model_status())
|
||||
status_payload.update(videomae_model_status())
|
||||
return status_payload
|
||||
|
||||
3
backend/data/pending-autostart/admin.json
Normal file
3
backend/data/pending-autostart/admin.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"items": []
|
||||
}
|
||||
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp_amd64.deb
vendored
BIN
backend/dist/nsfwapp_amd64.deb
vendored
Binary file not shown.
BIN
backend/ml/__pycache__/predict_pose_model.cpython-314.pyc
Normal file
BIN
backend/ml/__pycache__/predict_pose_model.cpython-314.pyc
Normal file
Binary file not shown.
@ -22,6 +22,7 @@ KEYPOINT_NAMES = [
|
||||
]
|
||||
|
||||
BASE_MODEL_NAME = "yolo26n-pose.pt"
|
||||
BASE_POSE_CONF = 0.10
|
||||
POSE_KEYPOINT_MIN_CONFIDENCE = 0.20
|
||||
POSE_RELIABLE_MIN_SCORE = 0.30
|
||||
POSE_RELIABLE_MIN_KEYPOINTS = 6
|
||||
@ -106,6 +107,7 @@ def base_model_candidates(root):
|
||||
|
||||
return [
|
||||
script_dir / BASE_MODEL_NAME,
|
||||
script_dir.parent / BASE_MODEL_NAME,
|
||||
Path.cwd() / BASE_MODEL_NAME,
|
||||
root / BASE_MODEL_NAME,
|
||||
root.parent / BASE_MODEL_NAME,
|
||||
@ -133,6 +135,98 @@ def resolve_model_path(root, requested):
|
||||
return trained, "pose_missing"
|
||||
|
||||
|
||||
def has_reliable_persons(persons):
|
||||
return any(bool(person.get("reliable")) for person in persons or [])
|
||||
|
||||
|
||||
def persons_from_results(results, img_w, img_h):
|
||||
persons = []
|
||||
|
||||
if not results:
|
||||
return persons
|
||||
|
||||
r = results[0]
|
||||
names = r.names or {}
|
||||
|
||||
kpts_xyn = None
|
||||
kpts_conf = None
|
||||
|
||||
if r.keypoints is not None:
|
||||
try:
|
||||
kpts_xyn = r.keypoints.xyn.cpu().numpy()
|
||||
except Exception:
|
||||
kpts_xyn = None
|
||||
try:
|
||||
kpts_conf = r.keypoints.conf.cpu().numpy()
|
||||
except Exception:
|
||||
kpts_conf = None
|
||||
|
||||
if r.boxes is None:
|
||||
return persons
|
||||
|
||||
for i, b in enumerate(r.boxes):
|
||||
score = float(b.conf[0].item())
|
||||
label = ""
|
||||
try:
|
||||
cls_id = int(b.cls[0].item())
|
||||
label = str(names.get(cls_id, cls_id)).strip().lower()
|
||||
except Exception:
|
||||
label = ""
|
||||
|
||||
x1, y1, x2, y2 = [float(v) for v in b.xyxy[0].tolist()]
|
||||
x1 = max(0.0, min(float(img_w), x1))
|
||||
y1 = max(0.0, min(float(img_h), y1))
|
||||
x2 = max(0.0, min(float(img_w), x2))
|
||||
y2 = max(0.0, min(float(img_h), y2))
|
||||
|
||||
if x2 <= x1 or y2 <= y1:
|
||||
continue
|
||||
|
||||
box = {
|
||||
"x": x1 / img_w,
|
||||
"y": y1 / img_h,
|
||||
"w": (x2 - x1) / img_w,
|
||||
"h": (y2 - y1) / img_h,
|
||||
}
|
||||
|
||||
keypoints = []
|
||||
if kpts_xyn is not None and i < len(kpts_xyn):
|
||||
person_kpts = kpts_xyn[i]
|
||||
for ki, (kx, ky) in enumerate(person_kpts):
|
||||
kconf = 0.0
|
||||
if kpts_conf is not None and i < len(kpts_conf) and ki < len(kpts_conf[i]):
|
||||
kconf = float(kpts_conf[i][ki])
|
||||
|
||||
name = KEYPOINT_NAMES[ki] if ki < len(KEYPOINT_NAMES) else str(ki)
|
||||
keypoints.append({
|
||||
"name": name,
|
||||
"x": float(kx),
|
||||
"y": float(ky),
|
||||
"conf": kconf,
|
||||
})
|
||||
|
||||
persons.append(annotate_pose_person_quality({
|
||||
"label": label,
|
||||
"score": score,
|
||||
"box": box,
|
||||
"keypoints": keypoints,
|
||||
}))
|
||||
|
||||
return persons
|
||||
|
||||
|
||||
def predict_persons(model_path, image_path, img_w, img_h, conf, imgsz, device):
|
||||
model = YOLO(str(model_path))
|
||||
results = model.predict(
|
||||
source=str(image_path),
|
||||
conf=float(conf),
|
||||
imgsz=int(imgsz),
|
||||
verbose=False,
|
||||
device=device,
|
||||
)
|
||||
return persons_from_results(results, img_w, img_h)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", required=True)
|
||||
@ -158,32 +252,18 @@ def main():
|
||||
img = Image.open(image_path).convert("RGB")
|
||||
img_w, img_h = img.size
|
||||
|
||||
try:
|
||||
model = YOLO(str(model_path))
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
"available": False,
|
||||
"source": "pose_load_failed",
|
||||
"modelPath": str(model_path),
|
||||
"error": repr(e),
|
||||
"persons": [],
|
||||
}, ensure_ascii=False))
|
||||
return
|
||||
|
||||
device = 0 if torch.cuda.is_available() else "cpu"
|
||||
effective_model_path = model_path
|
||||
effective_model_source = model_source
|
||||
effective_conf = float(args.conf)
|
||||
fallback_error = ""
|
||||
|
||||
try:
|
||||
results = model.predict(
|
||||
source=str(image_path),
|
||||
conf=float(args.conf),
|
||||
imgsz=int(args.imgsz),
|
||||
verbose=False,
|
||||
device=device,
|
||||
)
|
||||
persons = predict_persons(model_path, image_path, img_w, img_h, effective_conf, args.imgsz, device)
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
"available": False,
|
||||
"source": "pose_predict_failed",
|
||||
"source": "pose_failed",
|
||||
"modelPath": str(model_path),
|
||||
"image": str(image_path),
|
||||
"error": repr(e),
|
||||
@ -191,87 +271,50 @@ def main():
|
||||
}, ensure_ascii=False))
|
||||
return
|
||||
|
||||
persons = []
|
||||
if not has_reliable_persons(persons) and model_source != "yolo26_pose_base":
|
||||
for candidate in base_model_candidates(root):
|
||||
base_model_path = existing_file(candidate)
|
||||
if not base_model_path or base_model_path == model_path:
|
||||
continue
|
||||
|
||||
if results:
|
||||
r = results[0]
|
||||
names = r.names or {}
|
||||
|
||||
kpts_xyn = None
|
||||
kpts_conf = None
|
||||
|
||||
if r.keypoints is not None:
|
||||
try:
|
||||
kpts_xyn = r.keypoints.xyn.cpu().numpy()
|
||||
except Exception:
|
||||
kpts_xyn = None
|
||||
try:
|
||||
kpts_conf = r.keypoints.conf.cpu().numpy()
|
||||
except Exception:
|
||||
kpts_conf = None
|
||||
base_conf = min(float(args.conf), BASE_POSE_CONF)
|
||||
base_persons = predict_persons(
|
||||
base_model_path,
|
||||
image_path,
|
||||
img_w,
|
||||
img_h,
|
||||
base_conf,
|
||||
args.imgsz,
|
||||
device,
|
||||
)
|
||||
if base_persons:
|
||||
persons = base_persons
|
||||
effective_model_path = base_model_path
|
||||
effective_model_source = "yolo26_pose_base_fallback"
|
||||
effective_conf = base_conf
|
||||
break
|
||||
except Exception as e:
|
||||
fallback_error = repr(e)
|
||||
|
||||
if r.boxes is not None:
|
||||
for i, b in enumerate(r.boxes):
|
||||
score = float(b.conf[0].item())
|
||||
label = ""
|
||||
try:
|
||||
cls_id = int(b.cls[0].item())
|
||||
label = str(names.get(cls_id, cls_id)).strip().lower()
|
||||
except Exception:
|
||||
label = ""
|
||||
|
||||
x1, y1, x2, y2 = [float(v) for v in b.xyxy[0].tolist()]
|
||||
x1 = max(0.0, min(float(img_w), x1))
|
||||
y1 = max(0.0, min(float(img_h), y1))
|
||||
x2 = max(0.0, min(float(img_w), x2))
|
||||
y2 = max(0.0, min(float(img_h), y2))
|
||||
|
||||
if x2 <= x1 or y2 <= y1:
|
||||
continue
|
||||
|
||||
box = {
|
||||
"x": x1 / img_w,
|
||||
"y": y1 / img_h,
|
||||
"w": (x2 - x1) / img_w,
|
||||
"h": (y2 - y1) / img_h,
|
||||
}
|
||||
|
||||
keypoints = []
|
||||
if kpts_xyn is not None and i < len(kpts_xyn):
|
||||
person_kpts = kpts_xyn[i]
|
||||
for ki, (kx, ky) in enumerate(person_kpts):
|
||||
kconf = 0.0
|
||||
if kpts_conf is not None and i < len(kpts_conf) and ki < len(kpts_conf[i]):
|
||||
kconf = float(kpts_conf[i][ki])
|
||||
|
||||
name = KEYPOINT_NAMES[ki] if ki < len(KEYPOINT_NAMES) else str(ki)
|
||||
keypoints.append({
|
||||
"name": name,
|
||||
"x": float(kx),
|
||||
"y": float(ky),
|
||||
"conf": kconf,
|
||||
})
|
||||
|
||||
persons.append(annotate_pose_person_quality({
|
||||
"label": label,
|
||||
"score": score,
|
||||
"box": box,
|
||||
"keypoints": keypoints,
|
||||
}))
|
||||
|
||||
print(json.dumps({
|
||||
payload = {
|
||||
"available": True,
|
||||
"source": model_source,
|
||||
"modelPath": str(model_path),
|
||||
"source": effective_model_source,
|
||||
"modelPath": str(effective_model_path),
|
||||
"image": str(image_path),
|
||||
"conf": float(args.conf),
|
||||
"conf": effective_conf,
|
||||
"imgsz": int(args.imgsz),
|
||||
"device": str(device),
|
||||
"imageWidth": img_w,
|
||||
"imageHeight": img_h,
|
||||
"personCount": len(persons),
|
||||
"persons": persons,
|
||||
}, ensure_ascii=False))
|
||||
}
|
||||
|
||||
if fallback_error:
|
||||
payload["fallbackError"] = fallback_error
|
||||
|
||||
print(json.dumps(payload, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@ -312,6 +312,12 @@ def main():
|
||||
completed_epochs = 0
|
||||
epochs_without_improvement = 0
|
||||
|
||||
def training_progress(epoch: int, epoch_fraction: float) -> float:
|
||||
safe_epoch = max(1, min(epochs, int(epoch)))
|
||||
safe_fraction = max(0.0, min(1.0, float(epoch_fraction)))
|
||||
completed = (safe_epoch - 1) + safe_fraction
|
||||
return 0.04 + 0.90 * (completed / max(1, epochs))
|
||||
|
||||
for epoch in range(1, epochs + 1):
|
||||
completed_epochs = epoch
|
||||
model.train()
|
||||
@ -333,10 +339,9 @@ def main():
|
||||
seen += batch_size_seen
|
||||
running_loss += float(loss.item()) * batch_size_seen
|
||||
|
||||
completed = (epoch - 1) + min(1.0, batch_idx / total_batches)
|
||||
emit_progress(
|
||||
"videomae",
|
||||
0.04 + 0.84 * (completed / max(1, epochs)),
|
||||
training_progress(epoch, 0.85 * min(1.0, batch_idx / total_batches)),
|
||||
"VideoMAE trainiert...",
|
||||
epoch=epoch,
|
||||
epochs=epochs,
|
||||
@ -364,7 +369,7 @@ def main():
|
||||
|
||||
emit_progress(
|
||||
"videomae",
|
||||
0.88 + 0.08 * (epoch / max(1, epochs)),
|
||||
training_progress(epoch, 1.0),
|
||||
"VideoMAE wird validiert...",
|
||||
epoch=epoch,
|
||||
epochs=epochs,
|
||||
|
||||
@ -37,5 +37,8 @@
|
||||
"trainingYoloBatchSize": 0,
|
||||
"trainingLowPriority": false,
|
||||
"trainingVideoMAEEnabled": true,
|
||||
"trainingAutoPauseEnabled": false,
|
||||
"trainingAutoPauseCpuPercent": 95,
|
||||
"trainingAutoPauseTemperatureC": 85,
|
||||
"encryptedCookies": "p0aRfigWn3+TfypNAV7vPky13RW+gwhTlhNOGKKAED0FSMf0kdGV6LgIizzTeDMD5Ck+9SGGf6EfVg+RPnEr3ZjkkkjOTVGOJ9XNzpuczHqzi0ifmt0l9/lnNTaqh8oTzrk09uQC9YYe0CtsY6gjpYFAdVEPGimCPdULqn+8Jln1CSDiBGRGF0uZGxfmAZ1T/YQym/wjiCCdIRfcjTF4f+sz6PMBQOOnTA74if1OFNA6PxpGoXfHS7xvMeV6RX3J/qcTyMmFWr+uD2h1UKs7N9w4Y1sfw4heB10jzy/VL+9gWg2o4AKE5tGqOmlVYpv8KculMWfJHNxNpRonPNizPR4jO2jZYdr3AikuXTTeILyN7hb4MtO4fXblJ+9of1ABR6LvDe1dAXmbkNdqEqRo0k1WS6jaCdNkrleP8vGuH56H7jZMnEeJAcQxS99kVjFo0qMAEJ2ai8HPkdYURC3dWSu9uIbGmiqi6hgva2WM6jPG7TqZDyqUS7hjJwAmPa3aLPnsEWHqkcE="
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ func registerRoutes(mux *http.ServeMux, auth *AuthManager) *ModelStore {
|
||||
api.HandleFunc("/api/perf/stream", perfStreamHandler)
|
||||
api.HandleFunc("/api/status/disk", diskStatusHandler)
|
||||
api.HandleFunc("/api/status/ai-server", aiServerStatusHandler)
|
||||
api.HandleFunc("/api/status/ai-server/restart", aiServerRestartHandler)
|
||||
api.HandleFunc("/api/status/log", appLogHandler)
|
||||
|
||||
api.HandleFunc("/api/autostart/state", autostartStateHandler)
|
||||
|
||||
@ -119,6 +119,56 @@ var (
|
||||
aiServerRoot string
|
||||
)
|
||||
|
||||
type aiServerLifecycleSnapshot struct {
|
||||
State string
|
||||
Since time.Time
|
||||
Message string
|
||||
}
|
||||
|
||||
var aiServerLifecycle = struct {
|
||||
sync.Mutex
|
||||
state string
|
||||
since time.Time
|
||||
message string
|
||||
}{}
|
||||
|
||||
func setAIServerLifecycle(state string, message string) {
|
||||
state = strings.TrimSpace(strings.ToLower(state))
|
||||
message = strings.TrimSpace(message)
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
|
||||
aiServerLifecycle.Lock()
|
||||
defer aiServerLifecycle.Unlock()
|
||||
|
||||
if aiServerLifecycle.state != state {
|
||||
aiServerLifecycle.since = time.Now().UTC()
|
||||
} else if aiServerLifecycle.since.IsZero() {
|
||||
aiServerLifecycle.since = time.Now().UTC()
|
||||
}
|
||||
|
||||
aiServerLifecycle.state = state
|
||||
aiServerLifecycle.message = message
|
||||
}
|
||||
|
||||
func getAIServerLifecycle() aiServerLifecycleSnapshot {
|
||||
aiServerLifecycle.Lock()
|
||||
defer aiServerLifecycle.Unlock()
|
||||
|
||||
return aiServerLifecycleSnapshot{
|
||||
State: aiServerLifecycle.state,
|
||||
Since: aiServerLifecycle.since,
|
||||
Message: aiServerLifecycle.message,
|
||||
}
|
||||
}
|
||||
|
||||
func aiServerHasCurrentProcess() bool {
|
||||
aiServerMu.Lock()
|
||||
defer aiServerMu.Unlock()
|
||||
return aiServerCurrent != nil
|
||||
}
|
||||
|
||||
func aiServerAutostartEnabled() bool {
|
||||
raw := strings.ToLower(strings.TrimSpace(os.Getenv("AI_SERVER_AUTOSTART")))
|
||||
|
||||
@ -470,10 +520,34 @@ func aiServerStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Timeout: 900 * time.Millisecond,
|
||||
}
|
||||
|
||||
lifecycle := getAIServerLifecycle()
|
||||
state := strings.TrimSpace(strings.ToLower(lifecycle.State))
|
||||
if state == "" || state == "unknown" {
|
||||
if aiServerHasCurrentProcess() {
|
||||
state = "starting"
|
||||
} else {
|
||||
state = "stopped"
|
||||
}
|
||||
}
|
||||
|
||||
status := map[string]any{
|
||||
"ok": false,
|
||||
"running": false,
|
||||
"url": url,
|
||||
"ok": false,
|
||||
"running": false,
|
||||
"url": url,
|
||||
"state": state,
|
||||
"starting": state == "starting",
|
||||
"restarting": state == "restarting",
|
||||
}
|
||||
if !lifecycle.Since.IsZero() {
|
||||
status["stateSince"] = lifecycle.Since.Format(time.RFC3339)
|
||||
}
|
||||
if strings.TrimSpace(lifecycle.Message) != "" {
|
||||
status["message"] = lifecycle.Message
|
||||
}
|
||||
|
||||
if state == "restarting" && !lifecycle.Since.IsZero() && time.Since(lifecycle.Since) < 2*time.Minute {
|
||||
writeJSON(w, http.StatusOK, status)
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, url+"/health", nil)
|
||||
@ -498,27 +572,67 @@ func aiServerStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
status["ok"] = true
|
||||
status["running"] = true
|
||||
status["state"] = "running"
|
||||
status["starting"] = false
|
||||
status["restarting"] = false
|
||||
status["message"] = "AI Server läuft."
|
||||
setAIServerLifecycle("running", "AI Server läuft.")
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, status)
|
||||
}
|
||||
|
||||
func aiServerRestartHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
current := getAIServerLifecycle()
|
||||
if strings.EqualFold(current.State, "restarting") && time.Since(current.Since) < 2*time.Minute {
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{
|
||||
"ok": true,
|
||||
"state": "restarting",
|
||||
"restarting": true,
|
||||
"message": "AI Server Neustart läuft bereits.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setAIServerLifecycle("restarting", "AI Server wird neu gestartet…")
|
||||
appGo("ai-server-manual-restart", restartAIServer)
|
||||
|
||||
writeJSON(w, http.StatusAccepted, map[string]any{
|
||||
"ok": true,
|
||||
"state": "restarting",
|
||||
"restarting": true,
|
||||
"message": "AI Server wird neu gestartet.",
|
||||
})
|
||||
}
|
||||
|
||||
func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
if !aiServerAutostartEnabled() {
|
||||
appLogln("ℹ️ AI Server Autostart deaktiviert.")
|
||||
setAIServerLifecycle("stopped", "AI Server Autostart ist deaktiviert.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if getAIServerLifecycle().State != "restarting" {
|
||||
setAIServerLifecycle("starting", "AI Server wird gestartet…")
|
||||
}
|
||||
|
||||
if err := stopStaleAIServerOnPort(aiServerPortFromURL()); err != nil {
|
||||
appLogln("⚠️ Alter AI Server konnte nicht bereinigt werden:", err)
|
||||
}
|
||||
|
||||
if err := ensureMLPythonSetup(ctx); err != nil {
|
||||
setAIServerLifecycle("error", "AI Server konnte nicht gestartet werden: "+err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scriptDir, err := findAIServerScriptDir()
|
||||
if err != nil {
|
||||
setAIServerLifecycle("error", "AI Server Skript wurde nicht gefunden: "+err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -588,6 +702,14 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
env = upsertEnv(env, "TRAINING_ROOT", trainingRoot)
|
||||
env = upsertEnv(env, "DETECTION_LABELS_PATH", detectionLabelsPath)
|
||||
|
||||
if strings.TrimSpace(os.Getenv("YOLO_BASE_POSE_MODEL")) == "" {
|
||||
if poseBase, err := embeddedPoseModelPath(); err == nil && fileExistsNonEmpty(poseBase) {
|
||||
env = upsertEnv(env, "YOLO_BASE_POSE_MODEL", poseBase)
|
||||
} else if err != nil {
|
||||
appLogln("⚠️ YOLO Base-Pose-Modell nicht gefunden:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Modell ebenfalls standardmäßig aus generated/training nehmen,
|
||||
// aber YOLO_MODEL darf weiterhin extern überschrieben werden.
|
||||
if strings.TrimSpace(os.Getenv("YOLO_MODEL")) == "" && defaultModel.EffectiveExists {
|
||||
@ -629,10 +751,12 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
prepareAIServerCommandForLifecycle(cmd)
|
||||
|
||||
if ctx.Err() != nil {
|
||||
setAIServerLifecycle("stopped", "AI Server Start wurde abgebrochen.")
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
setAIServerLifecycle("error", "AI Server konnte nicht gestartet werden: "+err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -643,6 +767,16 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
|
||||
appGo("ai-server-wait", func() {
|
||||
err := cmd.Wait()
|
||||
defer func() {
|
||||
if err != nil && ctx.Err() == nil && !proc.isStopping() {
|
||||
setAIServerLifecycle("error", "AI Server wurde beendet: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if getAIServerLifecycle().State != "restarting" {
|
||||
setAIServerLifecycle("stopped", "AI Server wurde beendet.")
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil && ctx.Err() == nil && !proc.isStopping() {
|
||||
appLogf("⚠️ AI Server beendet: %v", err)
|
||||
@ -656,10 +790,12 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) {
|
||||
if err := waitForAIServer(ctx, aiServerURL(), 120*time.Second); err != nil {
|
||||
appLogln("⚠️ AI Server noch nicht bereit:", err)
|
||||
// Nicht hart abbrechen. Deine Analyse kann auf den Fallback gehen.
|
||||
setAIServerLifecycle("starting", "AI Server startet noch: "+err.Error())
|
||||
return proc, nil
|
||||
}
|
||||
|
||||
appLogln("🧠 AI Server bereit:", aiServerURL())
|
||||
setAIServerLifecycle("running", "AI Server läuft.")
|
||||
return proc, nil
|
||||
}
|
||||
|
||||
@ -695,6 +831,7 @@ func restartAIServer() {
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
setAIServerLifecycle("stopped", "AI Server Neustart übersprungen, App wird beendet.")
|
||||
appLogln("ℹ️ AI Server Neustart übersprungen (App wird beendet).")
|
||||
return
|
||||
}
|
||||
@ -702,6 +839,8 @@ func restartAIServer() {
|
||||
appLogln("🔄 AI Server wird neu gestartet…")
|
||||
publishAppNotification("info", "AI Server", "AI Server wird neu gestartet…", 2200)
|
||||
|
||||
setAIServerLifecycle("restarting", "AI Server wird neu gestartet…")
|
||||
|
||||
if aiServerCurrent != nil {
|
||||
aiServerCurrent.Stop()
|
||||
aiServerCurrent = nil
|
||||
@ -709,6 +848,7 @@ func restartAIServer() {
|
||||
|
||||
proc, err := startAIServer(ctx)
|
||||
if err != nil {
|
||||
setAIServerLifecycle("error", "AI Server Neustart fehlgeschlagen: "+err.Error())
|
||||
appLogln("⚠️ AI Server Neustart fehlgeschlagen:", err)
|
||||
publishAppNotification("warning", "AI Server", "Neustart fehlgeschlagen: "+err.Error(), 5000)
|
||||
return
|
||||
@ -717,6 +857,7 @@ func restartAIServer() {
|
||||
aiServerCurrent = proc
|
||||
|
||||
if proc != nil {
|
||||
setAIServerLifecycle("running", "AI Server wurde neu gestartet.")
|
||||
appLogln("✅ AI Server neu gestartet.")
|
||||
publishAppNotification("success", "AI Server", "AI Server wurde neu gestartet.", 2600)
|
||||
}
|
||||
|
||||
@ -67,12 +67,13 @@ type TrainingPrediction struct {
|
||||
}
|
||||
|
||||
type TrainingCorrection struct {
|
||||
SexPosition string `json:"sexPosition"`
|
||||
PeoplePresent []string `json:"peoplePresent"`
|
||||
BodyPartsPresent []string `json:"bodyPartsPresent"`
|
||||
ObjectsPresent []string `json:"objectsPresent"`
|
||||
ClothingPresent []string `json:"clothingPresent"`
|
||||
Boxes []TrainingBox `json:"boxes"`
|
||||
SexPosition string `json:"sexPosition"`
|
||||
PeoplePresent []string `json:"peoplePresent"`
|
||||
BodyPartsPresent []string `json:"bodyPartsPresent"`
|
||||
ObjectsPresent []string `json:"objectsPresent"`
|
||||
ClothingPresent []string `json:"clothingPresent"`
|
||||
Boxes []TrainingBox `json:"boxes"`
|
||||
PosePersons []TrainingPosePerson `json:"posePersons,omitempty"`
|
||||
}
|
||||
|
||||
type TrainingSample struct {
|
||||
@ -278,12 +279,16 @@ type TrainingJobStatus struct {
|
||||
CPUPercent float64 `json:"cpuPercent,omitempty"`
|
||||
TemperatureC float64 `json:"temperatureC,omitempty"`
|
||||
|
||||
Stage string `json:"stage,omitempty"`
|
||||
Epoch int `json:"epoch,omitempty"`
|
||||
Epochs int `json:"epochs,omitempty"`
|
||||
Stage string `json:"stage,omitempty"`
|
||||
StageStartedAt string `json:"stageStartedAt,omitempty"`
|
||||
StageProgress float64 `json:"stageProgress,omitempty"`
|
||||
Epoch int `json:"epoch,omitempty"`
|
||||
Epochs int `json:"epochs,omitempty"`
|
||||
|
||||
MAP50 float64 `json:"map50,omitempty"`
|
||||
MAP5095 float64 `json:"map5095,omitempty"`
|
||||
MAP50 float64 `json:"map50,omitempty"`
|
||||
MAP5095 float64 `json:"map5095,omitempty"`
|
||||
Accuracy float64 `json:"accuracy,omitempty"`
|
||||
Loss float64 `json:"loss,omitempty"`
|
||||
}
|
||||
|
||||
type TrainingConfidence struct {
|
||||
@ -348,6 +353,8 @@ type trainingProgressEvent struct {
|
||||
SampleID string `json:"sampleId,omitempty"`
|
||||
MAP50 *float64 `json:"mAP50,omitempty"`
|
||||
MAP5095 *float64 `json:"mAP5095,omitempty"`
|
||||
Accuracy *float64 `json:"accuracy,omitempty"`
|
||||
Loss *float64 `json:"loss,omitempty"`
|
||||
}
|
||||
|
||||
type TrainingFeedbackListResponse struct {
|
||||
@ -801,6 +808,31 @@ func trainingScaleProgress(local float64, start int, end int) int {
|
||||
return start + int(math.Round(local*float64(end-start)))
|
||||
}
|
||||
|
||||
func trainingApplyStageProgress(s *TrainingJobStatus, stage string, localProgress float64) {
|
||||
stage = strings.TrimSpace(stage)
|
||||
if stage == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if math.IsNaN(localProgress) || math.IsInf(localProgress, 0) {
|
||||
localProgress = 0
|
||||
}
|
||||
localProgress = clamp01(localProgress)
|
||||
|
||||
if strings.TrimSpace(s.Stage) != stage {
|
||||
s.StageStartedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
s.Epoch = 0
|
||||
s.Epochs = 0
|
||||
s.MAP50 = 0
|
||||
s.MAP5095 = 0
|
||||
s.Accuracy = 0
|
||||
s.Loss = 0
|
||||
}
|
||||
|
||||
s.Stage = stage
|
||||
s.StageProgress = localProgress
|
||||
}
|
||||
|
||||
func trainingHandleProgressLine(line string, start int, end int, defaultStep string) bool {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
@ -829,11 +861,12 @@ func trainingHandleProgressLine(line string, start int, end int, defaultStep str
|
||||
|
||||
s.Step = step
|
||||
|
||||
if strings.TrimSpace(ev.Stage) != "" {
|
||||
s.Stage = strings.TrimSpace(ev.Stage)
|
||||
}
|
||||
trainingApplyStageProgress(s, ev.Stage, ev.Progress)
|
||||
|
||||
if ev.Epoch > 0 {
|
||||
if s.Epoch <= 0 {
|
||||
s.StageStartedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
s.Epoch = ev.Epoch
|
||||
}
|
||||
|
||||
@ -847,6 +880,12 @@ func trainingHandleProgressLine(line string, start int, end int, defaultStep str
|
||||
if ev.MAP5095 != nil && *ev.MAP5095 > 0 {
|
||||
s.MAP5095 = *ev.MAP5095
|
||||
}
|
||||
if ev.Accuracy != nil {
|
||||
s.Accuracy = *ev.Accuracy
|
||||
}
|
||||
if ev.Loss != nil {
|
||||
s.Loss = *ev.Loss
|
||||
}
|
||||
|
||||
sampleID := strings.TrimSpace(ev.SampleID)
|
||||
if sampleID != "" &&
|
||||
@ -1814,11 +1853,13 @@ func trainingGetJobStatus() TrainingJobStatus {
|
||||
func trainingStartJob(cancel context.CancelFunc) {
|
||||
trainingJob.mu.Lock()
|
||||
|
||||
startedAt := time.Now().UTC().Format(time.RFC3339)
|
||||
trainingJob.status = TrainingJobStatus{
|
||||
Running: true,
|
||||
Progress: 5,
|
||||
Step: "Training wird vorbereitet…",
|
||||
StartedAt: time.Now().UTC().Format(time.RFC3339),
|
||||
Running: true,
|
||||
Progress: 5,
|
||||
Step: "Training wird vorbereitet…",
|
||||
StartedAt: startedAt,
|
||||
StageStartedAt: startedAt,
|
||||
}
|
||||
|
||||
trainingJob.cancel = cancel
|
||||
@ -3368,6 +3409,7 @@ func trainingNegativeCorrection() *TrainingCorrection {
|
||||
ObjectsPresent: []string{},
|
||||
ClothingPresent: []string{},
|
||||
Boxes: []TrainingBox{},
|
||||
PosePersons: []TrainingPosePerson{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -3441,7 +3483,7 @@ func trainingFeedbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
trainingDeletePoseSample(root, req.SampleID)
|
||||
if sexPosition := trainingSexPositionForFeedback(sample, req); !isNoSexPositionLabel(sexPosition) {
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, detectorBoxes); err != nil {
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, detectorBoxes, req.Correction); err != nil {
|
||||
appLogln("pose sample write failed:", err)
|
||||
}
|
||||
}
|
||||
@ -3575,7 +3617,7 @@ func trainingFeedbackUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Correction: req.Correction,
|
||||
Notes: req.Notes,
|
||||
}); !isNoSexPositionLabel(sexPosition) {
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, detectorBoxes); err != nil {
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, detectorBoxes, req.Correction); err != nil {
|
||||
appLogln("pose sample update failed:", err)
|
||||
}
|
||||
}
|
||||
@ -4064,6 +4106,7 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
|
||||
if !targets.Detector {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "detector", 1)
|
||||
if s.Progress < 58 {
|
||||
s.Progress = 58
|
||||
}
|
||||
@ -4079,6 +4122,8 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
positiveValCount > 0 {
|
||||
detectorStartedAt := time.Now()
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "detector", 0)
|
||||
s.Epochs = trainingDetectorEpochs()
|
||||
s.Progress = 15
|
||||
s.Step = "YOLO26 Detector wird trainiert…"
|
||||
})
|
||||
@ -4175,6 +4220,7 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
|
||||
if !targets.Pose {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "pose", 1)
|
||||
if s.Progress < 82 {
|
||||
s.Progress = 82
|
||||
}
|
||||
@ -4186,6 +4232,8 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
} else {
|
||||
poseStartedAt = time.Now()
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "pose", 0)
|
||||
s.Epochs = trainingDetectorEpochs()
|
||||
if s.Progress < 60 {
|
||||
s.Progress = 60
|
||||
}
|
||||
@ -4221,6 +4269,8 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
poseTrainCount >= minPoseTrainCount &&
|
||||
poseValCount >= minPoseValCount {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "pose", 0.04)
|
||||
s.Epochs = trainingDetectorEpochs()
|
||||
if s.Progress < 62 {
|
||||
s.Progress = 62
|
||||
}
|
||||
@ -4314,6 +4364,7 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
|
||||
if !targets.VideoMAE {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "videomae", 1)
|
||||
if s.Progress < 98 {
|
||||
s.Progress = 98
|
||||
}
|
||||
@ -4324,6 +4375,7 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
appLogln(videoMAEOutput)
|
||||
} else if !runtimeOpts.VideoMAEEnabled {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "videomae", 1)
|
||||
if s.Progress < 84 {
|
||||
s.Progress = 84
|
||||
}
|
||||
@ -4335,6 +4387,8 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
} else {
|
||||
videoMAEStartedAt = time.Now()
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "videomae", 0)
|
||||
s.Epochs = trainingVideoMAEEpochs()
|
||||
if s.Progress < 84 {
|
||||
s.Progress = 84
|
||||
}
|
||||
@ -4367,6 +4421,8 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
videoMAETrainCount >= minVideoMAETrainCount &&
|
||||
videoMAEValCount >= minVideoMAEValCount {
|
||||
trainingSetJobStatus(func(s *TrainingJobStatus) {
|
||||
trainingApplyStageProgress(s, "videomae", 0.04)
|
||||
s.Epochs = trainingVideoMAEEpochs()
|
||||
if s.Progress < 86 {
|
||||
s.Progress = 86
|
||||
}
|
||||
@ -4556,6 +4612,9 @@ func trainingRunJob(ctx context.Context, root string, count int, targets trainin
|
||||
|
||||
s.Running = false
|
||||
s.Progress = 100
|
||||
if strings.TrimSpace(s.Stage) != "" {
|
||||
s.StageProgress = 1
|
||||
}
|
||||
s.Step = "Training abgeschlossen."
|
||||
s.Message = message
|
||||
s.Error = errorText
|
||||
@ -4769,6 +4828,7 @@ func trainingEffectiveCorrection(annotation TrainingAnnotation) TrainingCorrecti
|
||||
ObjectsPresent: trainingScoredLabelsToStrings(p.ObjectsPresent),
|
||||
ClothingPresent: trainingScoredLabelsToStrings(p.ClothingPresent),
|
||||
Boxes: p.Boxes,
|
||||
PosePersons: p.Persons,
|
||||
}
|
||||
}
|
||||
|
||||
@ -7716,6 +7776,18 @@ func trainingPosePersonsForSample(root string, sample *TrainingSample) []Trainin
|
||||
return pose.Persons
|
||||
}
|
||||
|
||||
func trainingPosePersonsForCorrection(root string, sample *TrainingSample, correction *TrainingCorrection) []TrainingPosePerson {
|
||||
if correction != nil && correction.PosePersons != nil {
|
||||
persons := make([]TrainingPosePerson, 0, len(correction.PosePersons))
|
||||
for _, person := range correction.PosePersons {
|
||||
persons = append(persons, trainingAnnotatePosePersonQuality(person))
|
||||
}
|
||||
return persons
|
||||
}
|
||||
|
||||
return trainingPosePersonsForSample(root, sample)
|
||||
}
|
||||
|
||||
func trainingPoseContextPersonBoxes(boxes []TrainingBox) []TrainingBox {
|
||||
out := []TrainingBox{}
|
||||
|
||||
@ -7844,6 +7916,7 @@ func trainingWritePoseSample(
|
||||
sample *TrainingSample,
|
||||
sexPosition string,
|
||||
contextBoxes []TrainingBox,
|
||||
correction *TrainingCorrection,
|
||||
) error {
|
||||
if sample == nil {
|
||||
return errors.New("sample missing")
|
||||
@ -7864,7 +7937,7 @@ func trainingWritePoseSample(
|
||||
return fmt.Errorf("pose class not configured: %s", sexPosition)
|
||||
}
|
||||
|
||||
persons := trainingPosePersonsForSample(root, sample)
|
||||
persons := trainingPosePersonsForCorrection(root, sample, correction)
|
||||
persons = trainingFilterPosePersonsByContext(persons, contextBoxes)
|
||||
labelContent, err := trainingPoseLabelContent(persons, classID)
|
||||
if err != nil {
|
||||
@ -7972,7 +8045,7 @@ func trainingSyncPoseDataset(root string) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, effective.Boxes); err != nil {
|
||||
if err := trainingWritePoseSample(root, sample, sexPosition, effective.Boxes, &effective); err != nil {
|
||||
appLogln("pose sample sync skipped:", sampleID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -67,4 +67,4 @@
|
||||
transform: scale(1.04);
|
||||
filter: drop-shadow(0 0 6px rgb(16 185 129 / 0.55));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
frontend/src/assets/body_front.svg
Normal file
72
frontend/src/assets/body_front.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 522 KiB |
@ -2313,6 +2313,7 @@ export default function FinishedDownloads({
|
||||
const [deletedKeys, setDeletedKeys] = useState<Set<string>>(() => new Set())
|
||||
const [deletingKeys, setDeletingKeys] = useState<Set<string>>(() => new Set())
|
||||
const [deletedSuccessKeys, setDeletedSuccessKeys] = useState<Set<string>>(() => new Set())
|
||||
const [bulkDeletedSuccessKeys, setBulkDeletedSuccessKeys] = useState<Set<string>>(() => new Set())
|
||||
const [keepingKeys, setKeepingKeys] = useState<Set<string>>(() => new Set())
|
||||
const [keptSuccessKeys, setKeptSuccessKeys] = useState<Set<string>>(() => new Set())
|
||||
const [removingKeys, setRemovingKeys] = useState<Set<string>>(() => new Set())
|
||||
@ -3152,10 +3153,20 @@ export default function FinishedDownloads({
|
||||
return next
|
||||
}, [activeVisibleRows, keyFor, selectionStore, selectionVersion])
|
||||
|
||||
const visibleDeletedSuccessKeys = useMemo(() => {
|
||||
if (bulkDeletedSuccessKeys.size === 0) return deletedSuccessKeys
|
||||
|
||||
const next = new Set(deletedSuccessKeys)
|
||||
for (const key of bulkDeletedSuccessKeys) {
|
||||
next.add(key)
|
||||
}
|
||||
return next
|
||||
}, [bulkDeletedSuccessKeys, deletedSuccessKeys])
|
||||
|
||||
const autoPageSizeSuspended =
|
||||
bulkBusy ||
|
||||
deletingKeys.size > 0 ||
|
||||
deletedSuccessKeys.size > 0 ||
|
||||
visibleDeletedSuccessKeys.size > 0 ||
|
||||
keepingKeys.size > 0 ||
|
||||
keptSuccessKeys.size > 0 ||
|
||||
removingKeys.size > 0
|
||||
@ -3463,6 +3474,15 @@ export default function FinishedDownloads({
|
||||
})
|
||||
}, [])
|
||||
|
||||
const markBulkDeletedSuccess = useCallback((key: string, value: boolean) => {
|
||||
setBulkDeletedSuccessKeys((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (value) next.add(key)
|
||||
else next.delete(key)
|
||||
return next
|
||||
})
|
||||
}, [])
|
||||
|
||||
const markKeptSuccess = useCallback((key: string, value: boolean) => {
|
||||
setKeptSuccessKeys((prev) => {
|
||||
const next = new Set(prev)
|
||||
@ -3623,6 +3643,7 @@ export default function FinishedDownloads({
|
||||
})
|
||||
|
||||
markDeletedSuccess(key, false)
|
||||
markBulkDeletedSuccess(key, false)
|
||||
markKeptSuccess(key, false)
|
||||
markDeleting(key, false)
|
||||
markKeeping(key, false)
|
||||
@ -3636,6 +3657,7 @@ export default function FinishedDownloads({
|
||||
[
|
||||
cancelRemoveTimer,
|
||||
markDeletedSuccess,
|
||||
markBulkDeletedSuccess,
|
||||
markKeptSuccess,
|
||||
markDeleting,
|
||||
markKeeping,
|
||||
@ -3669,6 +3691,7 @@ export default function FinishedDownloads({
|
||||
setRemovingKeys(removeKeyAliases)
|
||||
setDeletingKeys(removeKeyAliases)
|
||||
setDeletedSuccessKeys(removeKeyAliases)
|
||||
setBulkDeletedSuccessKeys(removeKeyAliases)
|
||||
setKeepingKeys(removeKeyAliases)
|
||||
setKeptSuccessKeys(removeKeyAliases)
|
||||
|
||||
@ -4570,6 +4593,7 @@ export default function FinishedDownloads({
|
||||
deletedKeys.add(key)
|
||||
deletedItems.push({ key, file })
|
||||
markDeletedRowSuccess(key)
|
||||
markBulkDeletedSuccess(key, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -4685,6 +4709,7 @@ export default function FinishedDownloads({
|
||||
clearSelection,
|
||||
emitCountHint,
|
||||
markDeletedRowSuccess,
|
||||
markBulkDeletedSuccess,
|
||||
removeRowsTogether,
|
||||
notify,
|
||||
])
|
||||
@ -6805,7 +6830,7 @@ export default function FinishedDownloads({
|
||||
teaserState={teaserState}
|
||||
inlinePlay={inlinePlay}
|
||||
deletingKeys={deletingKeys}
|
||||
deletedSuccessKeys={deletedSuccessKeys}
|
||||
deletedSuccessKeys={visibleDeletedSuccessKeys}
|
||||
keptSuccessKeys={keptSuccessKeys}
|
||||
keepingKeys={keepingKeys}
|
||||
removingKeys={removingKeys}
|
||||
@ -6888,7 +6913,7 @@ export default function FinishedDownloads({
|
||||
assetNonce={assetNonce}
|
||||
assetNonceForJob={assetNonceForJob}
|
||||
deletingKeys={deletingKeys}
|
||||
deletedSuccessKeys={deletedSuccessKeys}
|
||||
deletedSuccessKeys={visibleDeletedSuccessKeys}
|
||||
keptSuccessKeys={keptSuccessKeys}
|
||||
keepingKeys={keepingKeys}
|
||||
removingKeys={removingKeys}
|
||||
@ -6941,7 +6966,7 @@ export default function FinishedDownloads({
|
||||
sizeBytesOf={sizeBytesOf}
|
||||
formatBytes={formatBytes}
|
||||
deletingKeys={deletingKeys}
|
||||
deletedSuccessKeys={deletedSuccessKeys}
|
||||
deletedSuccessKeys={visibleDeletedSuccessKeys}
|
||||
keptSuccessKeys={keptSuccessKeys}
|
||||
keepingKeys={keepingKeys}
|
||||
removingKeys={removingKeys}
|
||||
|
||||
@ -47,6 +47,198 @@ function IconBase({
|
||||
/* Einzelne Icons */
|
||||
/* ---------------------------------- */
|
||||
|
||||
export function PoseLeftEarIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2155 5110 c-171 -27 -335 -86 -471 -171 -107 -66 -278 -231 -352 -340 -105 -155 -192 -360 -232 -551 -39 -182 -36 -428 6 -510 40 -77 178 -76 218 1 12 23 14 57 9 177 -9 227 31 408 134 607 149 288 379 467 671 523 96 18 303 18 417 -1 310 -50 596 -204 797 -429 261 -292 428 -696 478 -1158 15 -141 12 -423 -5 -572 -59 -502 -262 -954 -648 -1439 -608 -766 -877 -987 -1199 -987 -209 0 -419 104 -533 264 -96 135 -153 305 -162 481 -6 123 -16 157 -54 190 -24 19 -42 25 -80 25 -42 0 -55 -5 -86 -34 -34 -31 -37 -38 -41 -100 -10 -159 39 -381 123 -552 130 -268 324 -427 616 -505 69 -19 109 -23 224 -23 123 0 151 4 228 27 225 69 447 227 690 492 118 128 377 437 515 613 373 479 586 957 659 1482 12 90 17 194 17 370 0 256 -11 372 -56 579 -203 944 -826 1525 -1658 1546 -91 2 -192 0 -225 -5z" />
|
||||
<path d="M2215 4564 c-95 -20 -236 -90 -319 -161 -129 -108 -239 -282 -291 -459 -97 -327 14 -746 290 -1099 95 -121 158 -182 199 -193 82 -23 156 32 156 117 0 53 -10 70 -89 157 -31 33 -71 82 -90 109 l-34 48 50 -7 c103 -13 251 -59 350 -106 127 -62 199 -129 245 -226 28 -60 32 -77 31 -158 0 -116 -22 -204 -83 -331 -96 -199 -254 -315 -505 -370 -150 -33 -238 -86 -414 -249 -115 -106 -126 -113 -152 -90 -30 27 -23 66 33 197 50 116 53 126 53 207 -1 47 -5 135 -9 195 -14 179 12 230 101 206 87 -24 117 -81 77 -147 -60 -99 -5 -204 107 -204 37 0 48 6 89 48 131 131 94 376 -74 492 -95 65 -247 89 -349 54 -94 -32 -168 -123 -198 -244 -15 -64 -15 -246 0 -339 10 -63 10 -63 -38 -180 -56 -133 -76 -215 -67 -280 20 -148 159 -276 298 -275 87 1 162 43 293 164 157 145 189 164 325 199 133 34 200 59 289 107 225 123 372 319 452 604 20 72 24 107 24 230 0 126 -3 153 -24 210 -89 251 -308 418 -666 509 -96 24 -275 51 -338 51 -45 0 -45 0 -61 48 -51 156 -64 331 -33 452 36 137 137 298 235 372 89 68 154 92 267 96 129 5 211 -14 340 -78 214 -107 365 -278 470 -533 87 -212 128 -445 128 -732 0 -363 -61 -618 -225 -949 -69 -139 -128 -221 -257 -350 -108 -110 -111 -114 -111 -158 0 -84 72 -147 150 -132 17 4 64 36 107 74 190 166 319 365 447 692 252 644 184 1471 -161 1949 -66 93 -202 227 -288 286 -234 159 -503 224 -730 177z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightEarIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2592 5109 c-438 -51 -818 -265 -1099 -621 -231 -291 -381 -671 -450 -1133 -24 -165 -24 -558 0 -735 73 -525 286 -1003 659 -1482 138 -176 397 -485 515 -613 243 -265 465 -423 690 -492 77 -23 105 -27 228 -27 115 0 155 4 224 23 292 78 486 237 616 505 84 171 133 393 123 552 -4 62 -7 69 -41 100 -31 29 -44 34 -86 34 -38 0 -56 -6 -80 -25 -38 -33 -48 -67 -54 -190 -9 -176 -66 -346 -162 -481 -114 -160 -324 -264 -533 -264 -322 0 -591 221 -1199 987 -461 580 -663 1110 -663 1741 0 259 27 465 91 688 188 654 618 1076 1194 1169 114 19 321 19 417 1 292 -56 522 -235 671 -523 103 -199 143 -380 134 -607 -5 -120 -3 -154 9 -177 40 -77 178 -78 218 -1 42 82 45 328 6 510 -40 191 -127 396 -232 551 -74 109 -245 274 -352 340 -240 149 -528 207 -844 170z" />
|
||||
<path d="M2590 4565 c-132 -27 -294 -96 -415 -178 -89 -61 -224 -196 -293 -293 -296 -416 -389 -1108 -231 -1712 53 -202 179 -495 276 -639 105 -158 289 -344 353 -357 78 -15 150 48 150 132 0 44 -3 48 -111 158 -129 129 -188 211 -257 350 -110 222 -174 416 -208 639 -23 150 -23 470 0 620 60 387 201 677 416 852 81 66 243 147 335 169 108 25 253 16 329 -19 123 -57 222 -159 291 -301 85 -174 91 -367 19 -588 -16 -48 -16 -48 -61 -48 -63 0 -242 -27 -338 -51 -295 -75 -498 -203 -607 -384 -68 -113 -83 -171 -83 -335 0 -123 4 -158 24 -230 80 -285 227 -481 452 -604 89 -48 156 -73 289 -107 136 -35 168 -54 325 -199 131 -121 206 -163 293 -164 139 -1 278 127 298 275 9 65 -11 147 -67 280 -48 117 -48 117 -38 180 15 93 15 275 0 339 -30 121 -104 212 -198 244 -102 35 -254 11 -349 -54 -123 -85 -181 -244 -139 -386 13 -45 28 -70 65 -106 41 -42 52 -48 89 -48 112 0 167 105 107 204 -40 66 -10 123 77 147 89 24 115 -27 101 -206 -4 -60 -8 -148 -9 -195 0 -81 3 -91 53 -207 56 -131 63 -170 33 -197 -26 -23 -37 -16 -152 90 -176 163 -264 216 -414 249 -251 55 -409 171 -505 370 -61 127 -83 215 -83 331 -1 81 3 98 31 158 46 97 118 164 245 226 99 47 247 93 350 106 l50 7 -34 -48 c-19 -27 -59 -76 -90 -109 -79 -87 -89 -104 -89 -157 0 -85 74 -140 156 -117 41 11 104 72 199 193 276 353 387 772 290 1099 -114 386 -424 639 -777 635 -46 0 -112 -7 -148 -14z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseEyeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2268 3935 c-429 -52 -860 -222 -1190 -468 -143 -107 -784 -634 -814 -669 -15 -18 -39 -58 -53 -88 -22 -47 -26 -69 -26 -150 0 -81 4 -103 26 -150 14 -31 40 -72 57 -91 38 -43 732 -611 850 -696 296 -212 686 -368 1067 -429 156 -24 491 -30 659 -10 437 51 867 220 1206 474 139 104 776 629 805 663 15 19 40 59 54 89 22 47 26 69 26 150 0 81 -4 103 -26 150 -14 30 -39 70 -54 89 -31 36 -677 568 -805 663 -355 262 -761 421 -1209 473 -151 18 -425 18 -573 0z m498 -384 c409 -84 727 -418 794 -833 18 -108 8 -310 -19 -413 -31 -114 -118 -286 -193 -380 -132 -165 -323 -288 -536 -346 -120 -32 -329 -37 -456 -10 -377 80 -679 367 -778 742 -31 114 -33 376 -4 484 105 398 414 689 811 763 90 17 282 13 381 -7z m-1323 -263 c-90 -134 -168 -336 -197 -507 -20 -121 -20 -321 0 -442 28 -165 100 -354 188 -494 18 -27 36 -56 40 -63 10 -17 -106 55 -202 126 -41 29 -231 182 -423 339 -303 249 -349 289 -349 313 0 24 47 66 365 326 312 256 577 454 606 454 4 0 -8 -24 -28 -52z m2334 -27 c130 -88 826 -650 843 -681 7 -14 8 -26 0 -39 -10 -20 -598 -507 -745 -617 -89 -67 -226 -156 -232 -150 -2 2 14 30 35 62 138 207 212 461 212 724 0 70 -7 169 -16 221 -29 171 -107 373 -197 507 -20 28 -32 52 -27 52 5 0 62 -36 127 -79z" />
|
||||
<path d="M2425 3186 c-128 -29 -264 -110 -349 -208 -25 -29 -64 -88 -86 -133 -93 -185 -93 -386 2 -575 84 -167 252 -297 433 -335 414 -87 798 242 771 660 -3 50 -8 100 -12 112 l-6 21 -51 -26 c-45 -24 -63 -27 -142 -27 -78 0 -97 4 -141 27 -154 81 -219 277 -142 425 l26 51 -21 7 c-48 14 -223 15 -282 1z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseNoseIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2631 4666 c-18 -19 -25 -46 -37 -137 -84 -643 -463 -1393 -1069 -2114 -166 -197 -206 -263 -240 -397 -21 -78 -20 -220 0 -300 53 -203 231 -392 461 -488 109 -45 217 -69 361 -80 206 -16 294 -37 413 -101 160 -85 227 -251 191 -473 -10 -64 -10 -79 3 -104 17 -33 34 -42 76 -42 69 0 100 80 100 260 0 185 -52 313 -172 424 -135 125 -304 184 -593 206 -244 18 -392 79 -532 216 -111 108 -146 180 -151 311 -4 73 0 106 17 160 26 85 58 134 200 303 456 542 798 1122 977 1658 76 226 134 499 134 629 0 44 -4 55 -26 72 -36 28 -85 27 -113 -3z" />
|
||||
<path d="M3390 2450 c-46 -46 -39 -70 60 -205 162 -220 256 -495 230 -678 -23 -159 -100 -276 -264 -400 -20 -16 -42 -40 -47 -55 -15 -38 7 -81 49 -99 31 -13 37 -12 80 8 67 32 229 198 270 276 124 236 115 491 -28 791 -54 114 -157 277 -215 341 -32 35 -43 41 -76 41 -25 0 -46 -7 -59 -20z" />
|
||||
<path d="M2555 1863 c-165 -12 -374 -83 -464 -158 -40 -34 -43 -84 -6 -120 35 -36 65 -31 179 24 131 64 214 84 341 83 113 0 203 -23 295 -74 71 -40 89 -54 182 -144 90 -87 119 -95 170 -44 25 26 29 36 24 62 -14 71 -190 227 -331 294 -114 55 -270 85 -390 77z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightShoulderIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M3983 4693 l-313 -3 0 -623 c0 -383 -4 -639 -11 -666 -40 -174 -185 -305 -368 -334 -61 -9 -73 -8 -96 7 -38 25 -51 58 -35 96 16 38 30 45 99 54 108 14 203 88 235 184 21 60 22 227 2 219 -7 -3 -312 -180 -677 -393 -365 -213 -681 -397 -702 -409 l-39 -23 189 -6 c592 -22 1243 -66 1418 -97 32 -6 71 -17 85 -24 39 -20 320 -455 320 -495 0 -56 -68 -97 -113 -67 -13 8 -84 108 -158 220 -126 195 -135 205 -169 211 -260 41 -1429 105 -1710 92 -205 -9 -305 -35 -469 -122 -382 -202 -712 -597 -870 -1040 -92 -257 -125 -468 -125 -781 1 -191 3 -225 18 -247 l16 -26 734 0 c724 0 734 0 759 21 26 20 27 22 29 161 l3 141 79 -134 c43 -74 90 -146 104 -161 l26 -28 1253 0 1254 0 24 25 25 24 0 2091 0 2091 -25 24 -24 25 -228 -2 c-125 -2 -369 -4 -540 -5z m72 -351 c6 -4 15 -14 21 -22 20 -25 247 -1017 240 -1045 -9 -35 -64 -61 -102 -48 -21 7 -34 21 -46 54 -10 24 -67 258 -128 520 -116 500 -117 512 -73 538 21 12 73 14 88 3z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftShoulderIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M346 4677 l-26 -26 0 -2091 0 -2091 25 -24 24 -25 1254 0 1253 0 26 27 c14 16 61 88 104 162 l79 134 3 -141 c2 -139 3 -141 29 -161 25 -21 35 -21 759 -21 l734 0 16 26 c15 22 17 56 18 247 0 313 -33 524 -125 781 -158 443 -488 838 -870 1040 -164 87 -264 113 -469 122 -281 13 -1450 -51 -1710 -92 -34 -6 -43 -16 -169 -211 -74 -112 -145 -212 -158 -220 -45 -30 -113 11 -113 67 0 40 281 475 320 495 14 7 53 18 85 24 175 31 826 75 1418 97 l189 6 -39 23 c-21 12 -337 196 -702 409 -365 213 -670 390 -677 393 -20 8 -19 -159 2 -219 32 -96 127 -170 235 -184 69 -9 83 -16 99 -54 16 -38 3 -71 -35 -96 -23 -15 -35 -16 -96 -7 -183 29 -328 160 -368 334 -7 27 -11 283 -11 667 l0 622 -193 0 c-107 0 -349 3 -539 7 l-346 6 -26 -26z m805 -338 c47 -25 45 -35 -71 -538 -61 -262 -118 -496 -128 -520 -12 -33 -25 -47 -46 -54 -38 -13 -93 13 -102 48 -7 28 220 1020 240 1045 6 8 16 18 21 22 15 11 63 10 86 -3z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftHandIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2567 4976 c-58 -21 -114 -62 -147 -108 -62 -85 -60 -55 -60 -1038 0 -893 0 -897 -21 -924 -27 -34 -86 -37 -115 -5 -18 20 -19 51 -24 773 -5 750 -5 751 -27 792 -36 67 -70 101 -135 133 -147 74 -319 8 -392 -148 -21 -46 -21 -54 -26 -1164 l-5 -1118 -36 48 c-20 26 -88 118 -152 204 -126 170 -156 200 -236 237 -228 105 -491 -65 -491 -318 0 -38 7 -90 16 -116 9 -26 151 -263 317 -528 211 -337 322 -526 372 -631 120 -251 216 -388 372 -535 199 -186 442 -310 727 -372 86 -19 132 -22 306 -22 225 0 313 13 500 74 374 122 702 413 874 773 106 223 197 591 226 917 8 86 10 434 8 1105 l-3 980 -22 41 c-24 45 -85 104 -134 130 -50 25 -146 30 -207 10 -67 -23 -134 -84 -169 -154 l-28 -57 -5 -623 -5 -624 -24 -19 c-38 -31 -98 -22 -117 18 -5 10 -11 443 -14 963 l-5 945 -30 53 c-55 100 -133 146 -245 147 -65 0 -82 -4 -129 -30 -66 -37 -115 -92 -141 -162 -19 -52 -20 -77 -20 -874 l0 -820 -25 -24 c-15 -16 -36 -25 -55 -25 -19 0 -40 9 -55 25 l-25 24 0 904 c0 780 -2 910 -15 955 -22 73 -99 154 -171 181 -63 24 -150 27 -207 7z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightHandIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2343 4970 c-69 -28 -147 -112 -168 -182 -13 -45 -15 -175 -15 -955 l0 -904 -25 -24 c-32 -33 -78 -33 -110 0 l-25 24 0 820 c0 797 -1 822 -20 874 -26 70 -75 125 -141 162 -47 26 -64 30 -129 30 -112 -1 -190 -47 -245 -147 l-30 -53 -5 -945 c-3 -520 -9 -953 -14 -963 -19 -40 -79 -49 -117 -18 l-24 19 -5 624 -5 623 -28 57 c-35 70 -102 131 -169 154 -61 20 -157 15 -207 -10 -49 -26 -110 -85 -134 -130 l-22 -41 -3 -980 c-2 -671 0 -1019 8 -1105 29 -326 120 -694 226 -917 172 -360 500 -651 874 -773 187 -61 275 -74 500 -74 174 0 220 3 306 22 285 62 528 186 727 372 156 147 252 284 372 535 50 105 161 294 372 631 166 265 308 502 317 528 23 66 21 173 -4 240 -23 61 -85 136 -140 171 -126 80 -304 67 -418 -30 -19 -16 -86 -98 -149 -184 -64 -86 -132 -178 -152 -204 l-36 -48 -5 1118 c-5 1110 -5 1118 -26 1164 -50 109 -156 179 -270 179 -107 0 -202 -60 -257 -164 -22 -41 -22 -42 -27 -792 -5 -722 -6 -753 -24 -773 -26 -29 -86 -29 -112 0 -18 20 -19 54 -24 943 l-5 921 -28 57 c-51 104 -138 159 -257 165 -58 2 -87 -1 -127 -17z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightElbowIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 289 267">
|
||||
<g transform="translate(0,267) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1047 2215 c-225 -318 -341 -538 -462 -880 -55 -155 -96 -254 -115 -280 -70 -93 -92 -172 -71 -251 15 -58 79 -113 198 -172 199 -100 356 -152 603 -202 273 -55 375 -64 730 -65 179 0 346 4 373 8 l47 8 0 524 0 524 -37 7 c-82 15 -501 -12 -525 -35 -4 -3 -17 -50 -28 -104 -17 -83 -64 -245 -75 -255 -2 -2 -21 6 -44 17 l-40 21 15 35 c26 63 64 237 84 390 35 263 85 511 141 698 11 38 19 71 17 73 -5 5 -646 64 -691 64 -30 0 -39 -10 -120 -125z m213 -116 c0 -7 -43 -82 -55 -97 -5 -5 -55 29 -55 37 0 4 13 28 30 54 30 47 30 47 55 31 14 -9 25 -20 25 -25z m-128 -173 c16 -12 9 -29 -83 -212 -55 -109 -104 -200 -109 -202 -13 -5 -50 17 -50 29 0 13 151 316 182 367 21 35 34 39 60 18z m-451 -913 c40 -34 59 -79 59 -139 0 -44 -22 -108 -42 -120 -5 -3 -20 4 -33 16 -22 21 -23 25 -10 44 8 11 15 40 15 63 0 33 -7 50 -30 77 -27 30 -29 37 -19 60 15 32 20 32 60 -1z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftElbowIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 289 267">
|
||||
<g transform="translate(0,267) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1355 2309 c-165 -15 -306 -28 -314 -29 -9 0 -11 -7 -7 -22 69 -242 121 -494 155 -748 21 -157 59 -331 85 -395 l15 -35 -40 -21 c-23 -11 -42 -19 -44 -17 -11 14 -48 134 -70 233 -14 66 -30 123 -33 127 -23 22 -419 48 -515 34 l-47 -7 0 -524 0 -524 48 -8 c26 -4 193 -8 372 -8 355 1 458 10 730 65 247 50 404 102 603 202 154 77 207 137 207 235 0 47 -33 128 -73 176 -26 32 -54 98 -116 277 -121 346 -247 585 -476 907 l-80 113 -50 -1 c-27 -1 -185 -14 -350 -30z m355 -216 c17 -26 30 -50 30 -54 0 -4 -12 -14 -26 -24 -29 -19 -33 -16 -70 53 -18 34 -18 34 6 53 14 10 26 19 28 19 1 0 16 -21 32 -47z m148 -259 c29 -54 74 -143 100 -198 45 -95 46 -99 27 -112 -11 -8 -25 -13 -31 -12 -10 3 -218 398 -211 403 16 12 44 24 52 21 5 -1 33 -47 63 -102z m411 -820 c10 -23 8 -30 -19 -60 -23 -27 -30 -44 -30 -77 0 -23 7 -52 15 -63 13 -19 12 -23 -10 -44 -28 -26 -37 -22 -59 24 -20 42 -21 115 -2 159 12 31 68 87 85 87 4 0 13 -12 20 -26z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftKneeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1076 4893 c-3 -10 -73 -415 -157 -900 l-151 -882 18 -38 c27 -55 135 -132 299 -213 304 -151 621 -235 1190 -315 287 -41 305 -47 305 -110 0 -38 -12 -53 -71 -93 -240 -161 -425 -498 -445 -811 -8 -118 6 -234 47 -378 51 -181 54 -229 41 -549 -11 -257 -11 -290 3 -311 9 -13 26 -28 38 -32 12 -5 311 -18 665 -30 716 -25 678 -27 707 42 18 45 136 519 245 987 142 608 209 829 305 1010 272 511 300 799 102 1058 -50 66 -109 112 -307 239 -597 384 -1221 711 -1885 988 -251 105 -907 355 -932 355 -6 0 -14 -8 -17 -17z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightKneeIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M3835 4844 c-545 -206 -765 -295 -1100 -446 -525 -238 -1030 -513 -1525 -831 -198 -127 -257 -173 -307 -239 -88 -115 -127 -227 -127 -368 0 -188 64 -381 229 -690 96 -181 163 -402 305 -1010 109 -468 227 -942 245 -987 29 -69 -9 -67 707 -42 354 12 653 25 665 30 12 4 29 19 38 32 14 21 14 54 3 311 -13 320 -10 368 41 549 41 144 55 260 47 378 -20 313 -205 650 -445 811 -59 40 -71 55 -71 93 0 63 18 69 305 110 569 80 886 164 1190 315 164 81 272 158 299 213 l18 38 -151 882 c-84 485 -154 890 -157 900 -3 9 -13 17 -22 16 -9 0 -93 -30 -187 -65z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightFootIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1173 5106 c-89 -28 -177 -108 -215 -194 -27 -62 -34 -163 -15 -223 8 -25 182 -356 387 -735 206 -379 391 -723 412 -766 73 -146 101 -322 79 -493 -52 -393 -197 -829 -398 -1191 -96 -173 -156 -258 -258 -359 -180 -181 -402 -294 -655 -335 -198 -31 -298 -99 -368 -247 -24 -52 -27 -70 -27 -163 0 -95 3 -111 28 -162 43 -88 107 -152 195 -195 l76 -38 810 0 c807 0 811 0 899 22 264 68 460 212 622 460 129 196 213 301 355 444 274 275 489 407 990 609 241 97 397 173 496 241 281 195 437 542 421 938 -9 221 -72 400 -194 551 -91 112 -174 179 -463 375 -172 116 -282 196 -369 269 -55 46 -106 48 -130 5 -14 -24 -9 -43 62 -276 42 -137 77 -261 77 -275 0 -43 -36 -78 -80 -78 -62 0 -74 19 -129 200 -100 330 -283 851 -359 1021 -16 36 -52 101 -79 145 -27 43 -78 132 -113 197 -81 149 -114 188 -198 229 l-67 33 -875 2 c-700 1 -883 -1 -917 -11z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftFootIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2133 5102 c-114 -40 -154 -82 -249 -261 -31 -58 -79 -142 -107 -185 -27 -44 -63 -109 -79 -145 -76 -170 -259 -691 -359 -1021 -55 -181 -67 -200 -129 -200 -44 0 -80 35 -80 78 0 14 35 138 77 275 71 233 76 252 62 276 -24 43 -75 41 -130 -5 -87 -73 -197 -153 -369 -269 -289 -196 -372 -263 -463 -375 -122 -151 -185 -330 -194 -551 -16 -396 140 -743 421 -938 99 -68 255 -144 496 -241 501 -202 716 -334 990 -609 142 -143 226 -248 355 -444 162 -248 358 -392 622 -460 88 -22 92 -22 899 -22 l810 0 76 38 c88 43 152 107 195 195 25 51 28 67 28 162 0 93 -3 111 -27 163 -70 148 -170 216 -368 247 -253 41 -475 154 -655 335 -102 101 -162 186 -258 359 -201 362 -346 798 -398 1191 -22 171 6 347 79 493 21 43 206 387 412 766 205 379 379 710 387 735 21 66 12 163 -21 232 -31 66 -107 141 -174 172 l-47 22 -875 2 c-821 2 -878 1 -927 -15z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseRightHipIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1845 4552 c-3 -3 -5 -26 -5 -51 -1 -97 -67 -325 -132 -456 -57 -113 -122 -200 -248 -330 -193 -199 -266 -298 -357 -480 -101 -201 -137 -352 -136 -565 0 -122 4 -160 26 -241 33 -126 77 -235 133 -328 63 -106 58 -101 174 -173 797 -494 1341 -963 1525 -1317 l29 -54 170 6 c94 4 389 7 655 7 l483 0 -7 73 c-14 160 -82 505 -160 818 -22 85 -67 216 -121 350 -217 539 -351 1046 -370 1395 -5 100 -3 133 14 204 59 248 91 534 99 866 l6 262 -799 6 c-439 3 -838 8 -886 10 -48 2 -90 1 -93 -2z" />
|
||||
<path d="M1393 1713 c19 -27 75 -104 125 -173 97 -132 133 -207 171 -355 29 -115 50 -278 59 -452 4 -84 10 -154 12 -156 3 -2 220 -7 483 -11 396 -5 477 -4 477 7 0 22 -70 125 -152 222 -185 221 -506 495 -893 761 -153 106 -303 204 -311 204 -3 0 10 -21 29 -47z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseLeftHipIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2700 4550 c-294 -4 -685 -8 -869 -9 l-334 -1 6 -262 c8 -334 40 -620 99 -868 17 -71 19 -104 14 -204 -19 -349 -153 -856 -370 -1395 -54 -134 -99 -265 -121 -350 -78 -313 -146 -658 -160 -818 l-7 -73 483 0 c266 0 561 -3 655 -7 l170 -6 29 54 c184 354 729 824 1525 1317 116 72 111 67 174 173 56 93 100 202 133 328 22 81 26 119 26 241 1 213 -35 364 -136 565 -91 182 -164 281 -357 480 -172 178 -242 287 -313 491 -36 104 -65 228 -69 304 -3 37 -7 45 -23 46 -11 0 -261 -3 -555 -6z" />
|
||||
<path d="M3660 1702 c-530 -352 -890 -647 -1108 -907 -82 -97 -152 -200 -152 -222 0 -11 81 -12 478 -7 262 4 479 9 482 11 2 2 8 72 12 156 9 174 30 337 59 452 38 148 74 223 171 355 151 207 160 220 151 219 -4 0 -46 -26 -93 -57z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseFaceMapIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 512 512">
|
||||
<g transform="translate(0,512) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M2390 5110 c-193 -23 -445 -139 -602 -277 -243 -214 -357 -420 -473 -849 -46 -173 -48 -177 -187 -407 -147 -243 -163 -278 -183 -395 -17 -99 -14 -134 16 -194 24 -47 26 -181 3 -259 -9 -30 -40 -94 -70 -144 -72 -120 -94 -188 -101 -307 -7 -118 5 -187 59 -328 76 -198 63 -293 -66 -488 -91 -136 -116 -208 -116 -326 0 -85 26 -209 54 -265 16 -29 15 -30 -29 -82 -56 -66 -118 -191 -146 -294 -27 -99 -40 -444 -19 -470 17 -21 57 -19 70 4 5 11 9 87 9 178 -1 107 4 181 14 229 35 162 122 309 240 402 112 89 154 108 592 263 127 44 279 101 340 125 60 24 112 41 116 36 3 -4 12 -29 19 -56 27 -101 128 -204 261 -266 128 -59 205 -74 399 -74 150 0 187 3 255 22 208 58 357 175 401 316 l17 53 96 -39 c53 -22 235 -86 406 -144 170 -58 341 -122 380 -142 97 -51 229 -183 277 -277 70 -137 81 -187 85 -410 5 -211 12 -245 53 -245 37 0 43 32 37 239 -3 157 -9 212 -26 276 -30 113 -81 212 -163 313 -111 138 -191 317 -223 500 -21 120 -19 185 12 330 22 101 27 152 26 247 -1 149 -18 231 -93 455 -71 214 -83 273 -83 420 1 124 6 157 48 295 51 170 21 312 -114 550 -100 175 -134 276 -166 487 -60 391 -79 436 -267 628 -239 245 -405 333 -703 369 -100 12 -354 13 -455 1z m640 -1104 c19 -59 66 -113 143 -165 83 -56 121 -95 136 -143 8 -26 31 -188 31 -219 0 -3 -25 7 -55 22 -125 63 -262 61 -401 -5 -79 -37 -101 -63 -84 -96 15 -28 31 -25 116 17 99 48 198 56 289 23 93 -34 112 -35 126 -11 12 19 14 16 26 -32 27 -107 80 -196 169 -284 74 -73 84 -86 84 -118 0 -52 -20 -84 -116 -188 -47 -51 -99 -112 -115 -137 -63 -94 -75 -220 -31 -307 l21 -40 -44 -68 c-98 -148 -221 -262 -372 -342 -84 -45 -330 -133 -370 -133 -62 0 -295 98 -411 173 -147 95 -303 270 -384 430 -21 44 -60 143 -85 221 -38 119 -45 149 -40 197 6 74 36 129 115 214 35 39 78 93 94 121 25 46 33 53 66 56 20 3 46 8 57 13 16 7 18 6 8 -6 -7 -7 -13 -32 -13 -55 0 -51 35 -99 80 -109 71 -16 146 63 122 128 -10 25 -9 28 6 22 38 -16 53 -17 67 -5 24 20 17 47 -17 73 -72 54 -177 71 -264 41 -25 -8 -48 -13 -50 -11 -3 2 0 24 5 48 6 24 11 58 11 76 0 29 4 33 43 44 75 20 177 7 261 -35 85 -41 101 -44 116 -16 15 28 -1 53 -52 83 -94 54 -228 76 -330 54 -33 -7 -38 -5 -38 10 0 78 58 150 154 193 93 42 291 106 433 141 70 17 155 44 188 59 79 36 170 104 231 173 l49 56 7 -64 c3 -36 11 -80 18 -99z m-624 -2262 c159 -61 196 -61 361 -1 l122 44 6 -51 c24 -173 123 -343 241 -412 52 -30 54 -46 20 -119 -48 -103 -156 -181 -321 -231 -81 -25 -105 -27 -245 -27 -126 0 -169 4 -231 21 -202 55 -336 166 -364 299 -6 30 -3 33 34 53 126 64 223 227 246 413 4 31 9 57 10 57 2 0 56 -21 121 -46z" />
|
||||
<path d="M3004 3296 c-91 -29 -134 -75 -103 -112 12 -15 34 -15 73 1 16 7 17 6 6 -14 -24 -46 20 -123 78 -134 35 -7 85 14 106 44 19 27 21 81 5 111 -10 19 -9 20 7 12 30 -14 116 -9 152 10 25 13 32 23 32 46 0 23 -5 31 -24 36 -13 3 -33 1 -44 -5 -28 -15 -59 -14 -107 4 -49 17 -126 18 -181 1z" />
|
||||
<path d="M2442 2788 c-20 -20 -13 -46 22 -81 67 -67 175 -67 242 0 35 35 42 61 22 81 -20 20 -50 14 -80 -15 -20 -19 -39 -27 -63 -27 -24 0 -43 8 -63 27 -30 29 -60 35 -80 15z" />
|
||||
<path d="M2300 2456 c-14 -14 -13 -29 4 -51 20 -28 181 -40 421 -32 135 5 135 5 147 32 19 42 -2 52 -133 59 -97 5 -119 3 -129 -9 -16 -19 -34 -19 -49 -1 -14 16 -245 18 -261 2z" />
|
||||
<path d="M2466 2257 c-25 -18 -17 -56 17 -81 41 -30 163 -30 204 0 36 27 42 68 13 84 -17 9 -27 8 -47 -5 -32 -21 -105 -21 -135 -1 -27 19 -29 19 -52 3z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function PoseBodyMapIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase {...props} viewBox="0 0 331 600">
|
||||
<g transform="translate(0,600) scale(0.1,-0.1)" fill="currentColor" stroke="none">
|
||||
<path d="M1532 5814 c-106 -53 -149 -142 -137 -283 3 -47 8 -104 9 -126 1 -22 13 -57 25 -78 19 -30 25 -61 32 -160 12 -156 17 -167 18 -42 l1 100 21 -35 c12 -19 30 -45 41 -57 22 -24 21 -56 -8 -145 -17 -53 -89 -125 -133 -134 -14 -2 -39 -11 -54 -19 -16 -8 -37 -15 -48 -15 -22 0 -88 -31 -132 -62 -77 -54 -106 -198 -147 -728 -6 -74 -16 -160 -22 -190 -13 -67 -189 -657 -217 -728 -33 -82 -72 -252 -65 -275 9 -30 94 -136 146 -183 36 -32 43 -35 46 -21 2 10 -2 25 -9 34 -11 13 -9 19 14 39 32 27 36 48 7 39 -15 -5 -18 -3 -14 9 7 19 -7 21 -25 4 -11 -11 -12 -10 -6 1 6 11 3 13 -14 8 -22 -5 -22 -4 -16 46 4 29 12 63 16 76 14 36 36 10 41 -49 5 -47 23 -84 37 -76 3 2 6 55 6 118 -1 94 -5 122 -24 169 l-23 57 99 173 c122 214 145 266 178 399 45 183 118 430 126 430 4 0 18 -21 31 -47 20 -42 23 -62 23 -168 -1 -119 -1 -121 -38 -193 -67 -130 -126 -306 -154 -462 -22 -123 -26 -175 -26 -345 -1 -238 4 -267 116 -645 72 -245 75 -261 83 -395 4 -77 6 -174 3 -215 -2 -41 -4 -160 -4 -265 0 -211 9 -265 101 -623 39 -152 44 -182 38 -239 -4 -40 -2 -84 6 -117 14 -61 3 -92 -57 -159 -62 -69 -39 -84 128 -86 142 -1 148 3 112 89 -24 57 -28 79 -29 180 -1 72 -8 139 -19 180 -15 57 -16 91 -9 275 10 247 9 350 -1 582 -7 149 -5 180 10 237 15 55 17 86 10 184 -13 210 -15 889 -2 1002 l12 105 63 3 c34 2 62 -1 62 -5 0 -4 6 -80 14 -168 9 -110 11 -207 5 -310 -4 -82 -5 -198 -2 -257 3 -59 0 -190 -7 -292 -12 -169 -11 -190 5 -260 15 -65 17 -99 10 -248 -10 -222 -10 -305 0 -573 7 -198 6 -226 -11 -285 -13 -47 -17 -91 -15 -160 2 -81 -1 -105 -23 -166 -42 -115 -43 -114 107 -113 165 2 187 18 122 90 -51 57 -69 103 -56 142 6 16 11 75 11 131 0 87 6 125 50 296 27 107 59 249 71 315 21 118 22 204 7 560 0 11 4 92 10 180 10 154 13 168 71 360 120 393 137 490 127 735 -11 277 -65 497 -181 745 -33 69 -38 92 -43 171 -5 100 14 188 48 219 16 14 20 7 55 -112 21 -71 53 -191 72 -268 40 -161 68 -226 193 -444 99 -173 98 -168 58 -258 -15 -34 -18 -60 -14 -145 2 -56 8 -104 13 -105 15 -5 33 44 33 86 0 46 23 72 38 44 6 -10 13 -43 17 -74 l7 -56 -37 3 c-27 2 -35 -1 -32 -11 4 -9 -1 -11 -14 -8 -26 7 -24 -6 7 -38 22 -23 24 -30 14 -49 -6 -11 -9 -26 -7 -33 6 -14 128 109 171 172 29 42 30 43 17 104 -7 34 -29 114 -50 177 -56 166 -112 348 -176 568 -56 192 -56 197 -81 465 -41 457 -53 532 -102 602 -29 42 -129 104 -169 105 -12 0 -34 7 -50 15 -15 8 -40 17 -54 19 -44 9 -116 81 -133 134 -29 89 -30 121 -8 145 11 12 29 38 41 57 l21 35 1 -105 c1 -129 5 -121 19 38 8 96 16 134 35 171 13 26 24 63 24 82 0 19 3 72 6 119 5 54 3 101 -4 127 -17 63 -68 124 -130 156 -50 25 -64 27 -166 27 -99 0 -116 -3 -163 -26z" />
|
||||
<path d="M1482 4980 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z" />
|
||||
</g>
|
||||
</IconBase>
|
||||
)
|
||||
}
|
||||
|
||||
export function FemaleBreastIcon(props: IconProps) {
|
||||
return (
|
||||
<IconBase
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
// frontend\src\components\ui\RecorderSettings.tsx
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState, type Dispatch, type ReactNode, type SetStateAction } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState, type Dispatch, type ReactNode, type SetStateAction } from 'react'
|
||||
import Button from './Button'
|
||||
import Card from './Card'
|
||||
import LabeledSwitch from './LabeledSwitch'
|
||||
import Modal from './Modal'
|
||||
import Task from './Task'
|
||||
import TaskList from './TaskList'
|
||||
import type { TaskItem } from './TaskList'
|
||||
import PostgresUrlModal from './PostgresUrlModal'
|
||||
import { CheckIcon, ChevronDownIcon, XMarkIcon } from '@heroicons/react/24/solid'
|
||||
import { ArrowPathIcon, CheckIcon, ChevronDownIcon, XMarkIcon } from '@heroicons/react/24/solid'
|
||||
import { startRegistration } from '@simplewebauthn/browser'
|
||||
|
||||
type TrainingPerformanceMode = 'auto' | 'eco' | 'balanced' | 'performance' | 'custom'
|
||||
@ -95,6 +96,11 @@ type AIServerStatus = {
|
||||
url?: string
|
||||
statusCode?: number
|
||||
error?: string
|
||||
state?: string
|
||||
starting?: boolean
|
||||
restarting?: boolean
|
||||
message?: string
|
||||
stateSince?: string
|
||||
}
|
||||
|
||||
type TrainingJobStatus = {
|
||||
@ -786,6 +792,8 @@ export default function Settings({ onAssetsGenerated }: Props) {
|
||||
const [err, setErr] = useState<string | null>(null)
|
||||
const [diskStatus, setDiskStatus] = useState<DiskStatus | null>(null)
|
||||
const [aiServerStatus, setAiServerStatus] = useState<AIServerStatus | null>(null)
|
||||
const [aiServerRestartModalOpen, setAiServerRestartModalOpen] = useState(false)
|
||||
const [aiServerRestartBusy, setAiServerRestartBusy] = useState(false)
|
||||
const [appLog, setAppLog] = useState<AppLog | null>(null)
|
||||
const [appLogLoading, setAppLogLoading] = useState(false)
|
||||
const [appLogOpen, setAppLogOpen] = useState(false)
|
||||
@ -831,6 +839,44 @@ export default function Settings({ onAssetsGenerated }: Props) {
|
||||
const trainingSettingsLocked = Boolean(trainingJobStatus?.running)
|
||||
const enrichPostworkTemporarilyDisabled =
|
||||
trainingSettingsLocked && !!value.enrichPostworkEnabled
|
||||
const aiServerState = String(aiServerStatus?.state || '').toLowerCase()
|
||||
const aiServerRestarting =
|
||||
aiServerRestartBusy || aiServerStatus?.restarting || aiServerState === 'restarting'
|
||||
const aiServerStarting =
|
||||
!aiServerRestarting && (aiServerStatus?.starting || aiServerState === 'starting')
|
||||
const aiServerBusy = aiServerRestarting || aiServerStarting
|
||||
const aiServerRunning = Boolean(aiServerStatus?.running) && !aiServerBusy
|
||||
const aiServerBadgeLabel =
|
||||
aiServerStatus === null
|
||||
? 'AI prüft…'
|
||||
: aiServerRestarting
|
||||
? 'AI startet neu…'
|
||||
: aiServerStarting
|
||||
? 'AI startet…'
|
||||
: aiServerRunning
|
||||
? 'AI online'
|
||||
: 'AI offline'
|
||||
const aiServerBadgeTitle = [
|
||||
aiServerStatus?.message || '',
|
||||
aiServerStatus?.url ? `URL: ${aiServerStatus.url}` : '',
|
||||
aiServerStatus?.error ? `Fehler: ${aiServerStatus.error}` : '',
|
||||
].filter(Boolean).join('\n') || 'AI Server Status'
|
||||
const aiServerBadgeClass =
|
||||
aiServerStatus === null
|
||||
? 'bg-gray-100 text-gray-700 ring-gray-200 hover:bg-gray-200 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10 dark:hover:bg-white/15'
|
||||
: aiServerBusy
|
||||
? 'bg-blue-50 text-blue-800 ring-blue-200 hover:bg-blue-100 dark:bg-blue-500/15 dark:text-blue-100 dark:ring-blue-400/30 dark:hover:bg-blue-500/20'
|
||||
: aiServerRunning
|
||||
? 'bg-emerald-50 text-emerald-800 ring-emerald-200 hover:bg-emerald-100 dark:bg-emerald-500/15 dark:text-emerald-100 dark:ring-emerald-400/30 dark:hover:bg-emerald-500/20'
|
||||
: 'bg-red-50 text-red-800 ring-red-200 hover:bg-red-100 dark:bg-red-500/15 dark:text-red-100 dark:ring-red-400/30 dark:hover:bg-red-500/20'
|
||||
const aiServerDotClass =
|
||||
aiServerStatus === null
|
||||
? 'bg-gray-400'
|
||||
: aiServerBusy
|
||||
? 'bg-blue-500 animate-pulse'
|
||||
: aiServerRunning
|
||||
? 'bg-emerald-500'
|
||||
: 'bg-red-500'
|
||||
|
||||
const [authStatus, setAuthStatus] = useState<AuthSecurityStatus | null>(null)
|
||||
const [securityBusy, setSecurityBusy] = useState(false)
|
||||
@ -1319,52 +1365,110 @@ export default function Settings({ onAssetsGenerated }: Props) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true
|
||||
const loadAiServerStatus = useCallback(async (opts?: { signal?: AbortSignal }) => {
|
||||
try {
|
||||
const res = await fetch('/api/status/ai-server', {
|
||||
cache: 'no-store',
|
||||
signal: opts?.signal,
|
||||
})
|
||||
const data = await res.json().catch(() => null)
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/status/ai-server', { cache: 'no-store' })
|
||||
const data = await res.json().catch(() => null)
|
||||
|
||||
if (!alive) return
|
||||
|
||||
if (!res.ok || !data) {
|
||||
setAiServerStatus({
|
||||
ok: false,
|
||||
running: false,
|
||||
error: `HTTP ${res.status}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
setAiServerStatus({
|
||||
ok: Boolean(data.ok),
|
||||
running: Boolean(data.running),
|
||||
url: String(data.url || ''),
|
||||
statusCode: Number(data.statusCode || 0),
|
||||
error: String(data.error || ''),
|
||||
})
|
||||
} catch (e) {
|
||||
if (!alive) return
|
||||
if (opts?.signal?.aborted) return
|
||||
|
||||
if (!res.ok || !data) {
|
||||
setAiServerStatus({
|
||||
ok: false,
|
||||
running: false,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
state: 'error',
|
||||
error: `HTTP ${res.status}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
void load()
|
||||
const timer = window.setInterval(load, 5000)
|
||||
setAiServerStatus({
|
||||
ok: Boolean(data.ok),
|
||||
running: Boolean(data.running),
|
||||
url: String(data.url || ''),
|
||||
statusCode: Number(data.statusCode || 0),
|
||||
error: String(data.error || ''),
|
||||
state: String(data.state || ''),
|
||||
starting: Boolean(data.starting),
|
||||
restarting: Boolean(data.restarting),
|
||||
message: String(data.message || ''),
|
||||
stateSince: String(data.stateSince || ''),
|
||||
})
|
||||
} catch (e) {
|
||||
if (opts?.signal?.aborted) return
|
||||
|
||||
return () => {
|
||||
alive = false
|
||||
window.clearInterval(timer)
|
||||
setAiServerStatus({
|
||||
ok: false,
|
||||
running: false,
|
||||
state: 'error',
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
void loadAiServerStatus({ signal: controller.signal })
|
||||
const timer = window.setInterval(() => {
|
||||
void loadAiServerStatus({ signal: controller.signal })
|
||||
}, 5000)
|
||||
|
||||
return () => {
|
||||
controller.abort()
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [loadAiServerStatus])
|
||||
|
||||
async function restartAiServer() {
|
||||
setAiServerRestartBusy(true)
|
||||
setErr(null)
|
||||
setMsg(null)
|
||||
setAiServerStatus((prev) => ({
|
||||
ok: false,
|
||||
running: false,
|
||||
url: prev?.url,
|
||||
statusCode: prev?.statusCode,
|
||||
state: 'restarting',
|
||||
starting: false,
|
||||
restarting: true,
|
||||
message: 'AI Server wird neu gestartet…',
|
||||
}))
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/status/ai-server/restart', {
|
||||
method: 'POST',
|
||||
cache: 'no-store',
|
||||
})
|
||||
const data = await res.json().catch(() => null)
|
||||
|
||||
if (!res.ok) {
|
||||
const text =
|
||||
typeof data?.error === 'string'
|
||||
? data.error
|
||||
: typeof data?.message === 'string'
|
||||
? data.message
|
||||
: `HTTP ${res.status}`
|
||||
throw new Error(text)
|
||||
}
|
||||
|
||||
setAiServerRestartModalOpen(false)
|
||||
setMsg('AI Server wird neu gestartet.')
|
||||
|
||||
window.setTimeout(() => void loadAiServerStatus(), 500)
|
||||
window.setTimeout(() => void loadAiServerStatus(), 1500)
|
||||
window.setTimeout(() => void loadAiServerStatus(), 3500)
|
||||
} catch (e: any) {
|
||||
setErr(e?.message ?? String(e))
|
||||
await loadAiServerStatus()
|
||||
} finally {
|
||||
setAiServerRestartBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAppLog() {
|
||||
setAppLogLoading(true)
|
||||
|
||||
@ -2178,75 +2282,59 @@ export default function Settings({ onAssetsGenerated }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
header={
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
Einstellungen
|
||||
</div>
|
||||
<>
|
||||
<Card
|
||||
header={
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
Einstellungen
|
||||
</div>
|
||||
|
||||
<span
|
||||
className={[
|
||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-bold ring-1',
|
||||
aiServerStatus === null
|
||||
? 'bg-gray-100 text-gray-700 ring-gray-200 dark:bg-white/10 dark:text-gray-200 dark:ring-white/10'
|
||||
: aiServerStatus.running
|
||||
? 'bg-emerald-50 text-emerald-800 ring-emerald-200 dark:bg-emerald-500/15 dark:text-emerald-100 dark:ring-emerald-400/30'
|
||||
: 'bg-red-50 text-red-800 ring-red-200 dark:bg-red-500/15 dark:text-red-100 dark:ring-red-400/30',
|
||||
].join(' ')}
|
||||
title={
|
||||
aiServerStatus === null
|
||||
? 'AI Server Status wird geladen…'
|
||||
: aiServerStatus.running
|
||||
? `AI Server läuft${aiServerStatus.url ? `: ${aiServerStatus.url}` : ''}`
|
||||
: `AI Server offline${aiServerStatus.error ? `: ${aiServerStatus.error}` : ''}`
|
||||
}
|
||||
>
|
||||
<span
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAiServerRestartModalOpen(true)}
|
||||
className={[
|
||||
'h-1.5 w-1.5 rounded-full',
|
||||
aiServerStatus === null
|
||||
? 'bg-gray-400'
|
||||
: aiServerStatus.running
|
||||
? 'bg-emerald-500'
|
||||
: 'bg-red-500',
|
||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-bold ring-1 transition',
|
||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-950',
|
||||
aiServerBadgeClass,
|
||||
].join(' ')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
title={`${aiServerBadgeTitle}\nKlicken zum Neustarten.`}
|
||||
aria-label="AI Server Status anzeigen und Neustart öffnen"
|
||||
>
|
||||
<span
|
||||
className={['h-1.5 w-1.5 rounded-full', aiServerDotClass].join(' ')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{aiServerStatus === null
|
||||
? 'AI prüft…'
|
||||
: aiServerStatus.running
|
||||
? 'AI online'
|
||||
: 'AI offline'}
|
||||
</span>
|
||||
{aiServerBadgeLabel}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs text-gray-600 dark:text-gray-300">
|
||||
Recorder-Konfiguration, Automatisierung und Tasks.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs text-gray-600 dark:text-gray-300">
|
||||
Recorder-Konfiguration, Automatisierung und Tasks.
|
||||
|
||||
{/* Rechts: Alerts + Button */}
|
||||
<div className="flex items-start gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
color={saveButton.color}
|
||||
onClick={save}
|
||||
disabled={saving}
|
||||
className="shrink-0"
|
||||
size="md"
|
||||
isLoading={saveButton.isLoading}
|
||||
leadingIcon={saveButton.icon}
|
||||
>
|
||||
{saveButton.text}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rechts: Alerts + Button */}
|
||||
<div className="flex items-start gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
color={saveButton.color}
|
||||
onClick={save}
|
||||
disabled={saving}
|
||||
className="shrink-0"
|
||||
size="md"
|
||||
isLoading={saveButton.isLoading}
|
||||
leadingIcon={saveButton.icon}
|
||||
>
|
||||
{saveButton.text}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
grayBody
|
||||
>
|
||||
}
|
||||
grayBody
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<TaskList
|
||||
tasks={visibleTasks}
|
||||
@ -3662,6 +3750,113 @@ export default function Settings({ onAssetsGenerated }: Props) {
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
open={aiServerRestartModalOpen}
|
||||
onClose={() => {
|
||||
if (!aiServerRestartBusy) setAiServerRestartModalOpen(false)
|
||||
}}
|
||||
title="AI Server neu starten"
|
||||
width="max-w-lg"
|
||||
footer={
|
||||
<div className="flex w-full flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setAiServerRestartModalOpen(false)}
|
||||
disabled={aiServerRestartBusy}
|
||||
className="sm:min-w-28"
|
||||
>
|
||||
Abbrechen
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
color="indigo"
|
||||
isLoading={aiServerRestartBusy}
|
||||
disabled={aiServerRestarting && !aiServerRestartBusy}
|
||||
onClick={() => void restartAiServer()}
|
||||
leadingIcon={<ArrowPathIcon className="size-4" />}
|
||||
className="sm:min-w-32"
|
||||
>
|
||||
{aiServerRestarting ? 'Neustart läuft…' : 'Neu starten'}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="p-4 sm:p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-blue-50 text-blue-700 ring-1 ring-blue-200 dark:bg-blue-500/15 dark:text-blue-200 dark:ring-blue-400/30">
|
||||
<ArrowPathIcon className={['size-5', aiServerBusy ? 'animate-spin' : ''].join(' ')} />
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
AI Server Prozess neu starten
|
||||
</div>
|
||||
<p className="mt-1 text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||
Der laufende AI Server wird beendet und direkt neu gestartet. Laufende
|
||||
Analysen können dabei kurz fehlschlagen und beim nächsten Versuch weiterlaufen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5 overflow-hidden rounded-xl border border-gray-200 bg-gray-50 dark:border-white/10 dark:bg-white/5">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-gray-200 px-4 py-3 dark:border-white/10">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Aktueller Status
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
|
||||
<span
|
||||
className={['h-2 w-2 rounded-full', aiServerDotClass].join(' ')}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{aiServerBadgeLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 px-4 py-3 text-xs">
|
||||
{aiServerStatus?.url ? (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-500 dark:text-gray-400">URL</div>
|
||||
<div className="mt-0.5 break-all text-gray-700 dark:text-gray-200">
|
||||
{aiServerStatus.url}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{aiServerStatus?.message ? (
|
||||
<div>
|
||||
<div className="font-semibold text-gray-500 dark:text-gray-400">Meldung</div>
|
||||
<div className="mt-0.5 text-gray-700 dark:text-gray-200">
|
||||
{aiServerStatus.message}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!aiServerStatus?.url && !aiServerStatus?.message && !aiServerStatus?.error ? (
|
||||
<div className="text-gray-600 dark:text-gray-300">
|
||||
Der Status wird gerade abgefragt.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{aiServerStatus?.error ? (
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-red-700 dark:border-red-500/30 dark:bg-red-500/10 dark:text-red-200">
|
||||
<div className="font-semibold">Fehler</div>
|
||||
<div className="mt-0.5 break-words">{aiServerStatus.error}</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aiServerRestarting ? (
|
||||
<div className="mt-4 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs font-medium text-blue-800 dark:border-blue-400/30 dark:bg-blue-500/10 dark:text-blue-200">
|
||||
Der Neustart läuft bereits. Die Badge aktualisiert sich automatisch, sobald der AI Server wieder bereit ist.
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user