ksaa 2025.11__py3-none-any.whl → 2025.11b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. kaa/application/{services → core}/feedback_service.py +1 -1
  2. kaa/config/schema.py +0 -2
  3. kaa/main/gr.py +2733 -71
  4. kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  5. kaa/resources/game.db +0 -0
  6. kaa/resources/game_ver.txt +0 -0
  7. kaa/sprites/__pycache__/__init__.cpython-310.pyc +0 -0
  8. kaa/tasks/R.py +128 -136
  9. kaa/tasks/daily/purchase.py +1 -33
  10. kaa/tasks/daily/upgrade_support_card.py +2 -12
  11. kaa/tasks/produce/common.py +0 -1
  12. kaa/tasks/produce/in_purodyuusu.py +7 -21
  13. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/METADATA +21 -21
  14. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/RECORD +275 -299
  15. kaa/__init__.py +0 -0
  16. kaa/application/__init__.py +0 -0
  17. kaa/application/services/__init__.py +0 -0
  18. kaa/application/services/config_service.py +0 -101
  19. kaa/application/services/produce_solution_service.py +0 -83
  20. kaa/application/services/task_service.py +0 -145
  21. kaa/application/ui/__init__.py +0 -0
  22. kaa/application/ui/common.py +0 -123
  23. kaa/application/ui/components/alert.py +0 -251
  24. kaa/application/ui/components/alert.pyi +0 -257
  25. kaa/application/ui/components/categorized_select.pyi +0 -697
  26. kaa/application/ui/facade.py +0 -189
  27. kaa/application/ui/gradio_view.py +0 -135
  28. kaa/application/ui/views/__init__.py +0 -0
  29. kaa/application/ui/views/feedback_view.py +0 -65
  30. kaa/application/ui/views/produce_view.py +0 -251
  31. kaa/application/ui/views/settings_view.py +0 -581
  32. kaa/application/ui/views/status_view.py +0 -171
  33. kaa/application/ui/views/task_view.py +0 -102
  34. kaa/application/ui/views/update_view.py +0 -106
  35. kaa/sprites/41904062-e218-4b28-972a-b5cfcd058d2c.png +0 -0
  36. kaa/sprites/5c49d3b3-656e-4c8c-ae1a-9b0209b9dcc3.png +0 -0
  37. kaa/sprites/ae4742aa-acda-442d-bf73-b3fe7b66e85c.png +0 -0
  38. kaa/util/reactive.py +0 -182
  39. /kaa/application/{services → core}/update_service.py +0 -0
  40. /kaa/sprites/{a32fa779-2fb9-488e-9153-0b99e4352281.png → 03d0acec-15fd-4c21-82ce-122764def185.png} +0 -0
  41. /kaa/sprites/{0a019c0d-26fa-4441-b281-b73060722bc9.png → 03d22cb3-0193-4dcb-8a0d-98b7ca9c2b4c.png} +0 -0
  42. /kaa/sprites/{1d560b06-feac-413d-bf90-07ac045f11fb.png → 0455e229-75c9-44ba-8729-745799746979.png} +0 -0
  43. /kaa/sprites/{98a68736-c4ec-4af1-ac64-40eb48f9c000.png → 049ecbf6-d434-46fa-a42c-a447475269bb.png} +0 -0
  44. /kaa/sprites/{307fcb37-7a90-4a3e-bd3a-64a691aeb5d5.png → 04ea8503-cdd5-4b3e-a33b-63893b92f6ba.png} +0 -0
  45. /kaa/sprites/{2927cceb-29dc-4c35-8614-1df42fc0733b.png → 05251a85-2883-4098-842e-6544391de919.png} +0 -0
  46. /kaa/sprites/{c344b05d-2707-43b0-9e3e-4ec433558dcb.png → 054db45e-1434-4604-91ec-7ac586ca11b5.png} +0 -0
  47. /kaa/sprites/{3fd694ea-8396-4cf3-9ba6-eaf95b61e8c8.png → 06cf74aa-635f-48ab-b965-36981f2063d3.png} +0 -0
  48. /kaa/sprites/{827cec52-d883-46d1-b24b-c11c82b1b459.png → 0814c0fd-6f89-47d3-8089-e8cfde569750.png} +0 -0
  49. /kaa/sprites/{91dbaa9c-7881-4687-bd64-8f25c201d98c.png → 08bde3ff-7554-4bdb-a7f0-193323489d5b.png} +0 -0
  50. /kaa/sprites/{491bd489-7a08-47b4-b1b7-5d0f44cedbca.png → 09222fb0-78a6-4663-8c1f-1d44e881513d.png} +0 -0
  51. /kaa/sprites/{36f11c55-e110-44b7-a800-85f4d7584560.png → 0b436edb-8f6a-429d-87b7-5d4250f651ac.png} +0 -0
  52. /kaa/sprites/{8f5081e4-ed3d-445b-bcf6-2e40c7d08298.png → 0c266b0e-de7a-49c1-9709-b01fb5086f75.png} +0 -0
  53. /kaa/sprites/{0e63e0b7-6874-46e9-86de-8c23e6491f24.png → 0db579bf-d555-4468-8104-c2d2e68d33b1.png} +0 -0
  54. /kaa/sprites/{ccac6d8e-bca2-4ab6-b88b-c0f5ba83df84.png → 114730f6-eddd-4381-b723-aee18cde0d90.png} +0 -0
  55. /kaa/sprites/{4e3a9fd3-86aa-4e14-ae48-46e53a553da2.png → 11870372-dc78-4a80-8320-15bb24da6f25.png} +0 -0
  56. /kaa/sprites/{bdbed86f-553c-458e-9435-d587005b367d.png → 11bc7d35-e58d-4301-a0b0-f557dbd73c7b.png} +0 -0
  57. /kaa/sprites/{546cb160-f99b-4cda-9719-02b38a5393a0.png → 12455dbd-7d70-4435-a3ba-f40654e75c37.png} +0 -0
  58. /kaa/sprites/{3114b2d5-0a96-4eba-9143-b09eabb63612.png → 12f330a6-4eab-4015-b0e3-4e1a35a7b676.png} +0 -0
  59. /kaa/sprites/{58de1cf0-4270-4987-9dc7-3f584f1119d1.png → 14a697d4-0b58-4644-bb2d-8823d2fefcb8.png} +0 -0
  60. /kaa/sprites/{2f7f70ea-f3f0-4d14-8885-1a735ff00545.png → 15605f7a-2206-436c-8a71-011d9f95cf4e.png} +0 -0
  61. /kaa/sprites/{138a527b-2191-4f0f-9e36-1d08bbf6ffb1.png → 15dd89cc-53ed-4c77-a355-62e3bd7a15dd.png} +0 -0
  62. /kaa/sprites/{0343a3b8-8cd6-47af-b956-760dd2c79711.png → 18b13508-c46f-4134-b991-975c0a1b9a74.png} +0 -0
  63. /kaa/sprites/{e82b9645-ee3f-4f63-922e-c1d703344baf.png → 19513e9f-2cbb-4f66-84b7-2ad2faed252d.png} +0 -0
  64. /kaa/sprites/{284a19c3-2c06-48a5-8f78-b4682e8ae19e.png → 1a5c147b-e28e-42ad-893a-7430d8f4fc35.png} +0 -0
  65. /kaa/sprites/{af5a6ae7-a355-492e-86a8-62fa2b06bb54.png → 1b3c84c5-16d1-43a7-8513-719bd7a10450.png} +0 -0
  66. /kaa/sprites/{072b167f-a2f6-4458-815e-a92ca2e6c9eb.png → 1b847790-90e2-44f8-b6d9-4814812006bd.png} +0 -0
  67. /kaa/sprites/{81553259-3b0a-42f7-b7b9-2c6e1dfec6c4.png → 1e57ab88-9962-4024-81df-746529a758f9.png} +0 -0
  68. /kaa/sprites/{78868e34-1c2a-40ee-b08c-c89e6a3aea54.png → 1ef5fa52-b6e1-42d0-947a-ce970eba057d.png} +0 -0
  69. /kaa/sprites/{2dfa926c-83c5-4f40-adfe-ff04ce448264.png → 205e03de-46d9-4983-a2ea-a7f7500d508f.png} +0 -0
  70. /kaa/sprites/{5cb4deb9-2ec5-4452-aba4-390eae2071c7.png → 20bd834e-c99c-4e80-92db-0740e5bbe746.png} +0 -0
  71. /kaa/sprites/{a8ca6a10-ba0a-4416-a27b-3323d38040ab.png → 218a6ab3-93f6-4bb1-9848-8c97ff695945.png} +0 -0
  72. /kaa/sprites/{04c287ed-9ab1-4cd1-af07-8ab61f7f49bc.png → 22071c63-a1bc-4163-8d5a-973b3d579c76.png} +0 -0
  73. /kaa/sprites/{4516d0e4-9280-4f91-ba7d-fc8af83a9996.png → 2460d0c9-50e2-451b-b609-ad466b574395.png} +0 -0
  74. /kaa/sprites/{0899a6e7-6617-4f11-a7a2-9ba377ad6cba.png → 264d021a-b322-4472-8a7b-5f1322aba993.png} +0 -0
  75. /kaa/sprites/{0240342e-b6f3-4ed6-8da5-a64092e6fcde.png → 28335ba3-9a0f-46cd-b66d-047781773264.png} +0 -0
  76. /kaa/sprites/{31b025df-ed25-4bc4-a94a-d05388e9156e.png → 28df544b-72f2-4ed8-8bf3-609ff99633d9.png} +0 -0
  77. /kaa/sprites/{9b91dc78-49e8-41b2-9500-c27cb46023c4.png → 2a82e2d6-0722-433a-87fa-f1752ce0b9b7.png} +0 -0
  78. /kaa/sprites/{97f3fb5a-fce2-4cc5-9da5-f0496e9dd294.png → 2abce6a4-111f-42bb-9255-f98d544108de.png} +0 -0
  79. /kaa/sprites/{0c192ca4-dd10-49b4-b0be-9b1fb4637910.png → 2d8e6464-aceb-41fd-9e78-7523738d5720.png} +0 -0
  80. /kaa/sprites/{14875449-7478-4969-a2d5-91ce34c9d1e4.png → 2f9e2dbb-66a1-4b13-9b9e-d62e10efa6d3.png} +0 -0
  81. /kaa/sprites/{5a337f99-8554-48bf-acb7-364259fbf1b7.png → 307b58f0-20c1-41bd-9eaa-1337fd0e6563.png} +0 -0
  82. /kaa/sprites/{cd601176-0336-41c7-8d3f-31ddf8cf708d.png → 318abb8a-5950-4d22-ac42-77219b5bc1a0.png} +0 -0
  83. /kaa/sprites/{0afb71f5-0752-47af-9ba2-85bdbe4fcbf7.png → 3192a62f-224d-44b1-a0b9-b1fc22de8ec1.png} +0 -0
  84. /kaa/sprites/{4dc5f817-86bc-4433-aeb5-b02c7b63fa8c.png → 31b4c370-696f-4dfc-a918-839495520266.png} +0 -0
  85. /kaa/sprites/{ca258eea-e69a-43c3-ad4c-ac7da0099011.png → 321b7040-87d7-4f87-9b44-ad2972584a11.png} +0 -0
  86. /kaa/sprites/{43d3fe01-27c0-4d3f-a908-aebd00ab219d.png → 330c2207-af32-453b-8db7-889cda83e7cc.png} +0 -0
  87. /kaa/sprites/{861bd6b8-cca7-46b1-9346-83af080e211b.png → 38186efc-850c-4d83-97bc-9417f1494b94.png} +0 -0
  88. /kaa/sprites/{0950d760-6d88-4265-b28c-0790a782a749.png → 38426696-ff13-4f20-9f74-b05855990153.png} +0 -0
  89. /kaa/sprites/{4055dbef-1523-43b7-9183-6b2721b4307b.png → 3a211792-1243-496a-b41e-0e3fd121cd5b.png} +0 -0
  90. /kaa/sprites/{56bc2743-5dd9-4d09-a681-af9d1fec568b.png → 3a4e2d8a-deca-4980-b6c2-37bfc17d2720.png} +0 -0
  91. /kaa/sprites/{3c224540-1571-435a-b8e6-2317e4411438.png → 3a4ec054-2c48-4359-afb9-fa5ea8191d16.png} +0 -0
  92. /kaa/sprites/{9a8bfa87-0e37-4d54-888f-b79f8778d232.png → 3ae72684-1d40-43cf-9508-511c010de64f.png} +0 -0
  93. /kaa/sprites/{16113270-3f35-498a-a29a-52f190d77cc1.png → 3c490171-6810-44f0-b28d-c6b664347a3c.png} +0 -0
  94. /kaa/sprites/{771d1e17-8ef4-417a-b482-26db514e17c6.png → 3cda578a-fa75-4bbd-9cff-be4bc4355a58.png} +0 -0
  95. /kaa/sprites/{b70b958a-9480-4b9b-86a4-5ea099535c36.png → 3e669188-5933-4502-b039-38f4d4649664.png} +0 -0
  96. /kaa/sprites/{0cae32b3-0248-49ad-98dd-d06a89308797.png → 3ea03db7-4de9-45c4-a07b-a9bbc2c63e70.png} +0 -0
  97. /kaa/sprites/{943cde40-f190-40b6-9235-4439fbb4ae50.png → 3ea8e354-2684-46f3-9f5a-43f094717c95.png} +0 -0
  98. /kaa/sprites/{20d01fc0-9a54-4ef5-9983-2bb8414dd94d.png → 3eb9b21b-abbe-49fd-a3a9-6e1c1a6e91d9.png} +0 -0
  99. /kaa/sprites/{0a27a783-0ef8-4f99-8c26-cf89768b5146.png → 3fb84a71-ed15-4989-9598-34e7342f38f5.png} +0 -0
  100. /kaa/sprites/{3658a26d-98d3-462a-abf4-4d8b74aeb134.png → 3ffd2992-fa55-499c-8c3b-193518ebbe07.png} +0 -0
  101. /kaa/sprites/{0d1fd6f0-3a6f-4008-a31a-8189dd09be50.png → 41ff9641-d200-4caa-bcfa-d8389a3438eb.png} +0 -0
  102. /kaa/sprites/{dcbeb7cd-cd57-4daf-b023-ff2c7ef1621a.png → 4370013d-f052-4314-94ff-a342d70754c3.png} +0 -0
  103. /kaa/sprites/{34e50d77-7b95-4bb7-8c62-ad6801dc0e55.png → 455dcf19-63f5-4b0a-99e6-b23d3876d199.png} +0 -0
  104. /kaa/sprites/{5701ecbe-7fed-435c-b409-75f8b0f2df51.png → 45f09e08-53ea-4e08-97dc-70f689bab002.png} +0 -0
  105. /kaa/sprites/{4b88639e-1f17-4e11-90b6-bb122f8cb5ab.png → 4729b509-0f4a-4e96-b186-ec56c680c9e6.png} +0 -0
  106. /kaa/sprites/{20cbf6b8-01db-45fe-a790-dbb7dd639b1d.png → 4eb665ef-7741-40f9-8174-7b6ca66d045b.png} +0 -0
  107. /kaa/sprites/{130cd7fd-767e-4177-9087-71f418f154dd.png → 4f370ff6-61d1-405a-b29e-e6d0b3072194.png} +0 -0
  108. /kaa/sprites/{e4be69f6-6030-4fd8-8a36-fd3938d1734b.png → 4fb1d36c-5fb6-4fe5-9ba3-07e056eb1dfc.png} +0 -0
  109. /kaa/sprites/{45bdb006-bea5-4a1f-bf1a-c3bcb2e99abd.png → 504e4927-bfeb-43ee-aa5c-35cec664a4c8.png} +0 -0
  110. /kaa/sprites/{0f4ac9ae-5e4d-4f3f-b9e0-b76570f2b040.png → 51eb4be2-22fa-4683-82c7-513f5934f7b9.png} +0 -0
  111. /kaa/sprites/{527c3803-1ddd-4f6b-a896-815d3c2e4caa.png → 5247b052-5b45-4e64-b7fc-d566f5d4bc82.png} +0 -0
  112. /kaa/sprites/{a1251011-00a9-402b-82da-1d2744db26cf.png → 545a9032-a676-4d9d-ac74-2b2c9b330d1a.png} +0 -0
  113. /kaa/sprites/{0f8026e8-62a9-49e5-97a6-0d78f33dcfcf.png → 568608bd-a1fa-4faf-ae15-179563a7e84f.png} +0 -0
  114. /kaa/sprites/{1617c180-ce9f-461a-bfdc-390337ecb205.png → 56ac84af-4c85-4b69-b449-98981c1df292.png} +0 -0
  115. /kaa/sprites/{6d302c9d-72cd-49b5-bb11-c39140ffbdd1.png → 57741798-65dd-47eb-b4b1-b3f0e3c3e318.png} +0 -0
  116. /kaa/sprites/{1d92cb8e-49a7-4166-9682-a5807e0c7912.png → 5a49b6d2-2bcb-49bc-a28f-b8bd1441f60a.png} +0 -0
  117. /kaa/sprites/{0f80ac6f-bdf8-4927-8393-c554209f517a.png → 5a677634-99cf-45b5-bc0c-94120872a518.png} +0 -0
  118. /kaa/sprites/{ec0d03db-7c66-46b7-86cc-4acaa6eba1c9.png → 5e2b4bb9-f200-4723-96ac-5f907e28eaaa.png} +0 -0
  119. /kaa/sprites/{791b3367-fb7c-48b5-9a0f-877908e51c4a.png → 5ec51718-1403-4a79-bd85-46e1fe04d8c9.png} +0 -0
  120. /kaa/sprites/{05d7740b-c6ea-453a-b805-7d0a5fe18184.png → 5f2d0b2b-8425-4dd8-8b85-d62f4b75286c.png} +0 -0
  121. /kaa/sprites/{0af981f5-bcc9-48eb-a392-50f79ec5149a.png → 5f4abae0-a1f0-4c8c-9d93-a8525caa9dd0.png} +0 -0
  122. /kaa/sprites/{20ca6d42-c19e-4c3e-b82c-f6642bbe5069.png → 5f6fafd9-43aa-443e-994e-f2688af5e238.png} +0 -0
  123. /kaa/sprites/{6c5698a4-b211-4f38-83a7-51b5f3a36911.png → 6005bf30-fd7c-401e-8efd-050fa97ad7f3.png} +0 -0
  124. /kaa/sprites/{116d3f3a-0255-47ed-b656-c26b41a5cfa8.png → 6069f8bc-2a06-45a4-95ca-1ac9d8fa3b04.png} +0 -0
  125. /kaa/sprites/{02df555c-b19c-4776-8f1b-d1a7e2ed2f82.png → 60896822-de58-4e07-b2d9-86a7f4068de1.png} +0 -0
  126. /kaa/sprites/{bd176e60-3bb2-453f-b2ef-45305b7f21c1.png → 61229c71-303b-4dc9-ba4e-6a14e5ab161e.png} +0 -0
  127. /kaa/sprites/{b406baf0-fa0b-4125-adc6-d370582a6104.png → 622d700e-0fd8-4a92-a286-1e8143c70570.png} +0 -0
  128. /kaa/sprites/{fc86085c-d708-498a-bfa6-92e1a1364c60.png → 62f93327-5946-4a48-aff8-a203483b0562.png} +0 -0
  129. /kaa/sprites/{93f3f6a9-e9d2-45b7-a2b3-1e8b64d10e9b.png → 63b74752-73ce-4020-a7c8-0a71a9f67415.png} +0 -0
  130. /kaa/sprites/{84797a78-97f3-44fa-b3c0-132c914f534a.png → 648d43f6-b251-4d6f-a808-ae7325c91a59.png} +0 -0
  131. /kaa/sprites/{f5e1e7ed-fd35-42c3-97ed-5e9214a50733.png → 64dfeb35-61ad-4e9b-9847-a18530f02584.png} +0 -0
  132. /kaa/sprites/{92f7ef0b-7931-4cd3-afda-c553232eb443.png → 64f8ef99-4766-471b-aeb0-b109704d9615.png} +0 -0
  133. /kaa/sprites/{23922edd-22aa-4514-ba99-65bcd72c2ad1.png → 65f79941-1c78-496a-a67c-c50f4227bd34.png} +0 -0
  134. /kaa/sprites/{9934f05f-ee5f-4131-b08d-aa61d5f3505a.png → 677933e9-1481-4f01-95b4-d55bea51db8c.png} +0 -0
  135. /kaa/sprites/{7c8e37b7-173f-4c03-8f11-bbd47f2a02c2.png → 67edcd91-d2c1-418b-987a-e60263626f56.png} +0 -0
  136. /kaa/sprites/{a4723b5b-2466-4ea4-b7ef-4185bb4dda74.png → 6c032aa7-20e3-4e2e-b1a4-1585141ba22b.png} +0 -0
  137. /kaa/sprites/{1b485aa9-f0c0-4b49-ba75-363a8d6e292c.png → 6f50cd3b-6e46-46c5-99ed-26728d3212ad.png} +0 -0
  138. /kaa/sprites/{06a7097c-fef6-4f2c-bece-26eed414708e.png → 6f6d7af3-8380-439b-9d26-3fbd5a103b49.png} +0 -0
  139. /kaa/sprites/{cea2d5c4-6e79-425e-9e98-7e7dbdee960b.png → 6fadb00b-e32e-4c45-a738-f21b8f48c139.png} +0 -0
  140. /kaa/sprites/{47186b16-f478-4a53-8e49-a132ee4c78db.png → 71913fde-c3db-463d-800d-e1b35fa2c8b4.png} +0 -0
  141. /kaa/sprites/{5588396d-dd38-4ef1-8c37-3041a750b1cc.png → 719297ec-82be-4820-8ecf-8bf6322b81fe.png} +0 -0
  142. /kaa/sprites/{ba1983ce-bf63-4022-9ef3-dcf97030e6c5.png → 71976c1a-5df9-44a5-b4ea-08b5d617d813.png} +0 -0
  143. /kaa/sprites/{0be5a84d-eb1e-4bb3-afcd-923e104190f4.png → 72a8bcd2-3f32-4ee5-8295-eedf441d6a1a.png} +0 -0
  144. /kaa/sprites/{20e1e948-6920-44cd-965e-9e1e37b3fbf2.png → 73b6f3aa-5179-41d6-a82e-22e14aa8cd6a.png} +0 -0
  145. /kaa/sprites/{ba7676fe-4982-4e92-822d-9c1a623420ac.png → 76970b91-7864-4413-bfc7-79c04a48dce7.png} +0 -0
  146. /kaa/sprites/{458b8bbe-9fa5-48d4-907b-66d36baf16a1.png → 7a74b5f5-7a04-4678-8e6e-57b5de127182.png} +0 -0
  147. /kaa/sprites/{45062c9b-a31d-457b-83a2-9b6891384a15.png → 7b4c3a81-8f09-4a00-90ee-d4df3f57e863.png} +0 -0
  148. /kaa/sprites/{31168fe2-8601-4de6-aa40-81e7ce452391.png → 7c4ca388-5167-478a-a32b-0e108edad888.png} +0 -0
  149. /kaa/sprites/{c95e8a90-dd6e-4cca-b74b-85ee90dbd5c1.png → 7c62081d-097a-4514-a69d-69e96201e816.png} +0 -0
  150. /kaa/sprites/{001486b2-f627-4ece-8c93-4dfec47da9b3.png → 7d4709f8-6d0d-493f-819e-74cb0f7c166b.png} +0 -0
  151. /kaa/sprites/{d0d9f650-bb30-4743-81f3-cd7ea8211d5c.png → 7e16af89-bb51-4889-8a7e-25715ff5faa1.png} +0 -0
  152. /kaa/sprites/{9af13381-661d-41cf-b704-e4a9c3691e14.png → 7e8904fd-5863-471f-8e9a-d69d9ca72f8f.png} +0 -0
  153. /kaa/sprites/{7ddaa9db-2287-43ba-8531-065e618ac8ef.png → 7efce7bc-12a5-4a53-9a0a-9fa755649ebd.png} +0 -0
  154. /kaa/sprites/{8b6a1eed-341d-4be1-b0ae-9adc52ed4543.png → 803fb770-6982-4460-8fe0-f9ccd67367c2.png} +0 -0
  155. /kaa/sprites/{3d9a9493-7616-4b2b-867e-44e776d831c0.png → 80800c2e-417d-4118-b726-93aaabfc08bd.png} +0 -0
  156. /kaa/sprites/{4e7f7b91-344c-4fb2-95fe-004831e81b11.png → 81b1b160-2d0f-42ad-924a-9572a54f343a.png} +0 -0
  157. /kaa/sprites/{8e024c75-c872-4e18-bb80-0c5894612815.png → 82200c8c-0916-4601-839a-945f96cc4ebc.png} +0 -0
  158. /kaa/sprites/{acaea032-45b8-4ad8-8b54-ff011750af80.png → 82259f79-2bef-442b-bf16-78cb4f22e3be.png} +0 -0
  159. /kaa/sprites/{46cb87f6-26f7-45f0-b10c-6491f115077e.png → 8299e3e2-cfd5-4380-adce-a46c93b1be4d.png} +0 -0
  160. /kaa/sprites/{9803a197-d873-43cc-9656-d63513e92fad.png → 83c27159-36d2-4ccf-ace7-e5d2169e7e52.png} +0 -0
  161. /kaa/sprites/{16cf9d8b-4f8e-47ce-a63d-fe9bfc3f31e8.png → 861a4faa-2522-41e9-8e20-7bd9180bea5a.png} +0 -0
  162. /kaa/sprites/{f31531b4-79ea-41dc-b23f-e445b818139c.png → 8661bc83-9b0a-4555-8b21-2258b31c7f57.png} +0 -0
  163. /kaa/sprites/{7bb874a3-3fac-4138-83bd-d6461c5793d3.png → 87ed135a-2eb1-4e21-8928-d0307a22ea66.png} +0 -0
  164. /kaa/sprites/{f72e9d65-68e7-4bb6-be44-4b5ed71f0bce.png → 8805fead-f7e2-4905-a42e-05ebba2fbe21.png} +0 -0
  165. /kaa/sprites/{7833fa1e-c842-4efd-a696-c67150a1dfa8.png → 88be2dcc-68ca-4596-9b2c-544a196cab64.png} +0 -0
  166. /kaa/sprites/{c68f1282-4d86-4b1a-b76b-dcf42f3f906b.png → 89437aca-7339-4795-b848-ec529b1e5e8d.png} +0 -0
  167. /kaa/sprites/{c3c495a2-1530-439f-a10e-76ca5d929cce.png → 89841ff0-e834-4a7e-b9d5-00a9490b6b7f.png} +0 -0
  168. /kaa/sprites/{b33bb89c-cabb-4fe0-822f-d911e8b42f82.png → 8a0260f5-b32e-430e-bc98-7b5a77cab6ab.png} +0 -0
  169. /kaa/sprites/{a6269fe5-e719-43ad-b674-ea728e43066b.png → 8bf08bab-7c55-45e1-8433-c2213a323480.png} +0 -0
  170. /kaa/sprites/{f7246b84-e70b-47d3-8166-90f0dc4f2bb6.png → 8c3bc157-022a-4bb1-be74-af1ad254e67f.png} +0 -0
  171. /kaa/sprites/{6b63fca9-a1ec-490e-96e9-fc0beeeb8851.png → 8d09178a-251e-4f74-b023-12233417621b.png} +0 -0
  172. /kaa/sprites/{ce7cec02-4039-4171-87ed-37eccc4ac832.png → 8d416e23-04ef-47bd-8cc8-7f18d1bab736.png} +0 -0
  173. /kaa/sprites/{1dadf765-ddd0-4768-9e91-6d3d08d0ca0d.png → 8f9dd0f0-f9af-4eec-a89e-8f2c92f88a98.png} +0 -0
  174. /kaa/sprites/{a408cdfd-e394-4ada-9f0e-5878e41b8e52.png → 8fbee6fe-9409-407e-8f55-d2d36ab37f6a.png} +0 -0
  175. /kaa/sprites/{5933f6ab-7af4-4731-aac8-d95939a3fbbf.png → 8fdc102b-d8f3-4d16-b8ef-1d804f5aff07.png} +0 -0
  176. /kaa/sprites/{0dbb752b-e60a-459d-80fa-f5b49e8b8e4d.png → 9088f8c0-59f8-41c6-994c-ae283ac94c24.png} +0 -0
  177. /kaa/sprites/{24c2db22-24e2-484e-8c16-8b1c9fe3ed14.png → 922af7d1-75e9-4ac8-a4e7-2dad8bb1ca87.png} +0 -0
  178. /kaa/sprites/{9bc6607d-ce44-467e-a5f4-b3cf30210c13.png → 93e91dd0-0050-48b4-8bf8-c732cd76f0e3.png} +0 -0
  179. /kaa/sprites/{27bcc513-9bab-468a-97b8-ed16f918e2a4.png → 94ed55fe-3ef8-46bc-855a-a7b1dedab801.png} +0 -0
  180. /kaa/sprites/{26683d54-74a9-4e7b-a839-c2e0850d1458.png → 95d715c3-d569-48b5-be7b-c21cd2a160db.png} +0 -0
  181. /kaa/sprites/{1539fcff-8842-478f-98a8-0d1a885d1b43.png → 97a3791d-fa9d-453d-8081-c574fe88b823.png} +0 -0
  182. /kaa/sprites/{d5407f49-fecc-404e-915a-22c9fa16a79a.png → 993a33ac-994b-4eb0-9c1e-b9412d499d67.png} +0 -0
  183. /kaa/sprites/{affc5006-4b67-4490-9fa7-557a7fecb133.png → 99a27ff1-bc1c-48a2-9f9b-0803338d1bd8.png} +0 -0
  184. /kaa/sprites/{7469d458-d308-4469-8bf1-b63e1ddc6a3c.png → 9a9a1f40-d308-4aa5-8d91-60342bc6a4e0.png} +0 -0
  185. /kaa/sprites/{4280fa1d-4223-4fc4-85f9-caf6157cb004.png → 9acd9a59-004f-4e1d-a208-0c293f2702d5.png} +0 -0
  186. /kaa/sprites/{b62f9c30-1faf-47fc-be8f-36db3806c5dc.png → 9f3bfe36-8462-404f-94da-9bc324b1beed.png} +0 -0
  187. /kaa/sprites/{32af48d2-fb5b-4288-930f-eb36edfe2116.png → 9f46f543-d7dc-4836-a210-125d686475f9.png} +0 -0
  188. /kaa/sprites/{dab08e98-9053-4e2a-9e74-cf30298fea22.png → 9f573ac2-9d28-4d92-8e3f-1b78031220dd.png} +0 -0
  189. /kaa/sprites/{f9697f23-07cf-4a14-8b22-d1fc95386c16.png → a2545c5c-b5cc-4fd2-93c1-04c74c17ee16.png} +0 -0
  190. /kaa/sprites/{7def2661-0847-4459-88c6-5270c2e8a32c.png → a2dd125b-9868-4e01-9a46-3b552c2ebdc9.png} +0 -0
  191. /kaa/sprites/{38bb2a90-b2a9-4f85-9025-662e83ab1ed1.png → a34502b4-10bd-42ca-9ada-a9cdb57097dc.png} +0 -0
  192. /kaa/sprites/{8473bc42-1909-4d70-b80d-7eba085c9978.png → a3fe5859-62ba-403e-9367-65db0ad15943.png} +0 -0
  193. /kaa/sprites/{0b83c2e5-ebb4-4a69-8b5f-658774d4c5a6.png → a42ac612-acbd-45cc-88fb-104ff9b7d2d8.png} +0 -0
  194. /kaa/sprites/{38f70a7e-5f7e-4ee6-a7c5-5f8014734719.png → a4c6d967-0251-4b33-acb3-81d24f82d5a4.png} +0 -0
  195. /kaa/sprites/{511232f6-01d6-482a-81a3-2bf79a6e88ed.png → a6ed21f0-4da4-4065-bb59-062a70e132ca.png} +0 -0
  196. /kaa/sprites/{aff3d8ac-d648-434c-9adb-6c1a10aa04b9.png → a7f25aeb-d91b-47e0-ba1a-0c6382380102.png} +0 -0
  197. /kaa/sprites/{459f532f-115c-4527-a897-affa7ddcbd76.png → a888e87e-49fe-405d-b074-3dda30304808.png} +0 -0
  198. /kaa/sprites/{ae5a051e-37f5-4e54-bfc7-64c445ad6fcc.png → a97e2840-1575-4f2c-97df-6a720c644969.png} +0 -0
  199. /kaa/sprites/{cb8d5df6-300b-48e5-ae0b-2b4750c9c092.png → a9ba2741-38da-4448-ab80-f0e5b3c3847f.png} +0 -0
  200. /kaa/sprites/{cda022d1-7898-4006-95c5-b55eceda9d95.png → a9c5aeb0-c0c9-47da-8793-ffe8d172bea0.png} +0 -0
  201. /kaa/sprites/{eab111a6-80bf-45c1-9a74-b44ca76508b7.png → aba47934-f264-49f9-a72c-6ed90b112701.png} +0 -0
  202. /kaa/sprites/{c98d6bc7-45ca-4877-8cd1-6f36d6a86a02.png → abe2757f-84f3-4967-bd05-79c17cb1f32c.png} +0 -0
  203. /kaa/sprites/{a5ba613c-46f4-4442-88e1-fbda4dd65a55.png → acd741da-d4ab-4b76-a6a3-4a2fd25870ef.png} +0 -0
  204. /kaa/sprites/{a88e3da2-98d3-4a64-b7ac-040847028a83.png → ad17ae00-f6ce-4f32-9386-9a8073b5e79a.png} +0 -0
  205. /kaa/sprites/{4059ec2d-cd20-4dd3-8586-e49a4cfa8717.png → ae0ce964-eb8f-45a1-aa4d-f47a5c8c720f.png} +0 -0
  206. /kaa/sprites/{2c5784df-e89b-4b95-a4b9-bac083c0ec08.png → ae106b38-da4f-4b0d-b9c3-541fd58f4de7.png} +0 -0
  207. /kaa/sprites/{e70ce43b-9b3b-4aa0-8b9c-1d5cc4a1d5b0.png → ae73d26b-2d47-4921-a6ec-5bbea32773a3.png} +0 -0
  208. /kaa/sprites/{7d4d06a8-7903-4803-a20b-7f45ffbe9ca7.png → aeab8f9a-fe61-4c2d-a494-ffc19a9cae91.png} +0 -0
  209. /kaa/sprites/{4876c6f3-17a5-4858-b1d1-ebb5619516c5.png → affc87db-2f63-4e4a-855e-6ff608226f1e.png} +0 -0
  210. /kaa/sprites/{3aceddce-09db-422a-8023-e08d41070cc8.png → b02c15b5-04b4-40d9-bb13-267786e8e049.png} +0 -0
  211. /kaa/sprites/{6c82d28f-913c-4444-baa6-2e37c7e24bc6.png → b10281ae-cfaf-4400-b2fb-c8c2b432c8e7.png} +0 -0
  212. /kaa/sprites/{6e603eb6-2336-46d2-ad01-2ba2fc3d4cbd.png → b40bebbf-2800-40fb-b7a2-1c2d5995e747.png} +0 -0
  213. /kaa/sprites/{b5f80604-d7d3-49e0-9934-58f75c393ffb.png → b76add75-2187-428c-9ede-fb43086bacb6.png} +0 -0
  214. /kaa/sprites/{6baaecdd-2817-4ba3-a2da-346ef9ef92fd.png → b77a7e97-dc59-493d-9d76-4ea9c2876978.png} +0 -0
  215. /kaa/sprites/{4a853ac5-24d5-44ac-8ddf-9f501c60a9be.png → b7f62cab-cc24-480e-bf4c-25c43a1e801e.png} +0 -0
  216. /kaa/sprites/{821c86c1-bc35-40bc-95db-f66935a92299.png → b9770cd2-b55f-44dc-965f-f29686fba7da.png} +0 -0
  217. /kaa/sprites/{c295af98-0b43-4f18-bbc3-e59d23055f1b.png → ba8ee2cd-f662-440c-90b3-1e4bfe7bab6b.png} +0 -0
  218. /kaa/sprites/{6a27faad-df3b-49d9-a20f-64ae8e8f9e22.png → bb3990c8-4618-4d96-9365-a4d1213b59f7.png} +0 -0
  219. /kaa/sprites/{e7c00f25-b15a-4403-a201-07b9b168e9f5.png → bbe59f62-c3e7-404c-a313-b50db70de7b5.png} +0 -0
  220. /kaa/sprites/{1472da80-4cbb-41e1-94e8-3ea3defd3b6f.png → bd4cd421-f702-47c0-9f8b-77914973108c.png} +0 -0
  221. /kaa/sprites/{a9f020e9-0e4b-4ddc-b8b8-386009578c15.png → bdb1460e-c5db-494a-a58a-688f72b6bdba.png} +0 -0
  222. /kaa/sprites/{e9fad64b-e4e2-46cb-a820-b2860b38da03.png → bdb72f31-5808-4451-827f-fffc3b616083.png} +0 -0
  223. /kaa/sprites/{8448237d-2db5-49ca-8d55-9fc0e56639a7.png → bf3ff37e-d4fc-42ee-87d6-db1e2040aed5.png} +0 -0
  224. /kaa/sprites/{d8485972-4e67-4190-8d95-60fd3c68b2ce.png → bff753bb-b02e-441b-89e5-14845f18c6f6.png} +0 -0
  225. /kaa/sprites/{bb953e97-7185-4c2c-b177-48d743e90d96.png → c035ac76-39e9-466b-a606-28bc52bc73e8.png} +0 -0
  226. /kaa/sprites/{b93b3c36-79a3-4c1a-944a-20fe6a88c46d.png → c05a600a-d79e-432d-a49f-5b2443d67bd4.png} +0 -0
  227. /kaa/sprites/{4b7426e9-395a-410c-bb99-c6f0a7b81208.png → c0801be3-f07a-4afc-ab0d-7f5d3c4afbd3.png} +0 -0
  228. /kaa/sprites/{b2a04604-15d4-4ff6-86f7-0823e642103f.png → c2100faf-1234-49db-b6d4-5a08a4b08272.png} +0 -0
  229. /kaa/sprites/{011e352f-6e8b-4225-b104-d1b2ccf0761c.png → c4505a31-74c5-4b3d-bfcf-a67c3ef0039e.png} +0 -0
  230. /kaa/sprites/{32de6ba0-859f-4f40-a57e-92bed913e3a7.png → c6b0f9ba-a976-474b-b22d-6fe4391d9373.png} +0 -0
  231. /kaa/sprites/{45af668c-2324-4fe4-83e0-11d65cfaa086.png → c6b6b77b-2ec7-4fb7-8033-f4d061f4e9c8.png} +0 -0
  232. /kaa/sprites/{ba971684-1fe3-47e1-8bdb-d20a0011ea68.png → c76c6ccb-d099-4c3c-9eda-13be33aa48ae.png} +0 -0
  233. /kaa/sprites/{d277a9c3-2b2c-42fc-9fbb-a836413c8cf4.png → c78fb143-18e3-4697-a0c1-6151514f2a7c.png} +0 -0
  234. /kaa/sprites/{7db2d761-ab44-4b3b-85aa-9b9758f97d5d.png → c8c17304-e949-4dfc-b07d-9e7133483e31.png} +0 -0
  235. /kaa/sprites/{aad1ac7d-cfa1-4459-9d46-d39c080bd78e.png → c91874f4-363f-4997-a38e-3e486dfd8043.png} +0 -0
  236. /kaa/sprites/{276c0178-65af-4b69-a3c3-d04253b9ed91.png → c9245201-4d29-4088-a5e0-f4e93d4494b5.png} +0 -0
  237. /kaa/sprites/{b002896d-5093-48e5-9e01-d18bfb240961.png → cb0bbf0f-a25b-4ec4-99bd-f5742270a062.png} +0 -0
  238. /kaa/sprites/{0d2143fa-7e81-4e7a-8a94-c3839719de5b.png → cb15d9a3-78fe-4f03-bd23-90ba6169ddff.png} +0 -0
  239. /kaa/sprites/{7ce3a2d5-efa7-4091-b012-f2870d9df422.png → ccfab3e2-7b6d-4ae3-998b-4e5c1863884a.png} +0 -0
  240. /kaa/sprites/{c05baa1e-02c3-4d02-904f-76086edbb97f.png → cdf310f0-b917-4f92-95a9-893af76e3c3a.png} +0 -0
  241. /kaa/sprites/{89663a2f-c6d3-4bb7-9935-a54d11d0a382.png → cea8f98d-ba68-498f-bd3d-cc10f0d4575b.png} +0 -0
  242. /kaa/sprites/{7156a1b5-cdd7-446e-9caf-d9f6b313ef4d.png → cec36685-85a7-4e84-bbbc-4df70b4f7bac.png} +0 -0
  243. /kaa/sprites/{f35e19ca-7de7-4721-af53-b5f95c5e74fe.png → cf49878d-a1c5-4b9f-9502-1e0d9eaadc49.png} +0 -0
  244. /kaa/sprites/{5d42f16f-3475-4ffb-ac85-3c86fa8fba46.png → d1bdfa3b-34df-488d-9f9b-8c7b04dd4ab3.png} +0 -0
  245. /kaa/sprites/{ef0db31a-fc44-4972-af7e-21c50f0b139c.png → d23a600a-7002-4268-96a4-18dc54928ec6.png} +0 -0
  246. /kaa/sprites/{e5196344-d61a-48ff-bc48-63f8f63c3788.png → d57f792a-f086-4fbe-b4d9-5db629a78d42.png} +0 -0
  247. /kaa/sprites/{16f04ca1-05a3-49c7-88ae-a0d02c1ec2ec.png → d6dd92b3-310f-4f5d-a8ef-b941c3659c0c.png} +0 -0
  248. /kaa/sprites/{c424f4a1-d032-478d-ac82-b1127a429e52.png → d8967770-c64a-4609-a0a5-f46ba81edceb.png} +0 -0
  249. /kaa/sprites/{9d0bf64a-2684-43fa-b9bf-552e60c717b4.png → d8ba1932-978c-451c-a717-d7d2b6db35d9.png} +0 -0
  250. /kaa/sprites/{de047f08-cef2-4b22-846e-fba2a06f8996.png → d91483a2-83fa-4664-8eda-52498c0e77bc.png} +0 -0
  251. /kaa/sprites/{f7e8e937-1eda-4bd3-a3cf-d306cb1973e2.png → d9a636be-a017-4ef5-a70c-c374827008a0.png} +0 -0
  252. /kaa/sprites/{cf302432-c592-4b6f-a6de-53c3e1bef23e.png → dae37bd7-6a34-4eb4-8cd0-3c0c03b4575a.png} +0 -0
  253. /kaa/sprites/{fad8b65b-7d9e-49d8-9f71-6d078aa672ca.png → dc51eccf-9470-495a-9e38-59d4481adbd4.png} +0 -0
  254. /kaa/sprites/{0cdd57aa-9bdd-4c83-9565-de7c229b34e3.png → dcea5aae-322e-4cd4-b218-0eac48690755.png} +0 -0
  255. /kaa/sprites/{c7cad813-2326-4662-9f55-0a15e1dad3dc.png → dcf465c6-ad64-48e7-9c3c-7bc7cfc13a44.png} +0 -0
  256. /kaa/sprites/{9db5de82-8fba-4b69-8faf-ffe7fcea32f8.png → ddb59f04-91a0-4fbd-8921-be13acdab291.png} +0 -0
  257. /kaa/sprites/{f1fa2fc3-c3d4-4040-90f1-7941b98dcee5.png → de81f919-4d01-4a4e-bdc1-ffe6e23a5c22.png} +0 -0
  258. /kaa/sprites/{d73e9e03-b910-42c0-9953-34980bff09f5.png → e05f2c16-926f-48be-bdd0-16182e879394.png} +0 -0
  259. /kaa/sprites/{678073ec-6700-4d94-af41-b392af11201b.png → e2a0c1a6-c0cf-400e-99be-1ed0d35c6ed8.png} +0 -0
  260. /kaa/sprites/{d963e76b-3c25-4bc7-a307-78d97728a819.png → e30922ed-a02f-4ede-9968-1656be5a05b2.png} +0 -0
  261. /kaa/sprites/{e4d0719b-d127-41a0-a250-492f79bf5625.png → e388db01-0870-412d-b8bc-5a512ec997c8.png} +0 -0
  262. /kaa/sprites/{f8babf59-d55e-40c2-a0b7-5d743ab71799.png → e392856d-ac59-4414-8fb8-df93a1a99be4.png} +0 -0
  263. /kaa/sprites/{c71f900b-9689-4800-89a0-e3371d33f47a.png → e57db8c2-c050-43fb-8205-339aeb5875e8.png} +0 -0
  264. /kaa/sprites/{38613745-1cf2-4bf1-9f58-0474b1e46895.png → e687c2ba-2c30-4fc8-804f-fea5815f6765.png} +0 -0
  265. /kaa/sprites/{4b02f8d4-7847-47a5-8f01-7ab5123709e5.png → e6e66204-8a2f-4377-8674-b7d663ff0081.png} +0 -0
  266. /kaa/sprites/{43bc2116-7fe3-4b20-8c8b-3ab2a2b5dd7d.png → e7630ccb-6824-49e3-986f-2a65a2c4a948.png} +0 -0
  267. /kaa/sprites/{b26ac4ff-42a0-45aa-b818-7d7603b52f12.png → e79a2a08-3b58-4ab0-8c2b-769f30cc8591.png} +0 -0
  268. /kaa/sprites/{df11b01f-2ab1-4bfb-831f-bb8dae6a092b.png → ebe22aea-0318-49d8-ab07-ce2eef34265f.png} +0 -0
  269. /kaa/sprites/{bee41ab5-c62e-4a79-a8d0-40f9efbc7e40.png → ec35a0b8-1afa-48ff-ae66-9c1ef966348d.png} +0 -0
  270. /kaa/sprites/{d50d4438-65bc-4849-9a11-7e8b796e1b9d.png → ed6df33a-c7aa-4542-a87b-9cc9cae0465c.png} +0 -0
  271. /kaa/sprites/{ce22506b-7410-4039-94de-dc1bdba8ac00.png → ed854df1-878c-4e0b-8138-9b28d314ede6.png} +0 -0
  272. /kaa/sprites/{e2f64c10-4db0-47c8-b23d-94802493d6fb.png → ed98a341-5dff-4af7-a194-d28232d481a4.png} +0 -0
  273. /kaa/sprites/{fb017237-bac6-4b73-9437-4518e764ef45.png → f0ed1dd3-b227-48c1-8c8c-858dbd84fcc7.png} +0 -0
  274. /kaa/sprites/{aa369159-cc3b-40f2-9fca-bca5b579cf97.png → f1eb4089-8cd1-4830-b00e-40535ddcd116.png} +0 -0
  275. /kaa/sprites/{a31e08c1-d3c3-4a99-a882-4a10de9ba815.png → f237d88e-92ac-4ebf-85e1-80a696b5203a.png} +0 -0
  276. /kaa/sprites/{db25f60d-4772-44f9-85dc-aba4b20cffa2.png → f2f818c9-b0cf-436d-9d3e-6544c5399063.png} +0 -0
  277. /kaa/sprites/{5c735cc5-7801-44e9-a8f1-2d44df4fa919.png → f333118a-81c2-417f-b54f-64dc4e8df613.png} +0 -0
  278. /kaa/sprites/{54d67100-70c7-4a00-afaa-a80771d34893.png → f34e8026-bdfc-47e6-a0ed-49e587ef0f40.png} +0 -0
  279. /kaa/sprites/{ab9e39ae-0856-4254-a6b6-585e82f8bf3b.png → f3a5992b-dabf-4c2b-83e9-4c91c3b7aac8.png} +0 -0
  280. /kaa/sprites/{993f2140-77be-4fe2-8e11-f71c4531bc9a.png → f4195ceb-586a-4a8c-9424-4962239d2120.png} +0 -0
  281. /kaa/sprites/{a408adaa-bfab-4aaf-acf7-08a4a3ebdd4c.png → f485db66-2159-4d31-ba53-0fbb726e7f64.png} +0 -0
  282. /kaa/sprites/{dd6f31d1-510d-4425-a160-958e7e984382.png → f7264cee-fea6-47b5-9f9b-8f3f31c03ea0.png} +0 -0
  283. /kaa/sprites/{eeff3d53-5a40-44c2-9d09-8f246157c72b.png → f7e34403-a53c-4d3f-935b-25b4acb7f7a5.png} +0 -0
  284. /kaa/sprites/{a9868db0-b355-470e-9bbe-033c5efa4b5e.png → f8d8c943-1b36-4391-a7fc-2a49f8966d53.png} +0 -0
  285. /kaa/sprites/{edd2d38d-9071-4094-8931-1a68b6ebe1b8.png → f9157db8-d70b-4d46-9800-5a833ce7eda8.png} +0 -0
  286. /kaa/sprites/{c0478a52-d6c5-4029-848f-e3e22b2de858.png → f9a353b2-958d-4aae-ade4-66bdc6481e35.png} +0 -0
  287. /kaa/sprites/{c54d4b7e-887c-4753-8a9e-6914fc408232.png → fa5b5b92-9c60-4811-a44c-8e897c9632c3.png} +0 -0
  288. /kaa/sprites/{e6f8f2a8-39e3-45e1-8a52-f2dc250bcd95.png → fac7b90a-1174-477b-803b-28798647c307.png} +0 -0
  289. /kaa/sprites/{b00b5890-3743-4064-8311-c5e35b3afbe6.png → fbbb73f9-880e-4c7f-9ec9-276bdab4b394.png} +0 -0
  290. /kaa/sprites/{d14a1c6c-339a-4dfe-9078-950d3518bc3b.png → fce4a481-e71c-4dab-aeac-59ff8b18201b.png} +0 -0
  291. /kaa/sprites/{4c243ed4-9ac2-42ba-89c6-946ffef84120.png → fd8a7a5d-f021-4bfd-83f7-a2dd33325837.png} +0 -0
  292. /kaa/sprites/{9b7eaa4c-2b67-4426-80da-b7538a87ed96.png → fda4b3eb-e31a-448b-8cb3-9126e1ab7deb.png} +0 -0
  293. /kaa/sprites/{8a312cf5-c670-4a52-a03b-fe55e78f8f07.png → fda78372-04b4-4d55-92b1-aedec20d0913.png} +0 -0
  294. /kaa/sprites/{cc727107-139b-4092-bafb-4bc9787bcdfd.png → fe993a91-1a49-488e-b7ff-c1b05b8f2b5f.png} +0 -0
  295. /kaa/sprites/{b5584de5-650e-4fde-b743-ccbd02eab196.png → ff8efb8f-e1a4-4202-a6e1-680eb44f58da.png} +0 -0
  296. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/WHEEL +0 -0
  297. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/entry_points.txt +0 -0
  298. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/licenses/LICENSE +0 -0
  299. {ksaa-2025.11.dist-info → ksaa-2025.11b3.dist-info}/top_level.txt +0 -0
kaa/main/gr.py CHANGED
@@ -1,78 +1,2740 @@
1
- import logging
1
+ import os
2
2
  import traceback
3
+ import zipfile
4
+ import logging
5
+ import copy
6
+ import sys
7
+ import subprocess
8
+ import json
9
+ from functools import partial
10
+ from itertools import chain
11
+ from datetime import datetime, timedelta
12
+ from typing import List, Dict, Tuple, Literal, Generator, Callable, Any, get_args, cast
13
+
14
+ import cv2
3
15
  import gradio as gr
4
16
 
5
- from kaa.main.kaa import Kaa
6
- from kaa.application.ui.facade import KaaFacade
7
- from kaa.application.ui.gradio_view import KaaGradioView
8
-
9
- custom_css = """
10
- .no-padding-dropdown {
11
- padding: 0 !important;
12
- }
13
- .no-padding-dropdown > label {
14
- padding: 0 !important;
15
- }
16
- .no-padding-dropdown > div {
17
- padding: 0 !important;
18
- }
19
- """
20
-
21
- def main(kaa_ins: Kaa, start_immediately: bool = False):
22
- """
23
- Main entry point for the KAA Gradio application.
24
- Initializes the core application, facade, and UI view, then launches the UI.
25
- """
26
- # Configure basic logging
27
- logging.basicConfig(level=logging.INFO, format='[%(asctime)s][%(levelname)s][%(name)s] %(message)s')
28
- logger = logging.getLogger(__name__)
29
-
30
- try:
31
- # 1. Initialize Model/Core Application
32
- # The Kaa instance holds the core application logic and state.
33
- kaa_instance = kaa_ins
34
-
35
- # 2. Initialize Facade
36
- # The Facade is the single point of entry for the UI to interact with the backend.
37
- # It orchestrates the various services.
38
- facade = KaaFacade(kaa_instance)
39
-
40
- # Set log level from config
41
- log_level_str = facade.config_service.get_options().misc.log_level
42
- log_level = logging.DEBUG if log_level_str == 'verbose' else logging.INFO
43
- logging.getLogger().setLevel(log_level)
44
- logger.info(f"Log level set to {log_level_str.upper()}")
45
-
46
-
47
- # 3. Initialize View
48
- # The View is responsible for building and rendering the UI.
49
- # It only interacts with the Facade.
50
- view = KaaGradioView(facade)
51
-
52
- # 4. Create and launch the Gradio UI
53
- blocks = view.create_ui()
54
-
55
- if start_immediately:
56
- facade.start_all_tasks()
57
-
58
- misc_opts = facade.config_service.get_options().misc
59
- logger.info(f"Launching Gradio UI... LAN exposure: {misc_opts.expose_to_lan}")
60
-
61
- blocks.launch(
62
- share=misc_opts.expose_to_lan,
63
- inbrowser=True,
64
- css=custom_css
17
+ from kaa.errors import ProduceSolutionNotFoundError
18
+ from kaa.main import Kaa
19
+ from kaa.db import IdolCard
20
+ from kotonebot.backend.context.context import vars
21
+ from kotonebot.errors import ContextNotInitializedError
22
+ from kotonebot.client.host import Mumu12Host, LeidianHost
23
+ from kotonebot.client.host.mumu12_host import Mumu12V5Host
24
+ from kotonebot.config.manager import load_config, save_config
25
+ from kotonebot.config.base_config import UserConfig, BackendConfig
26
+ from kotonebot.backend.context import task_registry, ContextStackVars
27
+ from kaa.config import (
28
+ BaseConfig, APShopItems, CapsuleToysConfig, ClubRewardConfig, PurchaseConfig, ActivityFundsConfig,
29
+ PresentsConfig, AssignmentConfig, ContestConfig, ProduceConfig,
30
+ MissionRewardConfig, DailyMoneyShopItems, ProduceAction,
31
+ RecommendCardDetectionMode, TraceConfig, StartGameConfig, EndGameConfig, UpgradeSupportCardConfig, MiscConfig,
32
+ IdleModeConfig,
33
+ )
34
+ from kaa.config.produce import ProduceSolution, ProduceSolutionManager, ProduceData
35
+ from kaa.application.adapter.misc_adapter import create_desktop_shortcut
36
+ from kaa.application.core.idle_mode import IdleModeManager
37
+ from kaa.application.core.update_service import UpdateService
38
+ from kaa.application.core.feedback_service import FeedbackService
39
+ from kaa.errors import (
40
+ ProduceSolutionNotFoundError, UpdateServiceError, UpdateFetchListError, FeedbackServiceError
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+ GradioInput = gr.Textbox | gr.Number | gr.Checkbox | gr.Dropdown | gr.Radio | gr.Slider | gr.Tabs | gr.Tab
45
+ ConfigKey = Literal[
46
+ # backend
47
+ 'adb_ip', 'adb_port',
48
+ 'screenshot_method', 'keep_screenshots',
49
+ 'check_emulator', 'emulator_path',
50
+ 'adb_emulator_name', 'emulator_args',
51
+ '_mumu_index', '_mumu12v5_index', '_leidian_index',
52
+ 'mumu_background_mode', 'target_screenshot_interval',
53
+
54
+ # purchase
55
+ 'purchase_enabled',
56
+ 'money_enabled', 'ap_enabled',
57
+ 'ap_items', 'money_items', 'money_refresh',
58
+
59
+ # assignment
60
+ 'assignment_enabled',
61
+ 'mini_live_reassign', 'mini_live_duration',
62
+ 'online_live_reassign', 'online_live_duration',
63
+ 'contest_enabled',
64
+ 'select_which_contestant', 'when_no_set',
65
+
66
+ # produce
67
+ 'produce_enabled', 'selected_solution_id', 'produce_count', 'produce_timeout_cd', 'interrupt_timeout', 'enable_fever_month',
68
+ 'mission_reward_enabled',
69
+
70
+ # club reward
71
+ 'club_reward_enabled',
72
+ 'selected_note',
73
+
74
+ # upgrade support card
75
+ 'upgrade_support_card_enabled',
76
+
77
+ # capsule toys
78
+ 'capsule_toys_enabled', 'friend_capsule_toys_count',
79
+ 'sense_capsule_toys_count', 'logic_capsule_toys_count',
80
+ 'anomaly_capsule_toys_count',
81
+
82
+ # start game
83
+ 'start_game_enabled', 'start_through_kuyo',
84
+ 'game_package_name', 'kuyo_package_name',
85
+ 'disable_gakumas_localify', 'dmm_game_path', 'dmm_bypass',
86
+
87
+ # end game
88
+ 'exit_kaa', 'kill_game', 'kill_dmm',
89
+ 'kill_emulator', 'shutdown', 'hibernate',
90
+ 'restore_gakumas_localify',
91
+
92
+ 'activity_funds',
93
+ 'presents',
94
+ 'mission_reward',
95
+ 'activity_funds_enabled',
96
+ 'presents_enabled',
97
+ 'trace_recommend_card_detection',
98
+
99
+ # misc
100
+ 'check_update', 'auto_install_update', 'expose_to_lan', 'update_channel', 'log_level',
101
+
102
+ # idle
103
+ 'idle_enabled', 'idle_seconds', 'idle_minimize_on_pause',
104
+
105
+ '_selected_backend_index'
106
+
107
+ ]
108
+ CONFIG_KEY_VALUE: tuple[str] = get_args(ConfigKey)
109
+ ConfigSetFunction = Callable[[BaseConfig, Dict[ConfigKey, Any]], None]
110
+ ConfigBuilderReturnValue = Tuple[ConfigSetFunction, Dict[ConfigKey, GradioInput]]
111
+
112
+ class KotoneBotUI:
113
+ def __init__(self, kaa: Kaa) -> None:
114
+ self.is_running: bool = False
115
+ self.single_task_running: bool = False
116
+ self.is_stopping: bool = False # 新增:标记是否正在停止过程中
117
+ self.is_single_task_stopping: bool = False # 新增:标记单个任务是否正在停止
118
+ self._kaa = kaa
119
+ self._load_config()
120
+ self.update_service = UpdateService()
121
+ self.feedback_service = FeedbackService()
122
+ # IdleModeManager 空闲检测
123
+ def safe_get_is_running():
124
+ try:
125
+ return (self.is_running or self.single_task_running) and not self.is_stopping and not self.is_single_task_stopping and not vars.flow.is_paused
126
+ except ContextNotInitializedError:
127
+ return False
128
+
129
+ def safe_get_is_paused():
130
+ try:
131
+ return vars.flow.is_paused
132
+ except ContextNotInitializedError:
133
+ return False
134
+
135
+ self.idle_mgr = IdleModeManager(
136
+ get_is_running=safe_get_is_running,
137
+ get_is_paused=safe_get_is_paused,
138
+ get_config=lambda: self.current_config.options.idle,
139
+ )
140
+ self._setup_kaa()
141
+
142
+ def _setup_kaa(self) -> None:
143
+ from kotonebot.backend.debug.vars import debug, clear_saved
144
+
145
+ self._kaa.initialize()
146
+ if self.current_config.keep_screenshots:
147
+ debug.auto_save_to_folder = 'dumps'
148
+ debug.enabled = True
149
+ clear_saved()
150
+ else:
151
+ debug.auto_save_to_folder = None
152
+ debug.enabled = False
153
+
154
+ def export_dumps(self) -> str:
155
+ """导出 dumps 文件夹为 zip 文件"""
156
+ if not os.path.exists('dumps'):
157
+ return "dumps 文件夹不存在"
158
+
159
+ timestamp = datetime.now().strftime('%y-%m-%d-%H-%M-%S')
160
+ zip_filename = f'dumps-{timestamp}.zip'
161
+
162
+ with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
163
+ for root, dirs, files in os.walk('dumps'):
164
+ for file in files:
165
+ file_path = os.path.join(root, file)
166
+ arcname = os.path.relpath(file_path, 'dumps')
167
+ zipf.write(file_path, arcname)
168
+
169
+ return f"已导出到 {zip_filename}"
170
+
171
+ def export_logs(self) -> str:
172
+ """导出 logs 文件夹为 zip 文件"""
173
+ if not os.path.exists('logs'):
174
+ return "logs 文件夹不存在"
175
+
176
+ timestamp = datetime.now().strftime('%y-%m-%d-%H-%M-%S')
177
+ zip_filename = f'logs-{timestamp}.zip'
178
+
179
+ with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
180
+ for root, dirs, files in os.walk('logs'):
181
+ for file in files:
182
+ file_path = os.path.join(root, file)
183
+ arcname = os.path.relpath(file_path, 'logs')
184
+ zipf.write(file_path, arcname)
185
+
186
+ return f"已导出到 {zip_filename}"
187
+
188
+ def get_button_status(self) -> Tuple[str, bool]:
189
+ """获取按钮状态和交互性"""
190
+ if not hasattr(self, 'run_status'):
191
+ return "启动", True
192
+
193
+ if not self.run_status.running:
194
+ self.is_running = False
195
+ self.is_stopping = False # 重置停止状态
196
+ return "启动", True
197
+
198
+ if self.is_stopping:
199
+ return "停止中...", False # 停止中时禁用按钮
200
+
201
+ return "停止", True
202
+
203
+ def update_task_status(self) -> List[List[str]]:
204
+ status_list: List[List[str]] = []
205
+ if not hasattr(self, 'run_status'):
206
+ for task_name, task in task_registry.items():
207
+ status_list.append([task.name, "等待中"])
208
+ return status_list
209
+
210
+ for task_status in self.run_status.tasks:
211
+ status_text = {
212
+ 'pending': '等待中',
213
+ 'running': '运行中',
214
+ 'finished': '已完成',
215
+ 'error': '出错',
216
+ 'cancelled': '已取消'
217
+ }.get(task_status.status, '未知')
218
+ status_list.append([task_status.task.name, status_text])
219
+ return status_list
220
+
221
+ def toggle_run(self) -> Tuple[str, List[List[str]]]:
222
+ if not self.is_running:
223
+ return self.start_run()
224
+
225
+ # 如果正在停止过程中,忽略重复点击
226
+ if self.is_stopping:
227
+ return "停止中...", self.update_task_status()
228
+
229
+ return self.stop_run()
230
+
231
+ def start_run(self) -> Tuple[str, List[List[str]]]:
232
+ self.is_running = True
233
+ self.run_status = self._kaa.start_all()
234
+ # 通知 Idle 管理器进入运行态
235
+ self.idle_mgr.notify_on_start()
236
+ return "停止", self.update_task_status()
237
+
238
+ def stop_run(self) -> Tuple[str, List[List[str]]]:
239
+ self.is_stopping = True # 设置停止状态
240
+
241
+ # 如果当前处于暂停状态,先恢复再停止
242
+ if vars.flow.is_paused:
243
+ gr.Info("检测到任务暂停,正在恢复后停止...")
244
+ vars.flow.request_resume()
245
+
246
+ self.is_running = False
247
+ if self._kaa:
248
+ self.run_status.interrupt()
249
+ gr.Info("正在停止任务...")
250
+ # 通知 Idle 管理器停止态
251
+ self.idle_mgr.notify_on_stop()
252
+
253
+ return "停止中...", self.update_task_status()
254
+
255
+ def start_single_task(self, task_name: str) -> Tuple[str, str]:
256
+ if not task_name:
257
+ gr.Warning("请先选择一个任务")
258
+ return "执行任务", ""
259
+ task = None
260
+ for name, t in task_registry.items():
261
+ if name == task_name:
262
+ task = t
263
+ break
264
+ if task is None:
265
+ gr.Warning(f"任务 {task_name} 未找到")
266
+ return "执行任务", ""
267
+
268
+ gr.Info(f"任务 {task_name} 开始执行")
269
+ self.single_task_running = True
270
+ self.run_status = self._kaa.start([task])
271
+ return "停止任务", f"正在执行任务: {task_name}"
272
+
273
+ def stop_single_task(self) -> Tuple[str, str]:
274
+ self.is_single_task_stopping = True # 设置单个任务停止状态
275
+
276
+ # 如果当前处于暂停状态,先恢复再停止
277
+ if vars.flow.is_paused:
278
+ gr.Info("检测到任务暂停,正在恢复后停止...")
279
+ vars.flow.request_resume()
280
+
281
+ self.single_task_running = False
282
+ if hasattr(self, 'run_status') and self._kaa:
283
+ self.run_status.interrupt()
284
+ gr.Info("正在停止任务...")
285
+ # 通知 Idle 管理器停止态
286
+ self.idle_mgr.notify_on_stop()
287
+ return "停止中...", "正在停止任务..."
288
+
289
+ def toggle_pause(self) -> str:
290
+ """切换暂停/恢复状态"""
291
+ if vars.flow.is_paused:
292
+ vars.flow.request_resume()
293
+ gr.Info("任务已恢复")
294
+ return "暂停"
295
+ else:
296
+ vars.flow.request_pause()
297
+ gr.Info("任务已暂停")
298
+ return "恢复"
299
+
300
+ def get_pause_button_status(self) -> str:
301
+ """获取暂停按钮的状态"""
302
+ if vars.flow.is_paused:
303
+ return "恢复"
304
+ else:
305
+ return "暂停"
306
+
307
+ def get_pause_button_with_interactive(self) -> gr.Button:
308
+ """获取暂停按钮的状态和交互性"""
309
+ try:
310
+ text = "恢复" if vars.flow.is_paused else "暂停"
311
+ except ContextNotInitializedError:
312
+ # ContextNotInitializedError: Forwarded object vars called before initialization.
313
+ # TODO: vars.flow.is_paused 应该要可以在脚本正式启动前就能访问
314
+ text = '未启动'
315
+ # 如果正在停止过程中,禁用暂停按钮
316
+ interactive = not (self.is_stopping or self.is_single_task_stopping)
317
+ return gr.Button(value=text, interactive=interactive)
318
+
319
+ def reload_config(self) -> bool:
320
+ """
321
+ 重新加载配置并重新初始化相关组件
322
+
323
+ :return: 是否成功重新加载
324
+ """
325
+ try:
326
+ # 重新加载配置文件
327
+ self._load_config()
328
+
329
+ # 重新设置 kaa 的配置
330
+ self._kaa.config_path = "config.json"
331
+ self._kaa.config_type = BaseConfig
332
+
333
+ # 重新初始化 kaa(这会重新创建设备和 Context)
334
+ self._setup_kaa()
335
+
336
+ # 重新加载 Context 中的配置数据
337
+ from kotonebot.backend.context.context import config
338
+ try:
339
+ config.load()
340
+ except ContextNotInitializedError:
341
+ pass
342
+
343
+ logger.info("配置已成功重新加载")
344
+ return True
345
+ except Exception as e:
346
+ logger.error(f"重新加载配置失败:{str(e)}")
347
+ return False
348
+
349
+ def save_settings2(self, return_values: list[ConfigBuilderReturnValue], *args) -> str:
350
+ options = BaseConfig()
351
+ # return_values: (set_func, { 'key': component })
352
+ keys = list(chain.from_iterable([list(data.keys()) for _, data in return_values]))
353
+ # 根据 keys 与 *args 构建 data 字典
354
+ data = dict[ConfigKey, Any]()
355
+ assert len(keys) == len(args), "keys 与 args 长度不一致"
356
+ for key, value in zip(keys, args):
357
+ assert key in CONFIG_KEY_VALUE, f"未知的配置项:{key}"
358
+ key = cast(ConfigKey, key)
359
+ data[key] = value
360
+
361
+ # 先设置options
362
+ for (set_func, _) in return_values:
363
+ set_func(options, data)
364
+
365
+ # 验证规则1:截图方法验证
366
+ screenshot_method = self.current_config.backend.screenshot_impl
367
+ backend_type = self.current_config.backend.type
368
+
369
+ valid_screenshot_methods = {
370
+ 'mumu12': ['adb', 'adb_raw', 'uiautomator2', 'nemu_ipc'],
371
+ 'mumu12v5': ['adb', 'adb_raw', 'uiautomator2', 'nemu_ipc'],
372
+ 'leidian': ['adb', 'adb_raw', 'uiautomator2'],
373
+ 'custom': ['adb', 'adb_raw', 'uiautomator2'],
374
+ 'dmm': ['remote_windows', 'windows']
375
+ }
376
+
377
+ if screenshot_method not in valid_screenshot_methods.get(backend_type, []):
378
+ gr.Warning(f"截图方法 '{screenshot_method}' 不适用于当前选择的模拟器类型,配置未保存。")
379
+ return ""
380
+
381
+ # 验证规则2:若启用培育,那么必须选择培育方案
382
+ if options.produce.enabled and not options.produce.selected_solution_id:
383
+ gr.Warning("启用培育时,必须选择培育方案,配置未保存。")
384
+ return ""
385
+
386
+ # 验证规则3:若启用AP/金币购买,对应的商品不能为空
387
+ if options.purchase.ap_enabled and not options.purchase.ap_items:
388
+ gr.Warning("启用AP购买时,AP商店购买物品不能为空,配置未保存。")
389
+ return ""
390
+
391
+ if options.purchase.money_enabled and not options.purchase.money_items:
392
+ gr.Warning("启用金币购买时,金币商店购买物品不能为空,配置未保存。")
393
+ return ""
394
+
395
+ # 验证通过,保存配置
396
+ self.current_config.options = options
397
+ try:
398
+ save_config(self.config, "config.json")
399
+
400
+ # 尝试热重载配置
401
+ if self.reload_config():
402
+ gr.Success("设置已保存并应用!")
403
+ else:
404
+ gr.Warning("设置已保存,但重新加载失败,请重启程序。")
405
+ return ""
406
+ except Exception as e:
407
+ gr.Warning(f"保存设置失败:{str(e)}")
408
+ return ""
409
+
410
+ def _create_status_tab(self) -> None:
411
+ with gr.Tab("状态"):
412
+ gr.Markdown("## 状态")
413
+
414
+ with gr.Row():
415
+ run_btn = gr.Button("启动", scale=2)
416
+ pause_btn = gr.Button("暂停", scale=1)
417
+
418
+ # 快速设置控制区域
419
+ gr.Markdown("### 快速设置")
420
+
421
+ with gr.Row():
422
+ select_all_btn = gr.Button("全选", scale=1)
423
+ unselect_all_btn = gr.Button("清空", scale=1)
424
+ select_produce_only_btn = gr.Button("只选培育", scale=1)
425
+ unselect_produce_only_btn = gr.Button("只不选培育", scale=1)
426
+
427
+ with gr.Row(elem_classes=["quick-controls-row"]):
428
+ purchase_quick = gr.Checkbox(
429
+ label="商店",
430
+ value=self.current_config.options.purchase.enabled,
431
+ interactive=True,
432
+ elem_classes=["quick-checkbox"]
433
+ )
434
+ assignment_quick = gr.Checkbox(
435
+ label="工作",
436
+ value=self.current_config.options.assignment.enabled,
437
+ interactive=True,
438
+ elem_classes=["quick-checkbox"]
439
+ )
440
+ contest_quick = gr.Checkbox(
441
+ label="竞赛",
442
+ value=self.current_config.options.contest.enabled,
443
+ interactive=True,
444
+ elem_classes=["quick-checkbox"]
445
+ )
446
+ produce_quick = gr.Checkbox(
447
+ label="培育",
448
+ value=self.current_config.options.produce.enabled,
449
+ interactive=True,
450
+ elem_classes=["quick-checkbox"]
451
+ )
452
+ mission_reward_quick = gr.Checkbox(
453
+ label="任务",
454
+ value=self.current_config.options.mission_reward.enabled,
455
+ interactive=True,
456
+ elem_classes=["quick-checkbox"]
457
+ )
458
+ club_reward_quick = gr.Checkbox(
459
+ label="社团",
460
+ value=self.current_config.options.club_reward.enabled,
461
+ interactive=True,
462
+ elem_classes=["quick-checkbox"]
463
+ )
464
+ activity_funds_quick = gr.Checkbox(
465
+ label="活动费",
466
+ value=self.current_config.options.activity_funds.enabled,
467
+ interactive=True,
468
+ elem_classes=["quick-checkbox"]
469
+ )
470
+ presents_quick = gr.Checkbox(
471
+ label="礼物",
472
+ value=self.current_config.options.presents.enabled,
473
+ interactive=True,
474
+ elem_classes=["quick-checkbox"]
475
+ )
476
+ capsule_toys_quick = gr.Checkbox(
477
+ label="扭蛋",
478
+ value=self.current_config.options.capsule_toys.enabled,
479
+ interactive=True,
480
+ elem_classes=["quick-checkbox"]
481
+ )
482
+ upgrade_support_card_quick = gr.Checkbox(
483
+ label="支援卡",
484
+ value=self.current_config.options.upgrade_support_card.enabled,
485
+ interactive=True,
486
+ elem_classes=["quick-checkbox"]
487
+ )
488
+
489
+ def _get_end_action_value() -> str:
490
+ if self.current_config.options.end_game.shutdown:
491
+ return "关机"
492
+ if self.current_config.options.end_game.hibernate:
493
+ return "休眠"
494
+ return "什么都不做"
495
+
496
+ end_action_dropdown = gr.Dropdown(
497
+ label="完成后:",
498
+ choices=["什么都不做", "关机", "休眠"],
499
+ value=_get_end_action_value(),
500
+ interactive=True
501
+ )
502
+
503
+ if self._kaa.upgrade_msg:
504
+ gr.Markdown('### 配置升级报告')
505
+ gr.Markdown(self._kaa.upgrade_msg)
506
+ gr.Markdown('脚本报错或者卡住?前往"反馈"选项卡可以快速导出报告!')
507
+
508
+ # 添加调试模式警告
509
+ if self.current_config.keep_screenshots:
510
+ gr.Markdown(
511
+ '<div style="color: red; font-size: larger;">当前启用了调试功能「保留截图数据」,调试结束后正常使用时建议关闭此选项!</div>',
512
+ elem_classes=["debug-warning"]
513
+ )
514
+
515
+ task_status = gr.Dataframe(
516
+ headers=["任务", "状态"],
517
+ value=self.update_task_status(),
518
+ label="任务状态"
519
+ )
520
+
521
+ # MARK: 状态 - 监听函数
522
+
523
+ def on_run_click(evt: gr.EventData) -> Tuple[gr.Button, List[List[str]]]:
524
+ result = self.toggle_run()
525
+ # 如果正在停止,禁用按钮
526
+ interactive = not self.is_stopping
527
+ button = gr.Button(value=result[0], interactive=interactive)
528
+ return button, result[1]
529
+
530
+ def on_pause_click(evt: gr.EventData) -> str:
531
+ return self.toggle_pause()
532
+
533
+ # 快速设置控制的事件处理函数
534
+ def save_quick_setting(success_msg: str, failed_msg: str):
535
+ """保存快速设置并立即应用"""
536
+ try:
537
+ # 保存配置
538
+ save_config(self.config, "config.json")
539
+
540
+ # 尝试热重载配置
541
+ if self.reload_config():
542
+ gr.Success(success_msg)
543
+ else:
544
+ gr.Warning(failed_msg)
545
+ except Exception as e:
546
+ gr.Error(f"✗ 保存失败:{str(e)}")
547
+
548
+ def update_and_save_quick_setting(field_name: str, value: bool, display_name: str):
549
+ """更新字段,并保存快速设置并立即应用"""
550
+
551
+ # 更新配置
552
+ if field_name == 'purchase':
553
+ self.current_config.options.purchase.enabled = value
554
+ elif field_name == 'assignment':
555
+ self.current_config.options.assignment.enabled = value
556
+ elif field_name == 'contest':
557
+ self.current_config.options.contest.enabled = value
558
+ elif field_name == 'produce':
559
+ self.current_config.options.produce.enabled = value
560
+ elif field_name == 'mission_reward':
561
+ self.current_config.options.mission_reward.enabled = value
562
+ elif field_name == 'club_reward':
563
+ self.current_config.options.club_reward.enabled = value
564
+ elif field_name == 'activity_funds':
565
+ self.current_config.options.activity_funds.enabled = value
566
+ elif field_name == 'presents':
567
+ self.current_config.options.presents.enabled = value
568
+ elif field_name == 'capsule_toys':
569
+ self.current_config.options.capsule_toys.enabled = value
570
+ elif field_name == 'upgrade_support_card':
571
+ self.current_config.options.upgrade_support_card.enabled = value
572
+
573
+ # 保存并重载配置
574
+ save_quick_setting(
575
+ f"✓ {display_name} 已{'启用' if value else '禁用'}",
576
+ f"⚠ {display_name} 已保存,但重新加载失败"
577
+ )
578
+
579
+ def batch_select(default: bool, produce_only: bool, success_msg: str) -> None:
580
+ self.current_config.options.purchase.enabled = default
581
+ self.current_config.options.assignment.enabled = default
582
+ self.current_config.options.contest.enabled = default
583
+ self.current_config.options.produce.enabled = produce_only
584
+ self.current_config.options.mission_reward.enabled = default
585
+ self.current_config.options.club_reward.enabled = default
586
+ self.current_config.options.activity_funds.enabled = default
587
+ self.current_config.options.presents.enabled = default
588
+ self.current_config.options.capsule_toys.enabled = default
589
+ self.current_config.options.upgrade_support_card.enabled = default
590
+
591
+ save_quick_setting(success_msg, f"⚠ 数据已保存,但重新加载失败")
592
+
593
+ # MARK: 状态 - UI回调函数
594
+
595
+ run_btn.click(
596
+ fn=on_run_click,
597
+ outputs=[run_btn, task_status]
598
+ )
599
+
600
+ pause_btn.click(
601
+ fn=on_pause_click,
602
+ outputs=[pause_btn]
603
+ )
604
+
605
+ select_all_btn.click(
606
+ fn=lambda: batch_select(True, True, f"✓ 全选成功"),
607
+ outputs=[]
608
+ )
609
+
610
+ unselect_all_btn.click(
611
+ fn=lambda: batch_select(False, False, f"✓ 清空成功"),
612
+ outputs=[]
613
+ )
614
+
615
+ select_produce_only_btn.click(
616
+ fn=lambda: batch_select(False, True, f"✓ 只选培育成功"),
617
+ outputs=[]
618
+ )
619
+
620
+ unselect_produce_only_btn.click(
621
+ fn=lambda: batch_select(True, False, f"✓ 只不选培育成功"),
622
+ outputs=[]
623
+ )
624
+
625
+ # 绑定快速设置控制的事件
626
+
627
+ # 以下回调函数均由.change修改为.input,这样回调函数就只会在用户操作时触发,避免全选功能导致反复触发
628
+ purchase_quick.input(
629
+ fn=lambda x: update_and_save_quick_setting('purchase', x, '商店'),
630
+ inputs=[purchase_quick]
631
+ )
632
+ assignment_quick.input(
633
+ fn=lambda x: update_and_save_quick_setting('assignment', x, '工作'),
634
+ inputs=[assignment_quick]
635
+ )
636
+ contest_quick.input(
637
+ fn=lambda x: update_and_save_quick_setting('contest', x, '竞赛'),
638
+ inputs=[contest_quick]
639
+ )
640
+ produce_quick.input(
641
+ fn=lambda x: update_and_save_quick_setting('produce', x, '培育'),
642
+ inputs=[produce_quick]
643
+ )
644
+ mission_reward_quick.input(
645
+ fn=lambda x: update_and_save_quick_setting('mission_reward', x, '任务奖励'),
646
+ inputs=[mission_reward_quick]
647
+ )
648
+ club_reward_quick.input(
649
+ fn=lambda x: update_and_save_quick_setting('club_reward', x, '社团奖励'),
650
+ inputs=[club_reward_quick]
651
+ )
652
+ activity_funds_quick.input(
653
+ fn=lambda x: update_and_save_quick_setting('activity_funds', x, '活动费'),
654
+ inputs=[activity_funds_quick]
655
+ )
656
+ presents_quick.input(
657
+ fn=lambda x: update_and_save_quick_setting('presents', x, '礼物'),
658
+ inputs=[presents_quick]
659
+ )
660
+ capsule_toys_quick.input(
661
+ fn=lambda x: update_and_save_quick_setting('capsule_toys', x, '扭蛋'),
662
+ inputs=[capsule_toys_quick]
663
+ )
664
+ upgrade_support_card_quick.input(
665
+ fn=lambda x: update_and_save_quick_setting('upgrade_support_card', x, '支援卡升级'),
666
+ inputs=[upgrade_support_card_quick]
667
+ )
668
+
669
+ # 处理完成后操作下拉框
670
+ def save_quick_end_action(action: str):
671
+ try:
672
+ if action == "关机":
673
+ self.current_config.options.end_game.shutdown = True
674
+ self.current_config.options.end_game.hibernate = False
675
+ elif action == "休眠":
676
+ self.current_config.options.end_game.shutdown = False
677
+ self.current_config.options.end_game.hibernate = True
678
+ else: # 什么都不做
679
+ self.current_config.options.end_game.shutdown = False
680
+ self.current_config.options.end_game.hibernate = False
681
+
682
+ save_config(self.config, "config.json")
683
+
684
+ # 尝试热重载配置
685
+ if self.reload_config():
686
+ gr.Success(f"✓ 完成后操作已设置为 {action}")
687
+ else:
688
+ gr.Warning("⚠ 设置已保存,但重新加载失败")
689
+ except Exception as e:
690
+ gr.Error(f"✗ 保存失败:{str(e)}")
691
+
692
+ end_action_dropdown.change(
693
+ fn=save_quick_end_action,
694
+ inputs=[end_action_dropdown]
695
+ )
696
+
697
+ # 添加定时器,分别更新按钮状态和任务状态
698
+ def update_run_button_status():
699
+ text, interactive = self.get_button_status()
700
+ return gr.Button(value=text, interactive=interactive)
701
+
702
+ def update_quick_checkboxes():
703
+ """更新快速设置区域控件的状态,确保与设置同步"""
704
+ end_action_val = _get_end_action_value()
705
+ return [
706
+ gr.Checkbox(value=self.current_config.options.purchase.enabled),
707
+ gr.Checkbox(value=self.current_config.options.assignment.enabled),
708
+ gr.Checkbox(value=self.current_config.options.contest.enabled),
709
+ gr.Checkbox(value=self.current_config.options.produce.enabled),
710
+ gr.Checkbox(value=self.current_config.options.mission_reward.enabled),
711
+ gr.Checkbox(value=self.current_config.options.club_reward.enabled),
712
+ gr.Checkbox(value=self.current_config.options.activity_funds.enabled),
713
+ gr.Checkbox(value=self.current_config.options.presents.enabled),
714
+ gr.Checkbox(value=self.current_config.options.capsule_toys.enabled),
715
+ gr.Checkbox(value=self.current_config.options.upgrade_support_card.enabled),
716
+ gr.Dropdown(value=end_action_val),
717
+ ]
718
+
719
+ gr.Timer(1.0).tick(
720
+ fn=update_run_button_status,
721
+ outputs=[run_btn]
722
+ )
723
+ gr.Timer(1.0).tick(
724
+ fn=self.get_pause_button_with_interactive,
725
+ outputs=[pause_btn]
726
+ )
727
+ gr.Timer(2.0).tick(
728
+ fn=update_quick_checkboxes,
729
+ outputs=[
730
+ purchase_quick, assignment_quick, contest_quick, produce_quick,
731
+ mission_reward_quick, club_reward_quick, activity_funds_quick, presents_quick,
732
+ capsule_toys_quick, upgrade_support_card_quick, end_action_dropdown
733
+ ]
734
+ )
735
+ gr.Timer(1.0).tick(
736
+ fn=self.update_task_status,
737
+ outputs=[task_status]
738
+ )
739
+
740
+ def _create_task_tab(self) -> None:
741
+ with gr.Tab("任务"):
742
+ gr.Markdown("## 执行任务")
743
+
744
+ # 第一行:统一的停止和暂停按钮
745
+ with gr.Row():
746
+ stop_all_btn = gr.Button("停止任务", variant="stop", scale=1)
747
+ pause_btn = gr.Button("暂停", scale=1)
748
+
749
+ task_result = gr.Markdown("")
750
+
751
+ # 获取所有任务并创建列表布局
752
+ tasks = list(task_registry.values())
753
+ task_buttons = [] # 存储所有任务按钮的引用
754
+
755
+ # 为每个任务创建一行:左侧启动按钮,右侧任务名
756
+ for task in tasks:
757
+ with gr.Row():
758
+ with gr.Column(scale=1, min_width=50):
759
+ task_btn = gr.Button("启动", variant="primary", size="sm")
760
+ task_buttons.append((task_btn, task.name))
761
+ with gr.Column(scale=7):
762
+ gr.Markdown(f"{task.name}")
763
+
764
+ # 事件处理函数
765
+ def start_single_task_by_name(task_name: str):
766
+ """启动指定任务并返回状态信息及所有按钮的更新状态"""
767
+ if self.single_task_running:
768
+ return ["已有任务正在运行"] + [gr.Button(interactive=False) for _ in task_buttons]
769
+
770
+ # 启动任务
771
+ result = self.start_single_task(task_name)
772
+ status_msg = result[1]
773
+
774
+ # 如果成功启动,禁用所有任务按钮
775
+ if self.single_task_running:
776
+ disabled_buttons = [gr.Button(value="运行中", interactive=False) for _ in task_buttons]
777
+ else:
778
+ disabled_buttons = [gr.Button(value="启动", interactive=True) for _ in task_buttons]
779
+
780
+ return [status_msg] + disabled_buttons
781
+
782
+ def stop_all_tasks():
783
+ """停止所有任务并重置按钮状态"""
784
+ if not self.single_task_running:
785
+ return ["没有正在运行的任务"] + [gr.Button(value="启动", interactive=True) for _ in task_buttons]
786
+
787
+ # 停止任务
788
+ result = self.stop_single_task()
789
+ status_msg = result[1]
790
+
791
+ # 如果正在停止中,显示停止中状态
792
+ if self.is_single_task_stopping:
793
+ disabled_buttons = [gr.Button(value="停止中", interactive=False) for _ in task_buttons]
794
+ else:
795
+ disabled_buttons = [gr.Button(value="启动", interactive=True) for _ in task_buttons]
796
+
797
+ return [status_msg] + disabled_buttons
798
+
799
+ def get_task_buttons_status() -> List[gr.Button]:
800
+ """获取所有任务按钮的状态"""
801
+ if not hasattr(self, 'run_status') or not self.run_status.running:
802
+ self.single_task_running = False
803
+ self.is_single_task_stopping = False
804
+ return [gr.Button(value="启动", interactive=True) for _ in task_buttons]
805
+
806
+ if self.is_single_task_stopping:
807
+ return [gr.Button(value="停止中", interactive=False) for _ in task_buttons]
808
+
809
+ if self.single_task_running:
810
+ return [gr.Button(value="运行中", interactive=False) for _ in task_buttons]
811
+
812
+ return [gr.Button(value="启动", interactive=True) for _ in task_buttons]
813
+
814
+ def get_single_task_status() -> str:
815
+ """获取任务状态信息"""
816
+ if not hasattr(self, 'run_status'):
817
+ return ""
818
+
819
+ if not self.run_status.running and self.single_task_running:
820
+ # 任务已结束但状态未更新
821
+ self.single_task_running = False
822
+
823
+ # 检查任务状态
824
+ for task_status in self.run_status.tasks:
825
+ status = task_status.status
826
+ task_name = task_status.task.name
827
+
828
+ if status == 'finished':
829
+ return f"任务 {task_name} 已完成"
830
+ elif status == 'error':
831
+ return f"任务 {task_name} 出错"
832
+ elif status == 'cancelled':
833
+ return f"任务 {task_name} 已取消"
834
+
835
+ return "任务已结束"
836
+
837
+ if self.single_task_running:
838
+ for task_status in self.run_status.tasks:
839
+ if task_status.status == 'running':
840
+ return f"正在执行任务: {task_status.task.name}"
841
+
842
+ return "正在准备执行任务..."
843
+
844
+ return ""
845
+
846
+ def on_pause_click() -> str:
847
+ return self.toggle_pause()
848
+
849
+ # 绑定事件 - 为每个任务按钮绑定点击事件
850
+ def create_task_handler(task_name: str):
851
+ """创建任务处理函数,避免闭包问题"""
852
+ def handler():
853
+ return start_single_task_by_name(task_name)
854
+ return handler
855
+
856
+ for task_btn, task_name in task_buttons:
857
+ task_btn.click(
858
+ fn=create_task_handler(task_name),
859
+ outputs=[task_result] + [btn for btn, _ in task_buttons]
860
+ )
861
+
862
+ # 绑定停止按钮事件
863
+ stop_all_btn.click(
864
+ fn=stop_all_tasks,
865
+ outputs=[task_result] + [btn for btn, _ in task_buttons]
866
+ )
867
+
868
+ # 绑定暂停按钮事件
869
+ pause_btn.click(
870
+ fn=on_pause_click,
871
+ outputs=[pause_btn]
872
+ )
873
+
874
+ # 添加定时器更新按钮状态和任务状态
875
+ gr.Timer(1.0).tick(
876
+ fn=get_task_buttons_status,
877
+ outputs=[btn for btn, _ in task_buttons]
878
+ )
879
+ gr.Timer(1.0).tick(
880
+ fn=self.get_pause_button_with_interactive,
881
+ outputs=[pause_btn]
882
+ )
883
+ gr.Timer(1.0).tick(
884
+ fn=get_single_task_status,
885
+ outputs=[task_result]
886
+ )
887
+
888
+ def _create_emulator_settings(self) -> ConfigBuilderReturnValue:
889
+ gr.Markdown("### 模拟器设置")
890
+ current_tab = 0
891
+
892
+ def _update_emulator_tab_options(impl_value: str, selected_index: int):
893
+ nonlocal current_tab
894
+ current_tab = selected_index
895
+ choices = ['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
896
+ if impl_value not in choices:
897
+ new_value = choices[0]
898
+ else:
899
+ new_value = impl_value
900
+ return new_value
901
+
902
+ with gr.Tabs(selected=self.current_config.backend.type):
903
+ with gr.Tab("MuMu 12 v4.x", id="mumu12") as tab_mumu12:
904
+ gr.Markdown("已选中 MuMu 12 v4.x 模拟器")
905
+ mumu_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
906
+ mumu_instance = gr.Dropdown(
907
+ label="选择多开实例",
908
+ choices=[],
909
+ interactive=True
910
+ )
911
+ mumu_refresh_btn = gr.Button("刷新")
912
+
913
+ def refresh_mumu_instances():
914
+ try:
915
+ instances = Mumu12Host.list()
916
+ is_mumu12 = self.current_config.backend.type == 'mumu12'
917
+ current_id = self.current_config.backend.instance_id if is_mumu12 else None
918
+ choices = [(i.name, i.id) for i in instances]
919
+ return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
920
+ except Exception as e:
921
+ logger.exception('Failed to list installed MuMu12')
922
+ gr.Error("获取 MuMu12 模拟器列表失败,请升级模拟器到最新版本。若问题依旧,前往 QQ 群、QQ 频道或 Github 反馈 bug。")
923
+ return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
924
+
925
+ mumu_refresh_btn.click(
926
+ fn=refresh_mumu_instances,
927
+ outputs=[mumu_instance, mumu_refresh_message]
928
+ )
929
+
930
+ # 如果当前是 MuMu 模拟器且有配置的 instance_id,立即加载实例列表
931
+ if self.current_config.backend.type == 'mumu12' and self.current_config.backend.instance_id:
932
+ try:
933
+ instances = Mumu12Host.list()
934
+ choices = [(i.name, i.id) for i in instances]
935
+ mumu_instance.choices = choices
936
+ mumu_instance.value = self.current_config.backend.instance_id
937
+ mumu_refresh_message.visible = False
938
+ except Exception as e:
939
+ logger.exception('Failed to auto-load MuMu12 instances')
940
+
941
+ mumu_background_mode = gr.Checkbox(
942
+ label="MuMu12 模拟器后台保活模式",
943
+ value=self.current_config.backend.mumu_background_mode,
944
+ info=BackendConfig.model_fields['mumu_background_mode'].description,
945
+ interactive=True
946
+ )
947
+
948
+ with gr.Tab("MuMu 12 v5.x", id="mumu12v5") as tab_mumu12v5:
949
+ gr.Markdown("已选中 MuMu 12 v5.x 模拟器")
950
+ mumu_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
951
+ mumu_instance12v5 = gr.Dropdown(
952
+ label="选择多开实例",
953
+ choices=[],
954
+ interactive=True
955
+ )
956
+ mumu_refresh_btn = gr.Button("刷新")
957
+
958
+ def refresh_mumu12v5_instances():
959
+ try:
960
+ instances = Mumu12V5Host.list()
961
+ is_mumu12v5 = self.current_config.backend.type == 'mumu12v5'
962
+ current_id = self.current_config.backend.instance_id if is_mumu12v5 else None
963
+ choices = [(i.name, i.id) for i in instances]
964
+ return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
965
+ except Exception as e:
966
+ logger.exception('Failed to list installed MuMu12v5')
967
+ gr.Error("获取 MuMu12 模拟器列表失败,请升级模拟器到最新版本。若问题依旧,前往 QQ 群、QQ 频道或 Github 反馈 bug。")
968
+ return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
969
+
970
+ mumu_refresh_btn.click(
971
+ fn=refresh_mumu12v5_instances,
972
+ outputs=[mumu_instance12v5, mumu_refresh_message]
973
+ )
974
+
975
+ # 如果当前是 MuMu 模拟器且有配置的 instance_id,立即加载实例列表
976
+ if self.current_config.backend.type == 'mumu12v5' and self.current_config.backend.instance_id:
977
+ try:
978
+ instances = Mumu12V5Host.list()
979
+ choices = [(i.name, i.id) for i in instances]
980
+ mumu_instance12v5.choices = choices
981
+ mumu_instance12v5.value = self.current_config.backend.instance_id
982
+ mumu_refresh_message.visible = False
983
+ except Exception as e:
984
+ logger.exception('Failed to auto-load MuMu12v5 instances')
985
+
986
+ mumu_background_mode = gr.Checkbox(
987
+ label="MuMu12v5 模拟器后台保活模式",
988
+ value=self.current_config.backend.mumu_background_mode,
989
+ info=BackendConfig.model_fields['mumu_background_mode'].description,
990
+ interactive=True
991
+ )
992
+
993
+ with gr.Tab("雷电", id="leidian") as tab_leidian:
994
+ gr.Markdown("已选中雷电模拟器")
995
+ leidian_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
996
+ leidian_instance = gr.Dropdown(
997
+ label="选择多开实例",
998
+ choices=[],
999
+ interactive=True
1000
+ )
1001
+ leidian_refresh_btn = gr.Button("刷新")
1002
+
1003
+ def refresh_leidian_instances():
1004
+ try:
1005
+ instances = LeidianHost.list()
1006
+ is_leidian = self.current_config.backend.type == 'leidian'
1007
+ current_id = self.current_config.backend.instance_id if is_leidian else None
1008
+ choices = [(i.name, i.id) for i in instances]
1009
+ return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
1010
+ except Exception as e:
1011
+ logger.exception('Failed to list installed Leidian')
1012
+ gr.Error("获取雷电模拟器列表失败,请前往 QQ 群、QQ 频道或 Github 反馈 bug。")
1013
+ return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
1014
+
1015
+ leidian_refresh_btn.click(
1016
+ fn=refresh_leidian_instances,
1017
+ outputs=[leidian_instance, leidian_refresh_message]
1018
+ )
1019
+
1020
+ # 如果当前是雷电模拟器且有配置的 instance_id,立即加载实例列表
1021
+ if self.current_config.backend.type == 'leidian' and self.current_config.backend.instance_id:
1022
+ try:
1023
+ instances = LeidianHost.list()
1024
+ choices = [(i.name, i.id) for i in instances]
1025
+ leidian_instance.choices = choices
1026
+ leidian_instance.value = self.current_config.backend.instance_id
1027
+ leidian_refresh_message.visible = False
1028
+ except Exception as e:
1029
+ logger.exception('Failed to auto-load Leidian instances')
1030
+ # 保持显示刷新提示
1031
+
1032
+ with gr.Tab("自定义", id="custom") as tab_custom:
1033
+ gr.Markdown("已选中自定义模拟器")
1034
+ adb_ip = gr.Textbox(
1035
+ value=self.current_config.backend.adb_ip,
1036
+ label="ADB IP 地址",
1037
+ info=BackendConfig.model_fields['adb_ip'].description,
1038
+ interactive=True
1039
+ )
1040
+ adb_port = gr.Number(
1041
+ value=self.current_config.backend.adb_port,
1042
+ label="ADB 端口",
1043
+ info=BackendConfig.model_fields['adb_port'].description,
1044
+ minimum=1,
1045
+ maximum=65535,
1046
+ step=1,
1047
+ interactive=True
1048
+ )
1049
+ check_emulator = gr.Checkbox(
1050
+ label="检查并启动模拟器",
1051
+ value=self.current_config.backend.check_emulator,
1052
+ info=BackendConfig.model_fields['check_emulator'].description,
1053
+ interactive=True
1054
+ )
1055
+ with gr.Group(visible=self.current_config.backend.check_emulator) as check_emulator_group:
1056
+ emulator_path = gr.Textbox(
1057
+ value=self.current_config.backend.emulator_path,
1058
+ label="模拟器 exe 文件路径",
1059
+ info=BackendConfig.model_fields['emulator_path'].description,
1060
+ interactive=True
1061
+ )
1062
+ adb_emulator_name = gr.Textbox(
1063
+ value=self.current_config.backend.adb_emulator_name,
1064
+ label="ADB 模拟器名称",
1065
+ info=BackendConfig.model_fields['adb_emulator_name'].description,
1066
+ interactive=True
1067
+ )
1068
+ emulator_args = gr.Textbox(
1069
+ value=self.current_config.backend.emulator_args,
1070
+ label="模拟器启动参数",
1071
+ info=BackendConfig.model_fields['emulator_args'].description,
1072
+ interactive=True
1073
+ )
1074
+ check_emulator.change(
1075
+ fn=lambda x: gr.Group(visible=x),
1076
+ inputs=[check_emulator],
1077
+ outputs=[check_emulator_group]
1078
+ )
1079
+
1080
+ with gr.Tab("DMM", id="dmm") as tab_dmm:
1081
+ gr.Markdown("已选中 DMM")
1082
+ gr.Markdown("**暂停快捷键 <kbd>Ctrl</kbd> + <kbd>F4</kbd>,停止快捷键 <kbd>Ctrl</kbd> + <kbd>F3</kbd>**")
1083
+
1084
+ choices = ['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
1085
+ screenshot_impl = gr.Dropdown(
1086
+ choices=choices,
1087
+ value=self.current_config.backend.screenshot_impl,
1088
+ label="截图方法",
1089
+ info=BackendConfig.model_fields['screenshot_impl'].description,
1090
+ interactive=True
1091
+ )
1092
+
1093
+
1094
+ target_screenshot_interval = gr.Number(
1095
+ label="最小截图间隔(秒)",
1096
+ value=self.current_config.backend.target_screenshot_interval,
1097
+ info=BackendConfig.model_fields['target_screenshot_interval'].description,
1098
+ minimum=0,
1099
+ step=0.1,
1100
+ interactive=True
65
1101
  )
66
1102
 
67
- except Exception:
68
- logger.exception("A critical error occurred during application startup.")
69
- # If startup fails, display a minimal error UI to the user.
70
- with gr.Blocks() as error_blocks:
71
- gr.Markdown("# 琴音小助手 启动失败")
72
- gr.Markdown(
73
- "kaa 在启动时遇到严重错误。\n\n"
74
- "请前往 [QQ 群](https://qm.qq.com/q/DFglKikW2s)或 [Github Issues](https://github.com/a-date-na/kotone-auto-assistant/issues) 寻求帮助。"
1103
+ tab_mumu12.select(fn=partial(_update_emulator_tab_options, selected_index=0), inputs=[screenshot_impl], outputs=[screenshot_impl])
1104
+ tab_mumu12v5.select(fn=partial(_update_emulator_tab_options, selected_index=1), inputs=[screenshot_impl], outputs=[screenshot_impl])
1105
+ tab_leidian.select(fn=partial(_update_emulator_tab_options, selected_index=2), inputs=[screenshot_impl], outputs=[screenshot_impl])
1106
+ tab_custom.select(fn=partial(_update_emulator_tab_options, selected_index=3), inputs=[screenshot_impl], outputs=[screenshot_impl])
1107
+ tab_dmm.select(fn=partial(_update_emulator_tab_options, selected_index=4), inputs=[screenshot_impl], outputs=[screenshot_impl])
1108
+
1109
+ # 初值
1110
+ if self.current_config.backend.type == 'mumu12':
1111
+ _update_emulator_tab_options(
1112
+ impl_value=self.current_config.backend.screenshot_impl,
1113
+ selected_index=0
75
1114
  )
76
- gr.Code(traceback.format_exc(), label="Traceback")
1115
+ elif self.current_config.backend.type == 'mumu12v5':
1116
+ _update_emulator_tab_options(
1117
+ impl_value=self.current_config.backend.screenshot_impl,
1118
+ selected_index=1
1119
+ )
1120
+ elif self.current_config.backend.type == 'leidian':
1121
+ _update_emulator_tab_options(
1122
+ impl_value=self.current_config.backend.screenshot_impl,
1123
+ selected_index=2
1124
+ )
1125
+ elif self.current_config.backend.type == 'custom':
1126
+ _update_emulator_tab_options(
1127
+ impl_value=self.current_config.backend.screenshot_impl,
1128
+ selected_index=3
1129
+ )
1130
+ elif self.current_config.backend.type == 'dmm':
1131
+ _update_emulator_tab_options(
1132
+ impl_value=self.current_config.backend.screenshot_impl,
1133
+ selected_index=4
1134
+ )
1135
+ else:
1136
+ gr.Warning(f"未知的模拟器类型:{self.current_config.backend.type}")
1137
+
1138
+ def set_config(_: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1139
+ # current_tab is updated by _update_emulator_tab_options
1140
+ if current_tab == 0: # Mumu
1141
+ self.current_config.backend.type = 'mumu12'
1142
+ self.current_config.backend.instance_id = data['_mumu_index']
1143
+ self.current_config.backend.mumu_background_mode = data['mumu_background_mode']
1144
+ elif current_tab == 1: # Mumu12v5
1145
+ self.current_config.backend.type = 'mumu12v5'
1146
+ self.current_config.backend.instance_id = data['_mumu12v5_index']
1147
+ self.current_config.backend.mumu_background_mode = data['mumu_background_mode']
1148
+ elif current_tab == 2: # Leidian
1149
+ self.current_config.backend.type = 'leidian'
1150
+ self.current_config.backend.instance_id = data['_leidian_index']
1151
+ elif current_tab == 3: # Custom
1152
+ self.current_config.backend.type = 'custom'
1153
+ self.current_config.backend.instance_id = None
1154
+ self.current_config.backend.adb_ip = data['adb_ip']
1155
+ self.current_config.backend.adb_port = data['adb_port']
1156
+ self.current_config.backend.adb_emulator_name = data['adb_emulator_name']
1157
+ self.current_config.backend.check_emulator = data['check_emulator']
1158
+ self.current_config.backend.emulator_path = data['emulator_path']
1159
+ self.current_config.backend.emulator_args = data['emulator_args']
1160
+ elif current_tab == 4: # DMM
1161
+ self.current_config.backend.type = 'dmm'
1162
+ self.current_config.backend.instance_id = None # DMM doesn't use instance_id here
1163
+
1164
+ # Common settings for all backend types
1165
+ self.current_config.backend.screenshot_impl = data['screenshot_method']
1166
+ self.current_config.backend.target_screenshot_interval = data['target_screenshot_interval']
1167
+
1168
+ return set_config, {
1169
+ 'adb_ip': adb_ip,
1170
+ 'adb_port': adb_port,
1171
+ 'screenshot_method': screenshot_impl,
1172
+ 'target_screenshot_interval': target_screenshot_interval,
1173
+ 'check_emulator': check_emulator,
1174
+ 'emulator_path': emulator_path,
1175
+ 'adb_emulator_name': adb_emulator_name,
1176
+ 'emulator_args': emulator_args,
1177
+ '_mumu_index': mumu_instance,
1178
+ '_leidian_index': leidian_instance,
1179
+ '_mumu12v5_index': mumu_instance12v5,
1180
+ 'mumu_background_mode': mumu_background_mode
1181
+ }
1182
+
1183
+ def _create_purchase_settings(self) -> ConfigBuilderReturnValue:
1184
+ with gr.Column():
1185
+ gr.Markdown("### 商店购买设置")
1186
+ purchase_enabled = gr.Checkbox(
1187
+ label="启用商店购买",
1188
+ value=self.current_config.options.purchase.enabled,
1189
+ info=PurchaseConfig.model_fields['enabled'].description
1190
+ )
1191
+ with gr.Group(visible=self.current_config.options.purchase.enabled) as purchase_group:
1192
+ money_enabled = gr.Checkbox(
1193
+ label="启用金币购买",
1194
+ value=self.current_config.options.purchase.money_enabled,
1195
+ info=PurchaseConfig.model_fields['money_enabled'].description
1196
+ )
1197
+
1198
+ # 添加金币商店商品选择
1199
+ money_items = gr.Dropdown(
1200
+ multiselect=True,
1201
+ choices=list(DailyMoneyShopItems.all()),
1202
+ value=self.current_config.options.purchase.money_items,
1203
+ label="金币商店购买物品",
1204
+ info=PurchaseConfig.model_fields['money_items'].description
1205
+ )
1206
+
1207
+ money_refresh = gr.Checkbox(
1208
+ label="每日一次免费刷新金币商店",
1209
+ value=self.current_config.options.purchase.money_refresh,
1210
+ info=PurchaseConfig.model_fields['money_refresh'].description
1211
+ )
1212
+
1213
+ ap_enabled = gr.Checkbox(
1214
+ label="启用AP购买",
1215
+ value=self.current_config.options.purchase.ap_enabled,
1216
+ info=PurchaseConfig.model_fields['ap_enabled'].description
1217
+ )
1218
+
1219
+ # 转换枚举值为显示文本
1220
+ selected_items: List[str] = []
1221
+ ap_items_map = {
1222
+ APShopItems.PRODUCE_PT_UP: "支援强化点数提升",
1223
+ APShopItems.PRODUCE_NOTE_UP: "笔记数提升",
1224
+ APShopItems.RECHALLENGE: "重新挑战券",
1225
+ APShopItems.REGENERATE_MEMORY: "回忆再生成券"
1226
+ }
1227
+ for item_value in self.current_config.options.purchase.ap_items:
1228
+ item_enum = APShopItems(item_value)
1229
+ if item_enum in ap_items_map:
1230
+ selected_items.append(ap_items_map[item_enum])
1231
+
1232
+ ap_items = gr.Dropdown(
1233
+ multiselect=True,
1234
+ choices=list(ap_items_map.values()),
1235
+ value=selected_items,
1236
+ label="AP商店购买物品",
1237
+ info=PurchaseConfig.model_fields['ap_items'].description
1238
+ )
1239
+
1240
+ purchase_enabled.change(
1241
+ fn=lambda x: gr.Group(visible=x),
1242
+ inputs=[purchase_enabled],
1243
+ outputs=[purchase_group]
1244
+ )
1245
+
1246
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1247
+ config.purchase.enabled = data['purchase_enabled']
1248
+ config.purchase.money_enabled = data['money_enabled']
1249
+ config.purchase.money_items = [DailyMoneyShopItems(x) for x in data['money_items']]
1250
+ config.purchase.money_refresh = data['money_refresh']
1251
+ config.purchase.ap_enabled = data['ap_enabled']
1252
+ ap_items_enum: List[Literal[0, 1, 2, 3]] = []
1253
+ ap_items_map: Dict[str, APShopItems] = {
1254
+ "支援强化点数提升": APShopItems.PRODUCE_PT_UP,
1255
+ "笔记数提升": APShopItems.PRODUCE_NOTE_UP,
1256
+ "重新挑战券": APShopItems.RECHALLENGE,
1257
+ "回忆再生成券": APShopItems.REGENERATE_MEMORY
1258
+ }
1259
+ for item in data['ap_items']:
1260
+ if item in ap_items_map:
1261
+ ap_items_enum.append(ap_items_map[item].value) # type: ignore
1262
+ config.purchase.ap_items = ap_items_enum
1263
+
1264
+ return set_config, {
1265
+ 'purchase_enabled': purchase_enabled,
1266
+ 'money_enabled': money_enabled,
1267
+ 'ap_enabled': ap_enabled,
1268
+ 'ap_items': ap_items,
1269
+ 'money_items': money_items,
1270
+ 'money_refresh': money_refresh
1271
+ }
1272
+
1273
+ def _create_work_settings(self) -> ConfigBuilderReturnValue:
1274
+ with gr.Column():
1275
+ gr.Markdown("### 工作设置")
1276
+ assignment_enabled = gr.Checkbox(
1277
+ label="启用工作",
1278
+ value=self.current_config.options.assignment.enabled,
1279
+ info=AssignmentConfig.model_fields['enabled'].description
1280
+ )
1281
+ with gr.Group(visible=self.current_config.options.assignment.enabled) as work_group:
1282
+ with gr.Row():
1283
+ with gr.Column():
1284
+ mini_live_reassign = gr.Checkbox(
1285
+ label="启用重新分配 MiniLive",
1286
+ value=self.current_config.options.assignment.mini_live_reassign_enabled,
1287
+ info=AssignmentConfig.model_fields['mini_live_reassign_enabled'].description
1288
+ )
1289
+ mini_live_duration = gr.Dropdown(
1290
+ choices=[4, 6, 12],
1291
+ value=self.current_config.options.assignment.mini_live_duration,
1292
+ label="MiniLive 工作时长",
1293
+ interactive=True,
1294
+ info=AssignmentConfig.model_fields['mini_live_duration'].description
1295
+ )
1296
+ with gr.Column():
1297
+ online_live_reassign = gr.Checkbox(
1298
+ label="启用重新分配 OnlineLive",
1299
+ value=self.current_config.options.assignment.online_live_reassign_enabled,
1300
+ info=AssignmentConfig.model_fields['online_live_reassign_enabled'].description
1301
+ )
1302
+ online_live_duration = gr.Dropdown(
1303
+ choices=[4, 6, 12],
1304
+ value=self.current_config.options.assignment.online_live_duration,
1305
+ label="OnlineLive 工作时长",
1306
+ interactive=True,
1307
+ info=AssignmentConfig.model_fields['online_live_duration'].description
1308
+ )
1309
+
1310
+ assignment_enabled.change(
1311
+ fn=lambda x: gr.Group(visible=x),
1312
+ inputs=[assignment_enabled],
1313
+ outputs=[work_group]
1314
+ )
1315
+ # return assignment_enabled, mini_live_reassign, mini_live_duration, online_live_reassign, online_live_duration
1316
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1317
+ config.assignment.enabled = data['assignment_enabled']
1318
+ config.assignment.mini_live_reassign_enabled = data['mini_live_reassign']
1319
+ config.assignment.mini_live_duration = data['mini_live_duration']
1320
+ config.assignment.online_live_reassign_enabled = data['online_live_reassign']
1321
+ config.assignment.online_live_duration = data['online_live_duration']
1322
+
1323
+ return set_config, {
1324
+ 'assignment_enabled': assignment_enabled,
1325
+ 'mini_live_reassign': mini_live_reassign,
1326
+ 'mini_live_duration': mini_live_duration,
1327
+ 'online_live_reassign': online_live_reassign,
1328
+ 'online_live_duration': online_live_duration
1329
+ }
1330
+
1331
+
1332
+ def _create_contest_settings(self) -> ConfigBuilderReturnValue:
1333
+ with gr.Column():
1334
+ gr.Markdown("### 竞赛设置")
1335
+ contest_enabled = gr.Checkbox(
1336
+ label="启用竞赛",
1337
+ value=self.current_config.options.contest.enabled,
1338
+ info=ContestConfig.model_fields['enabled'].description
1339
+ )
1340
+ with gr.Group(visible=self.current_config.options.contest.enabled) as contest_group:
1341
+ select_which_contestant = gr.Dropdown(
1342
+ choices=[1, 2, 3],
1343
+ value=self.current_config.options.contest.select_which_contestant,
1344
+ label="选择第几位竞赛目标",
1345
+ interactive=True,
1346
+ info=ContestConfig.model_fields['select_which_contestant'].description
1347
+ )
1348
+
1349
+ when_no_set_choices = [
1350
+ ("通知我并跳过竞赛", "remind"),
1351
+ ("提醒我并等待手动编成", "wait"),
1352
+ ("使用自动编成并提醒我", "auto_set"),
1353
+ ("使用自动编成", "auto_set_silent")
1354
+ ]
1355
+ when_no_set = gr.Dropdown(
1356
+ choices=when_no_set_choices,
1357
+ value=self.current_config.options.contest.when_no_set,
1358
+ label="竞赛队伍未编成时",
1359
+ interactive=True,
1360
+ info=ContestConfig.model_fields['when_no_set'].description
1361
+ )
1362
+ contest_enabled.change(
1363
+ fn=lambda x: gr.Group(visible=x),
1364
+ inputs=[contest_enabled],
1365
+ outputs=[contest_group]
1366
+ )
1367
+
1368
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1369
+ config.contest.enabled = data['contest_enabled']
1370
+ config.contest.select_which_contestant = data['select_which_contestant']
1371
+ config.contest.when_no_set = data['when_no_set']
1372
+
1373
+ return set_config, {
1374
+ 'contest_enabled': contest_enabled,
1375
+ 'select_which_contestant': select_which_contestant,
1376
+ 'when_no_set': when_no_set
1377
+ }
1378
+
1379
+ def _create_produce_settings(self) -> ConfigBuilderReturnValue:
1380
+ with gr.Column():
1381
+ gr.Markdown("### 培育设置")
1382
+ produce_enabled = gr.Checkbox(
1383
+ label="启用培育",
1384
+ value=self.current_config.options.produce.enabled,
1385
+ info=ProduceConfig.model_fields['enabled'].description
1386
+ )
1387
+
1388
+ with gr.Group(visible=self.current_config.options.produce.enabled) as produce_group:
1389
+ # 培育方案管理区域
1390
+ solution_manager = ProduceSolutionManager()
1391
+ solutions = solution_manager.list()
1392
+ solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1393
+
1394
+ selected_solution_id = self.current_config.options.produce.selected_solution_id
1395
+ solution_dropdown = gr.Dropdown(
1396
+ choices=solution_choices,
1397
+ value=selected_solution_id,
1398
+ label="当前使用的培育方案",
1399
+ interactive=True
1400
+ )
1401
+
1402
+ produce_count = gr.Number(
1403
+ minimum=1,
1404
+ value=self.current_config.options.produce.produce_count,
1405
+ label="培育次数",
1406
+ interactive=True,
1407
+ info=ProduceConfig.model_fields['produce_count'].description
1408
+ )
1409
+
1410
+ produce_timeout_cd = gr.Number(
1411
+ minimum=20,
1412
+ value=self.current_config.options.produce.produce_timeout_cd,
1413
+ label="推荐卡检测用时上限",
1414
+ interactive=True,
1415
+ info=ProduceConfig.model_fields['produce_timeout_cd'].description
1416
+ )
1417
+
1418
+ interrupt_timeout = gr.Number(
1419
+ minimum=20,
1420
+ value=self.current_config.options.produce.interrupt_timeout,
1421
+ label="检测超时时间",
1422
+ interactive=True,
1423
+ info=ProduceConfig.model_fields['interrupt_timeout'].description
1424
+ )
1425
+
1426
+ enable_fever_month = gr.Radio(
1427
+ label="自动启用强化月间",
1428
+ choices=[("不操作", "ignore"), ("自动启用", "on"), ("自动禁用", "off")],
1429
+ value=self.current_config.options.produce.enable_fever_month,
1430
+ info=ProduceConfig.model_fields['enable_fever_month'].description,
1431
+ interactive=True
1432
+ )
1433
+
1434
+ # 绑定启用状态变化事件
1435
+ def on_produce_enabled_change(enabled):
1436
+ return gr.Group(visible=enabled)
1437
+
1438
+ produce_enabled.change(
1439
+ fn=on_produce_enabled_change,
1440
+ inputs=[produce_enabled],
1441
+ outputs=[produce_group]
1442
+ )
1443
+
1444
+ # 存储设置Tab中的下拉框引用,供培育Tab使用
1445
+ self._settings_solution_dropdown = solution_dropdown
1446
+
1447
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1448
+ config.produce.enabled = data['produce_enabled']
1449
+ config.produce.selected_solution_id = data.get('selected_solution_id')
1450
+ config.produce.produce_count = data.get('produce_count', 1)
1451
+ config.produce.produce_timeout_cd = data.get('produce_timeout_cd', 60)
1452
+ config.produce.interrupt_timeout = data.get('interrupt_timeout', 180)
1453
+ config.produce.enable_fever_month = data.get('enable_fever_month', 'ignore')
1454
+
1455
+ return set_config, {
1456
+ 'produce_enabled': produce_enabled,
1457
+ 'selected_solution_id': solution_dropdown,
1458
+ 'produce_count': produce_count,
1459
+ 'produce_timeout_cd': produce_timeout_cd,
1460
+ 'interrupt_timeout': interrupt_timeout,
1461
+ 'enable_fever_month': enable_fever_month
1462
+ }
1463
+
1464
+
1465
+ def _create_produce_tab(self) -> None:
1466
+ """创建培育Tab"""
1467
+ with gr.Tab("培育"):
1468
+ gr.Markdown("## 培育管理")
1469
+
1470
+ # 培育方案管理区域
1471
+ solution_manager = ProduceSolutionManager()
1472
+ solutions = solution_manager.list()
1473
+ solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1474
+
1475
+ selected_solution_id = self.current_config.options.produce.selected_solution_id
1476
+ solution_dropdown = gr.Dropdown(
1477
+ choices=solution_choices,
1478
+ value=selected_solution_id,
1479
+ label="选择培育方案",
1480
+ interactive=True
1481
+ )
1482
+
1483
+ # 获取设置Tab中的下拉框引用
1484
+ settings_dropdown = getattr(self, '_settings_solution_dropdown', None)
1485
+
1486
+ with gr.Row():
1487
+ new_solution_btn = gr.Button("新建培育", scale=1)
1488
+ delete_solution_btn = gr.Button("删除当前培育", scale=1)
1489
+
1490
+ # 培育方案详细设置区域
1491
+ with gr.Group() as solution_settings_group:
1492
+ # 获取当前选中的方案数据
1493
+ current_solution = None
1494
+ if selected_solution_id:
1495
+ try:
1496
+ current_solution = solution_manager.read(selected_solution_id)
1497
+ except ProduceSolutionNotFoundError:
1498
+ pass
1499
+
1500
+ if current_solution is None:
1501
+ # 如果没有选中方案,隐藏所有设置组件
1502
+ solution_name = gr.Textbox(label="方案名称", value="", visible=False)
1503
+ solution_description = gr.Textbox(label="方案描述", value="", visible=False)
1504
+ # 其他设置组件都设为不可见
1505
+ produce_mode = gr.Dropdown(
1506
+ choices=["regular", "pro", "master"],
1507
+ label="培育模式",
1508
+ info="进行一次 REGULAR 培育需要 ~30min,进行一次 PRO 培育需要 ~1h(具体视设备性能而定)。",
1509
+ visible=False
1510
+ )
1511
+
1512
+ # 添加偶像选择
1513
+ idol_choices = []
1514
+ for idol in IdolCard.all():
1515
+ if idol.is_another:
1516
+ idol_choices.append((f'{idol.name} 「{idol.another_name}」', idol.skin_id))
1517
+ else:
1518
+ idol_choices.append((f'{idol.name}', idol.skin_id))
1519
+
1520
+ produce_idols = gr.Dropdown(
1521
+ choices=idol_choices,
1522
+ label="选择要培育的偶像",
1523
+ multiselect=False,
1524
+ info="要培育偶像的 IdolCardSkin.id。",
1525
+ visible=False
1526
+ )
1527
+ kotone_warning = gr.Markdown(visible=False)
1528
+ auto_set_memory = gr.Checkbox(
1529
+ label="自动编成回忆",
1530
+ info="是否自动编成回忆。此选项优先级高于回忆编成编号。",
1531
+ visible=False
1532
+ )
1533
+ with gr.Group(visible=False) as memory_sets_group:
1534
+ memory_sets = gr.Dropdown(
1535
+ choices=[str(i) for i in range(1, 11)],
1536
+ label="回忆编成编号",
1537
+ multiselect=False,
1538
+ info="要使用的回忆编成编号,从 1 开始。",
1539
+ visible=False
1540
+ )
1541
+ auto_set_support = gr.Checkbox(
1542
+ label="自动编成支援卡",
1543
+ info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。",
1544
+ visible=False
1545
+ )
1546
+ with gr.Group(visible=False) as support_card_sets_group:
1547
+ support_card_sets = gr.Dropdown(
1548
+ choices=[str(i) for i in range(1, 11)],
1549
+ label="支援卡编成编号",
1550
+ multiselect=False,
1551
+ info="要使用的支援卡编成编号,从 1 开始。",
1552
+ visible=False
1553
+ )
1554
+ use_pt_boost = gr.Checkbox(
1555
+ label="使用支援强化 Pt 提升",
1556
+ info="是否使用支援强化 Pt 提升。",
1557
+ visible=False
1558
+ )
1559
+ use_note_boost = gr.Checkbox(
1560
+ label="使用笔记数提升",
1561
+ info="是否使用笔记数提升。",
1562
+ visible=False
1563
+ )
1564
+ follow_producer = gr.Checkbox(
1565
+ label="关注租借了支援卡的制作人",
1566
+ info="是否关注租借了支援卡的制作人。",
1567
+ visible=False
1568
+ )
1569
+ self_study_lesson = gr.Dropdown(
1570
+ choices=['dance', 'visual', 'vocal'],
1571
+ label='文化课自习时选项',
1572
+ info='自习课类型。',
1573
+ visible=False
1574
+ )
1575
+ prefer_lesson_ap = gr.Checkbox(
1576
+ label="SP 课程优先",
1577
+ info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。",
1578
+ visible=False
1579
+ )
1580
+ actions_order = gr.Dropdown(
1581
+ choices=[(action.display_name, action.value) for action in ProduceAction],
1582
+ label="行动优先级",
1583
+ info="每一周的行动将会按这里设置的优先级执行。",
1584
+ multiselect=True,
1585
+ visible=False
1586
+ )
1587
+ recommend_card_detection_mode = gr.Dropdown(
1588
+ choices=[
1589
+ (RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
1590
+ (RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
1591
+ ],
1592
+ label="推荐卡检测模式",
1593
+ info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。推荐Kotone使用严格模式,其他角色使用正常模式。",
1594
+ visible=False
1595
+ )
1596
+ use_ap_drink = gr.Checkbox(
1597
+ label="AP 不足时自动使用 AP 饮料",
1598
+ info="AP 不足时自动使用 AP 饮料",
1599
+ visible=False
1600
+ )
1601
+ skip_commu = gr.Checkbox(
1602
+ label="检测并跳过交流",
1603
+ info="检测并跳过交流",
1604
+ visible=False
1605
+ )
1606
+ save_solution_btn = gr.Button("保存培育方案", variant="primary", visible=False)
1607
+ else:
1608
+ # 显示选中方案的设置
1609
+ solution_name = gr.Textbox(
1610
+ label="方案名称",
1611
+ value=current_solution.name,
1612
+ interactive=True
1613
+ )
1614
+ solution_description = gr.Textbox(
1615
+ label="方案描述",
1616
+ value=current_solution.description or "",
1617
+ interactive=True
1618
+ )
1619
+
1620
+ produce_mode = gr.Dropdown(
1621
+ choices=["regular", "pro", "master"],
1622
+ value=current_solution.data.mode,
1623
+ label="培育模式",
1624
+ info="进行一次 REGULAR 培育需要 ~30min,进行一次 PRO 培育需要 ~1h(具体视设备性能而定)。"
1625
+ )
1626
+
1627
+ # 添加偶像选择
1628
+ idol_choices = []
1629
+ for idol in IdolCard.all():
1630
+ if idol.is_another:
1631
+ idol_choices.append((f'{idol.name} 「{idol.another_name}」', idol.skin_id))
1632
+ else:
1633
+ idol_choices.append((f'{idol.name}', idol.skin_id))
1634
+
1635
+ produce_idols = gr.Dropdown(
1636
+ choices=idol_choices,
1637
+ value=current_solution.data.idol,
1638
+ label="选择要培育的偶像",
1639
+ multiselect=False,
1640
+ interactive=True,
1641
+ info="要培育偶像的 IdolCardSkin.id。"
1642
+ )
1643
+
1644
+ has_kotone = bool(current_solution.data.idol and "藤田ことね" in current_solution.data.idol)
1645
+ is_strict_mode = current_solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
1646
+ kotone_warning = gr.Markdown(
1647
+ visible=has_kotone and not is_strict_mode,
1648
+ value="使用「藤田ことね」进行培育时,确保将「推荐卡检测模式」设置为「严格模式」"
1649
+ )
1650
+
1651
+ auto_set_memory = gr.Checkbox(
1652
+ label="自动编成回忆",
1653
+ value=current_solution.data.auto_set_memory,
1654
+ info="是否自动编成回忆。此选项优先级高于回忆编成编号。"
1655
+ )
1656
+
1657
+ with gr.Group(visible=not current_solution.data.auto_set_memory) as memory_sets_group:
1658
+ memory_sets = gr.Dropdown(
1659
+ choices=[str(i) for i in range(1, 21)], # 最多20个编成位
1660
+ value=str(current_solution.data.memory_set) if current_solution.data.memory_set else None,
1661
+ label="回忆编成编号",
1662
+ multiselect=False,
1663
+ interactive=True,
1664
+ info="要使用的回忆编成编号,从 1 开始。"
1665
+ )
1666
+
1667
+ auto_set_support = gr.Checkbox(
1668
+ label="自动编成支援卡",
1669
+ value=current_solution.data.auto_set_support_card,
1670
+ info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。"
1671
+ )
1672
+
1673
+ with gr.Group(visible=not current_solution.data.auto_set_support_card) as support_card_sets_group:
1674
+ support_card_sets = gr.Dropdown(
1675
+ choices=[str(i) for i in range(1, 21)], # 最多20个编成位
1676
+ value=str(current_solution.data.support_card_set) if current_solution.data.support_card_set else None,
1677
+ label="支援卡编成编号",
1678
+ multiselect=False,
1679
+ interactive=True,
1680
+ info="要使用的支援卡编成编号,从 1 开始。"
1681
+ )
1682
+ use_pt_boost = gr.Checkbox(
1683
+ label="使用支援强化 Pt 提升",
1684
+ value=current_solution.data.use_pt_boost,
1685
+ info="是否使用支援强化 Pt 提升。"
1686
+ )
1687
+ use_note_boost = gr.Checkbox(
1688
+ label="使用笔记数提升",
1689
+ value=current_solution.data.use_note_boost,
1690
+ info="是否使用笔记数提升。"
1691
+ )
1692
+ follow_producer = gr.Checkbox(
1693
+ label="关注租借了支援卡的制作人",
1694
+ value=current_solution.data.follow_producer,
1695
+ info="是否关注租借了支援卡的制作人。"
1696
+ )
1697
+ self_study_lesson = gr.Dropdown(
1698
+ choices=['dance', 'visual', 'vocal'],
1699
+ value=current_solution.data.self_study_lesson,
1700
+ label='文化课自习时选项',
1701
+ info='自习课类型。'
1702
+ )
1703
+ prefer_lesson_ap = gr.Checkbox(
1704
+ label="SP 课程优先",
1705
+ value=current_solution.data.prefer_lesson_ap,
1706
+ info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。"
1707
+ )
1708
+ actions_order = gr.Dropdown(
1709
+ choices=[(action.display_name, action.value) for action in ProduceAction],
1710
+ value=[action.value for action in current_solution.data.actions_order],
1711
+ label="行动优先级",
1712
+ info="每一周的行动将会按这里设置的优先级执行。",
1713
+ multiselect=True
1714
+ )
1715
+ recommend_card_detection_mode = gr.Dropdown(
1716
+ choices=[
1717
+ (RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
1718
+ (RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
1719
+ ],
1720
+ value=current_solution.data.recommend_card_detection_mode.value,
1721
+ label="推荐卡检测模式",
1722
+ info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。推荐Kotone使用严格模式,其他角色使用正常模式。"
1723
+ )
1724
+ use_ap_drink = gr.Checkbox(
1725
+ label="AP 不足时自动使用 AP 饮料",
1726
+ value=current_solution.data.use_ap_drink,
1727
+ info="AP 不足时自动使用 AP 饮料"
1728
+ )
1729
+ skip_commu = gr.Checkbox(
1730
+ label="检测并跳过交流",
1731
+ value=current_solution.data.skip_commu,
1732
+ info="检测并跳过交流"
1733
+ )
1734
+
1735
+ # 添加保存按钮
1736
+ save_solution_btn = gr.Button("保存培育方案", variant="primary")
1737
+
1738
+ # 添加事件处理
1739
+ def update_kotone_warning(selected_idol, recommend_card_detection_mode):
1740
+ # Handle case where selected_idol is None (no selection)
1741
+ # selected_idol is a single string (skin_id), not a list
1742
+ if selected_idol is None:
1743
+ has_kotone = False
1744
+ else:
1745
+ has_kotone = "藤田ことね" in selected_idol
1746
+ is_strict_mode = recommend_card_detection_mode == RecommendCardDetectionMode.STRICT.value
1747
+ return gr.Markdown(visible=has_kotone and not is_strict_mode)
1748
+
1749
+ def on_solution_change(solution_id):
1750
+ """当选择的培育方案改变时,更新所有设置组件"""
1751
+ if not solution_id:
1752
+ return [
1753
+ gr.Textbox(value="", visible=False), # solution_name
1754
+ gr.Textbox(value="", visible=False), # solution_description
1755
+ gr.Dropdown(visible=False), # produce_mode
1756
+ gr.Dropdown(visible=False), # produce_idols
1757
+ gr.Markdown(visible=False), # kotone_warning
1758
+ gr.Checkbox(visible=False), # auto_set_memory
1759
+ gr.Group(visible=False), # memory_sets_group
1760
+ gr.Dropdown(visible=False), # memory_sets
1761
+ gr.Checkbox(visible=False), # auto_set_support
1762
+ gr.Group(visible=False), # support_card_sets_group
1763
+ gr.Dropdown(visible=False), # support_card_sets
1764
+ gr.Checkbox(visible=False), # use_pt_boost
1765
+ gr.Checkbox(visible=False), # use_note_boost
1766
+ gr.Checkbox(visible=False), # follow_producer
1767
+ gr.Dropdown(visible=False), # self_study_lesson
1768
+ gr.Checkbox(visible=False), # prefer_lesson_ap
1769
+ gr.Dropdown(visible=False), # actions_order
1770
+ gr.Dropdown(visible=False), # recommend_card_detection_mode
1771
+ gr.Checkbox(visible=False), # use_ap_drink
1772
+ gr.Checkbox(visible=False), # skip_commu
1773
+ gr.Button(visible=False), # save_solution_btn
1774
+ ]
1775
+
1776
+ try:
1777
+ solution = solution_manager.read(solution_id)
1778
+ has_kotone = bool(solution.data.idol and "藤田ことね" in solution.data.idol)
1779
+ is_strict_mode = solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
1780
+
1781
+ return [
1782
+ gr.Textbox(value=solution.name, visible=True),
1783
+ gr.Textbox(value=solution.description or "", visible=True),
1784
+ gr.Dropdown(value=solution.data.mode, visible=True),
1785
+ gr.Dropdown(value=solution.data.idol, visible=True),
1786
+ gr.Markdown(visible=has_kotone and not is_strict_mode),
1787
+ gr.Checkbox(value=solution.data.auto_set_memory, visible=True),
1788
+ gr.Group(visible=not solution.data.auto_set_memory),
1789
+ gr.Dropdown(value=str(solution.data.memory_set) if solution.data.memory_set else None, visible=True),
1790
+ gr.Checkbox(value=solution.data.auto_set_support_card, visible=True),
1791
+ gr.Group(visible=not solution.data.auto_set_support_card),
1792
+ gr.Dropdown(value=str(solution.data.support_card_set) if solution.data.support_card_set else None, visible=True),
1793
+ gr.Checkbox(value=solution.data.use_pt_boost, visible=True),
1794
+ gr.Checkbox(value=solution.data.use_note_boost, visible=True),
1795
+ gr.Checkbox(value=solution.data.follow_producer, visible=True),
1796
+ gr.Dropdown(value=solution.data.self_study_lesson, visible=True),
1797
+ gr.Checkbox(value=solution.data.prefer_lesson_ap, visible=True),
1798
+ gr.Dropdown(value=[action.value for action in solution.data.actions_order], visible=True),
1799
+ gr.Dropdown(value=solution.data.recommend_card_detection_mode.value, visible=True),
1800
+ gr.Checkbox(value=solution.data.use_ap_drink, visible=True),
1801
+ gr.Checkbox(value=solution.data.skip_commu, visible=True),
1802
+ gr.Button(visible=True), # save_solution_btn
1803
+ ]
1804
+ except ProduceSolutionNotFoundError:
1805
+ gr.Warning(f"培育方案 {solution_id} 不存在")
1806
+ return on_solution_change(None)
1807
+ except Exception as e:
1808
+ gr.Error(f"加载培育方案失败:{str(e)}")
1809
+ return on_solution_change(None)
1810
+
1811
+ def on_new_solution():
1812
+ """创建新的培育方案"""
1813
+ try:
1814
+ new_solution = solution_manager.new("新培育方案")
1815
+ solution_manager.save(new_solution.id, new_solution)
1816
+
1817
+ # 重新列出所有方案
1818
+ solutions = solution_manager.list()
1819
+ solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1820
+
1821
+ gr.Success("新培育方案创建成功")
1822
+
1823
+ # 更新培育 Tab 下拉框并保持设置 Tab 当前选中值
1824
+ updated_dropdown_produce = gr.Dropdown(choices=solution_choices, value=new_solution.id)
1825
+ if settings_dropdown is not None:
1826
+ current_selected = self.current_config.options.produce.selected_solution_id
1827
+ updated_dropdown_settings = gr.Dropdown(choices=solution_choices, value=current_selected)
1828
+ return [updated_dropdown_produce, updated_dropdown_settings]
1829
+ else:
1830
+ return updated_dropdown_produce
1831
+ except Exception as e:
1832
+ gr.Error(f"创建培育方案失败:{str(e)}")
1833
+ if settings_dropdown is not None:
1834
+ return [gr.Dropdown(), gr.Dropdown()]
1835
+ else:
1836
+ return gr.Dropdown()
1837
+
1838
+ def on_delete_solution(solution_id):
1839
+ """删除当前培育方案"""
1840
+ if not solution_id:
1841
+ gr.Warning("请先选择要删除的培育方案")
1842
+ if settings_dropdown is not None:
1843
+ return [gr.Dropdown(), gr.Dropdown()]
1844
+ else:
1845
+ return gr.Dropdown()
1846
+
1847
+ # 若尝试删除当前正在使用的培育方案,则拒绝并提示
1848
+ if solution_id == self.current_config.options.produce.selected_solution_id:
1849
+ gr.Warning("不可删除选中方案。请先在设置中选择其他方案,保存后再删除此方案。")
1850
+ if settings_dropdown is not None:
1851
+ return [gr.Dropdown(), gr.Dropdown()]
1852
+ else:
1853
+ return gr.Dropdown()
1854
+
1855
+ try:
1856
+ solution_manager.delete(solution_id)
1857
+
1858
+ # 重新列出所有方案
1859
+ solutions = solution_manager.list()
1860
+ solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1861
+
1862
+ gr.Success("培育方案删除成功")
1863
+ # 删除方案后,保持当前培育方案的选择不变
1864
+ current_selected = self.current_config.options.produce.selected_solution_id
1865
+ if current_selected not in [sol.id for sol in solutions]:
1866
+ current_selected = None # 已不存在
1867
+
1868
+ updated_dropdown = gr.Dropdown(choices=solution_choices, value=current_selected)
1869
+ if settings_dropdown is not None:
1870
+ return [updated_dropdown, updated_dropdown]
1871
+ else:
1872
+ return updated_dropdown
1873
+ except Exception as e:
1874
+ gr.Error(f"删除培育方案失败:{str(e)}")
1875
+ if settings_dropdown is not None:
1876
+ return [gr.Dropdown(), gr.Dropdown()]
1877
+ else:
1878
+ return gr.Dropdown()
1879
+
1880
+ def on_save_solution(solution_id, name, description, mode, idols, auto_memory, memory_sets,
1881
+ auto_support, support_card_sets, pt_boost, note_boost, follow_producer, study_lesson, prefer_ap,
1882
+ actions, detection_mode, ap_drink, skip_commu_val):
1883
+ """保存培育方案"""
1884
+ if not solution_id:
1885
+ gr.Warning("请先选择要保存的培育方案")
1886
+ if settings_dropdown is not None:
1887
+ return [gr.Dropdown(), gr.Dropdown()]
1888
+ else:
1889
+ return gr.Dropdown()
1890
+
1891
+ try:
1892
+ # 构建培育数据
1893
+ produce_data = ProduceData(
1894
+ mode=mode,
1895
+ idol=idols if idols else None,
1896
+ memory_set=int(memory_sets) if memory_sets else None,
1897
+ support_card_set=int(support_card_sets) if support_card_sets else None,
1898
+ auto_set_memory=auto_memory,
1899
+ auto_set_support_card=auto_support,
1900
+ use_pt_boost=pt_boost,
1901
+ use_note_boost=note_boost,
1902
+ follow_producer=follow_producer,
1903
+ self_study_lesson=study_lesson,
1904
+ prefer_lesson_ap=prefer_ap,
1905
+ actions_order=[ProduceAction(action) for action in actions] if actions else [],
1906
+ recommend_card_detection_mode=RecommendCardDetectionMode(detection_mode),
1907
+ use_ap_drink=ap_drink,
1908
+ skip_commu=skip_commu_val
1909
+ )
1910
+
1911
+ # 构建方案对象
1912
+ solution = ProduceSolution(
1913
+ id=solution_id,
1914
+ name=name or "未命名方案",
1915
+ description=description,
1916
+ data=produce_data
1917
+ )
1918
+
1919
+ # 保存方案
1920
+ solution_manager.save(solution_id, solution)
1921
+
1922
+ # 重新列出所有方案(确保没有重复项)
1923
+ solutions = solution_manager.list()
1924
+ solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1925
+
1926
+ gr.Success("培育方案保存成功")
1927
+ # 根据是否有设置Tab的下拉框来决定返回值
1928
+ updated_dropdown = gr.Dropdown(choices=solution_choices, value=solution_id)
1929
+ if settings_dropdown is not None:
1930
+ return [updated_dropdown, updated_dropdown]
1931
+ else:
1932
+ return updated_dropdown
1933
+ except Exception as e:
1934
+ gr.Error(f"保存培育方案失败:{str(e)}")
1935
+ if settings_dropdown is not None:
1936
+ return [gr.Dropdown(), gr.Dropdown()]
1937
+ else:
1938
+ return gr.Dropdown()
1939
+
1940
+ # 绑定事件
1941
+ # 为所有控件绑定change事件,无论是否有选中方案
1942
+ auto_set_memory.change(
1943
+ fn=lambda x: gr.Group(visible=not x),
1944
+ inputs=[auto_set_memory],
1945
+ outputs=[memory_sets_group]
1946
+ )
1947
+
1948
+ auto_set_support.change(
1949
+ fn=lambda x: gr.Group(visible=not x),
1950
+ inputs=[auto_set_support],
1951
+ outputs=[support_card_sets_group]
1952
+ )
1953
+
1954
+ if current_solution is not None:
1955
+ recommend_card_detection_mode.change(
1956
+ fn=update_kotone_warning,
1957
+ inputs=[produce_idols, recommend_card_detection_mode],
1958
+ outputs=kotone_warning
1959
+ )
1960
+ produce_idols.change(
1961
+ fn=update_kotone_warning,
1962
+ inputs=[produce_idols, recommend_card_detection_mode],
1963
+ outputs=kotone_warning
1964
+ )
1965
+
1966
+ # 绑定方案管理事件
1967
+ solution_dropdown.change(
1968
+ fn=on_solution_change,
1969
+ inputs=[solution_dropdown],
1970
+ outputs=[
1971
+ solution_name, solution_description,
1972
+ produce_mode, produce_idols, kotone_warning,
1973
+ auto_set_memory, memory_sets_group, memory_sets,
1974
+ auto_set_support, support_card_sets_group, support_card_sets,
1975
+ use_pt_boost, use_note_boost, follow_producer,
1976
+ self_study_lesson, prefer_lesson_ap, actions_order,
1977
+ recommend_card_detection_mode, use_ap_drink, skip_commu,
1978
+ save_solution_btn
1979
+ ]
1980
+ )
1981
+
1982
+ # 准备输出列表,如果设置Tab的下拉框存在,则同时更新
1983
+ outputs_for_new = [solution_dropdown]
1984
+ outputs_for_delete = [solution_dropdown]
1985
+ outputs_for_save = [solution_dropdown]
1986
+
1987
+ if settings_dropdown is not None:
1988
+ outputs_for_new.append(settings_dropdown)
1989
+ outputs_for_delete.append(settings_dropdown)
1990
+ outputs_for_save.append(settings_dropdown)
1991
+
1992
+ new_solution_btn.click(
1993
+ fn=on_new_solution,
1994
+ outputs=outputs_for_new
1995
+ )
1996
+
1997
+ delete_solution_btn.click(
1998
+ fn=on_delete_solution,
1999
+ inputs=[solution_dropdown],
2000
+ outputs=outputs_for_delete
2001
+ )
2002
+
2003
+ # 绑定保存按钮事件
2004
+ save_solution_btn.click(
2005
+ fn=on_save_solution,
2006
+ inputs=[
2007
+ solution_dropdown, solution_name, solution_description,
2008
+ produce_mode, produce_idols,
2009
+ auto_set_memory, memory_sets, auto_set_support, support_card_sets,
2010
+ use_pt_boost, use_note_boost, follow_producer,
2011
+ self_study_lesson, prefer_lesson_ap, actions_order,
2012
+ recommend_card_detection_mode, use_ap_drink, skip_commu
2013
+ ],
2014
+ outputs=outputs_for_save
2015
+ )
2016
+
2017
+ def _create_club_reward_settings(self) -> ConfigBuilderReturnValue:
2018
+ with gr.Column():
2019
+ gr.Markdown("### 社团奖励设置")
2020
+ club_reward_enabled = gr.Checkbox(
2021
+ label="启用社团奖励",
2022
+ value=self.current_config.options.club_reward.enabled,
2023
+ info=ClubRewardConfig.model_fields['enabled'].description
2024
+ )
2025
+ with gr.Group(visible=self.current_config.options.club_reward.enabled) as club_reward_group:
2026
+ selected_note = gr.Dropdown(
2027
+ choices=list(DailyMoneyShopItems.note_items()),
2028
+ value=self.current_config.options.club_reward.selected_note,
2029
+ label="想在社团奖励中获取到的笔记",
2030
+ interactive=True,
2031
+ info=ClubRewardConfig.model_fields['selected_note'].description
2032
+ )
2033
+ club_reward_enabled.change(
2034
+ fn=lambda x: gr.Group(visible=x),
2035
+ inputs=[club_reward_enabled],
2036
+ outputs=[club_reward_group]
2037
+ )
2038
+
2039
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2040
+ config.club_reward.enabled = data['club_reward_enabled']
2041
+ config.club_reward.selected_note = DailyMoneyShopItems(data['selected_note'])
2042
+
2043
+ return set_config, {
2044
+ 'club_reward_enabled': club_reward_enabled,
2045
+ 'selected_note': selected_note
2046
+ }
2047
+
2048
+ def _create_capsule_toys_settings(self) -> ConfigBuilderReturnValue:
2049
+ with gr.Column():
2050
+ gr.Markdown("### 扭蛋设置")
2051
+ capsule_toys_enabled = gr.Checkbox(
2052
+ label="是否启用自动扭蛋机",
2053
+ value=self.current_config.options.capsule_toys.enabled,
2054
+ info=CapsuleToysConfig.model_fields['enabled'].description
2055
+ )
2056
+ min_value = 0
2057
+ max_value = 10
2058
+ with gr.Group(visible=self.current_config.options.capsule_toys.enabled) as capsule_toys_group:
2059
+ friend_capsule_toys_count = gr.Number(
2060
+ value=self.current_config.options.capsule_toys.friend_capsule_toys_count,
2061
+ label="好友扭蛋机的扭蛋次数",
2062
+ info=CapsuleToysConfig.model_fields['friend_capsule_toys_count'].description,
2063
+ minimum=0,
2064
+ maximum=5
2065
+ )
2066
+ sense_capsule_toys_count = gr.Number(
2067
+ value=self.current_config.options.capsule_toys.sense_capsule_toys_count,
2068
+ label="感性扭蛋机的扭蛋次数",
2069
+ info=CapsuleToysConfig.model_fields['sense_capsule_toys_count'].description,
2070
+ minimum=0,
2071
+ maximum=5
2072
+ )
2073
+ logic_capsule_toys_count = gr.Number(
2074
+ value=self.current_config.options.capsule_toys.logic_capsule_toys_count,
2075
+ label="逻辑扭蛋机的扭蛋次数",
2076
+ info=CapsuleToysConfig.model_fields['logic_capsule_toys_count'].description,
2077
+ minimum=0,
2078
+ maximum=5
2079
+ )
2080
+ anomaly_capsule_toys_count = gr.Number(
2081
+ value=self.current_config.options.capsule_toys.anomaly_capsule_toys_count,
2082
+ label="非凡扭蛋机的扭蛋次数",
2083
+ info=CapsuleToysConfig.model_fields['anomaly_capsule_toys_count'].description,
2084
+ minimum=0,
2085
+ maximum=5
2086
+ )
2087
+ capsule_toys_enabled.change(
2088
+ fn=lambda x: gr.Group(visible=x),
2089
+ inputs=[capsule_toys_enabled],
2090
+ outputs=[capsule_toys_group]
2091
+ )
2092
+
2093
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2094
+ config.capsule_toys.enabled = data['capsule_toys_enabled']
2095
+ config.capsule_toys.friend_capsule_toys_count = data['friend_capsule_toys_count']
2096
+ config.capsule_toys.sense_capsule_toys_count = data['sense_capsule_toys_count']
2097
+ config.capsule_toys.logic_capsule_toys_count = data['logic_capsule_toys_count']
2098
+ config.capsule_toys.anomaly_capsule_toys_count = data['anomaly_capsule_toys_count']
2099
+
2100
+ return set_config, {
2101
+ 'capsule_toys_enabled': capsule_toys_enabled,
2102
+ 'friend_capsule_toys_count': friend_capsule_toys_count,
2103
+ 'sense_capsule_toys_count': sense_capsule_toys_count,
2104
+ 'logic_capsule_toys_count': logic_capsule_toys_count,
2105
+ 'anomaly_capsule_toys_count': anomaly_capsule_toys_count
2106
+ }
2107
+
2108
+
2109
+ def _create_start_game_settings(self) -> ConfigBuilderReturnValue:
2110
+ with gr.Column():
2111
+ gr.Markdown("### 启动游戏设置")
2112
+ start_game_enabled = gr.Checkbox(
2113
+ label="是否启用 自动启动游戏",
2114
+ value=self.current_config.options.start_game.enabled,
2115
+ info=StartGameConfig.model_fields['enabled'].description
2116
+ )
2117
+ with gr.Group(visible=self.current_config.options.start_game.enabled) as start_game_group:
2118
+ start_through_kuyo = gr.Checkbox(
2119
+ label="是否通过Kuyo启动游戏",
2120
+ value=self.current_config.options.start_game.start_through_kuyo,
2121
+ info=StartGameConfig.model_fields['start_through_kuyo'].description
2122
+ )
2123
+ game_package_name = gr.Textbox(
2124
+ value=self.current_config.options.start_game.game_package_name,
2125
+ label="游戏包名",
2126
+ info=StartGameConfig.model_fields['game_package_name'].description
2127
+ )
2128
+ kuyo_package_name = gr.Textbox(
2129
+ value=self.current_config.options.start_game.kuyo_package_name,
2130
+ label="Kuyo包名",
2131
+ info=StartGameConfig.model_fields['kuyo_package_name'].description
2132
+ )
2133
+ disable_gakumas_localify = gr.Checkbox(
2134
+ label="禁用 Gakumas Localify 汉化插件",
2135
+ value=self.current_config.options.start_game.disable_gakumas_localify,
2136
+ info=StartGameConfig.model_fields['disable_gakumas_localify'].description
2137
+ )
2138
+ dmm_game_path = gr.Textbox(
2139
+ value=self.current_config.options.start_game.dmm_game_path or "",
2140
+ label="DMM 版游戏路径",
2141
+ info=StartGameConfig.model_fields['dmm_game_path'].description,
2142
+ placeholder="例:F:\\Games\\gakumas\\gakumas.exe"
2143
+ )
2144
+ dmm_bypass = gr.Checkbox(
2145
+ label="绕过 DMM 启动器直接启动游戏(实验性)",
2146
+ value=self.current_config.options.start_game.dmm_bypass,
2147
+ info=StartGameConfig.model_fields['dmm_bypass'].description
2148
+ )
2149
+ start_game_enabled.change(
2150
+ fn=lambda x: gr.Group(visible=x),
2151
+ inputs=[start_game_enabled],
2152
+ outputs=[start_game_group]
2153
+ )
2154
+
2155
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2156
+ config.start_game.enabled = data['start_game_enabled']
2157
+ config.start_game.start_through_kuyo = data['start_through_kuyo']
2158
+ config.start_game.game_package_name = data['game_package_name']
2159
+ config.start_game.kuyo_package_name = data['kuyo_package_name']
2160
+ config.start_game.disable_gakumas_localify = data['disable_gakumas_localify']
2161
+ config.start_game.dmm_game_path = data['dmm_game_path'] if data['dmm_game_path'] else None
2162
+ config.start_game.dmm_bypass = data['dmm_bypass']
2163
+
2164
+ return set_config, {
2165
+ 'start_game_enabled': start_game_enabled,
2166
+ 'start_through_kuyo': start_through_kuyo,
2167
+ 'game_package_name': game_package_name,
2168
+ 'kuyo_package_name': kuyo_package_name,
2169
+ 'disable_gakumas_localify': disable_gakumas_localify,
2170
+ 'dmm_game_path': dmm_game_path,
2171
+ 'dmm_bypass': dmm_bypass
2172
+ }
2173
+
2174
+
2175
+ def _create_end_game_settings(self) -> ConfigBuilderReturnValue:
2176
+ with gr.Column():
2177
+ gr.Markdown("### 关闭游戏设置")
2178
+ gr.Markdown("在所有任务执行完毕后执行下面这些操作:\n(执行单个任务时不会触发)")
2179
+ exit_kaa = gr.Checkbox(
2180
+ label="退出 kaa",
2181
+ value=self.current_config.options.end_game.exit_kaa,
2182
+ info=EndGameConfig.model_fields['exit_kaa'].description
2183
+ )
2184
+ kill_game = gr.Checkbox(
2185
+ label="关闭游戏",
2186
+ value=self.current_config.options.end_game.kill_game,
2187
+ info=EndGameConfig.model_fields['kill_game'].description
2188
+ )
2189
+ kill_dmm = gr.Checkbox(
2190
+ label="关闭 DMM",
2191
+ value=self.current_config.options.end_game.kill_dmm,
2192
+ info=EndGameConfig.model_fields['kill_dmm'].description
2193
+ )
2194
+ kill_emulator = gr.Checkbox(
2195
+ label="关闭模拟器",
2196
+ value=self.current_config.options.end_game.kill_emulator,
2197
+ info=EndGameConfig.model_fields['kill_emulator'].description
2198
+ )
2199
+ shutdown = gr.Checkbox(
2200
+ label="关闭系统",
2201
+ value=self.current_config.options.end_game.shutdown,
2202
+ info=EndGameConfig.model_fields['shutdown'].description
2203
+ )
2204
+ hibernate = gr.Checkbox(
2205
+ label="休眠系统",
2206
+ value=self.current_config.options.end_game.hibernate,
2207
+ info=EndGameConfig.model_fields['hibernate'].description
2208
+ )
2209
+ restore_gakumas_localify = gr.Checkbox(
2210
+ label="恢复 Gakumas Localify 汉化插件状态",
2211
+ value=self.current_config.options.end_game.restore_gakumas_localify,
2212
+ info=EndGameConfig.model_fields['restore_gakumas_localify'].description
2213
+ )
2214
+
2215
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2216
+ config.end_game.exit_kaa = data['exit_kaa']
2217
+ config.end_game.kill_game = data['kill_game']
2218
+ config.end_game.kill_dmm = data['kill_dmm']
2219
+ config.end_game.kill_emulator = data['kill_emulator']
2220
+ config.end_game.shutdown = data['shutdown']
2221
+ config.end_game.hibernate = data['hibernate']
2222
+ config.end_game.restore_gakumas_localify = data['restore_gakumas_localify']
2223
+
2224
+ return set_config, {
2225
+ 'exit_kaa': exit_kaa,
2226
+ 'kill_game': kill_game,
2227
+ 'kill_dmm': kill_dmm,
2228
+ 'kill_emulator': kill_emulator,
2229
+ 'shutdown': shutdown,
2230
+ 'hibernate': hibernate,
2231
+ 'restore_gakumas_localify': restore_gakumas_localify
2232
+ }
2233
+
2234
+ def _create_activity_funds_settings(self) -> ConfigBuilderReturnValue:
2235
+ with gr.Column():
2236
+ gr.Markdown("### 活动费设置")
2237
+ activity_funds = gr.Checkbox(
2238
+ label="启用收取活动费",
2239
+ value=self.current_config.options.activity_funds.enabled,
2240
+ info=ActivityFundsConfig.model_fields['enabled'].description
2241
+ )
2242
+
2243
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2244
+ config.activity_funds.enabled = data['activity_funds']
2245
+
2246
+ return set_config, {
2247
+ 'activity_funds': activity_funds
2248
+ }
2249
+
2250
+ def _create_presents_settings(self) -> ConfigBuilderReturnValue:
2251
+ with gr.Column():
2252
+ gr.Markdown("### 礼物设置")
2253
+ presents = gr.Checkbox(
2254
+ label="启用收取礼物",
2255
+ value=self.current_config.options.presents.enabled,
2256
+ info=PresentsConfig.model_fields['enabled'].description
2257
+ )
2258
+
2259
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2260
+ config.presents.enabled = data['presents']
2261
+
2262
+ return set_config, {
2263
+ 'presents': presents
2264
+ }
2265
+
2266
+ def _create_mission_reward_settings(self) -> ConfigBuilderReturnValue:
2267
+ with gr.Column():
2268
+ gr.Markdown("### 任务奖励设置")
2269
+ mission_reward = gr.Checkbox(
2270
+ label="启用领取任务奖励",
2271
+ value=self.current_config.options.mission_reward.enabled,
2272
+ info=MissionRewardConfig.model_fields['enabled'].description
2273
+ )
2274
+
2275
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2276
+ config.mission_reward.enabled = data['mission_reward']
2277
+
2278
+ return set_config, {
2279
+ 'mission_reward': mission_reward
2280
+ }
2281
+
2282
+ def _create_upgrade_support_card_settings(self) -> ConfigBuilderReturnValue:
2283
+ with gr.Column():
2284
+ gr.Markdown("### 升级支援卡设置")
2285
+ upgrade_support_card_enabled = gr.Checkbox(
2286
+ label="启用升级支援卡",
2287
+ value=self.current_config.options.upgrade_support_card.enabled,
2288
+ info=UpgradeSupportCardConfig.model_fields['enabled'].description
2289
+ )
2290
+
2291
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2292
+ config.upgrade_support_card.enabled = data['upgrade_support_card_enabled']
2293
+
2294
+ return set_config, {
2295
+ 'upgrade_support_card_enabled': upgrade_support_card_enabled
2296
+ }
2297
+
2298
+ def _create_trace_settings(self) -> ConfigBuilderReturnValue:
2299
+ with gr.Column():
2300
+ gr.Markdown("### 跟踪设置")
2301
+ trace_recommend_card_detection = gr.Checkbox(
2302
+ label="跟踪推荐卡检测",
2303
+ value=self.current_config.options.trace.recommend_card_detection,
2304
+ info=TraceConfig.model_fields['recommend_card_detection'].description,
2305
+ interactive=True
2306
+ )
2307
+
2308
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2309
+ config.trace.recommend_card_detection = data['trace_recommend_card_detection']
2310
+
2311
+ return set_config, {
2312
+ 'trace_recommend_card_detection': trace_recommend_card_detection
2313
+ }
2314
+
2315
+ def _create_misc_settings(self) -> ConfigBuilderReturnValue:
2316
+ with gr.Column():
2317
+ gr.Markdown("### 杂项设置")
2318
+ check_update = gr.Dropdown(
2319
+ choices=[
2320
+ ("启动时检查更新", "startup"),
2321
+ ("从不检查更新", "never")
2322
+ ],
2323
+ value=self.current_config.options.misc.check_update,
2324
+ label="检查更新时机",
2325
+ info=MiscConfig.model_fields['check_update'].description,
2326
+ interactive=True
2327
+ )
2328
+ auto_install_update = gr.Checkbox(
2329
+ label="自动安装更新",
2330
+ value=self.current_config.options.misc.auto_install_update,
2331
+ info=MiscConfig.model_fields['auto_install_update'].description,
2332
+ interactive=True
2333
+ )
2334
+ expose_to_lan = gr.Checkbox(
2335
+ label="允许局域网访问",
2336
+ value=self.current_config.options.misc.expose_to_lan,
2337
+ info=MiscConfig.model_fields['expose_to_lan'].description,
2338
+ interactive=True
2339
+ )
2340
+ update_channel = gr.Dropdown(
2341
+ choices=[
2342
+ ("正式版", "release"),
2343
+ ("测试版", "beta")
2344
+ ],
2345
+ value=self.current_config.options.misc.update_channel,
2346
+ label="更新渠道",
2347
+ info=MiscConfig.model_fields['update_channel'].description,
2348
+ interactive=True
2349
+ )
2350
+ log_level = gr.Dropdown(
2351
+ choices=[
2352
+ ("调试", "debug"),
2353
+ ("详细", "verbose")
2354
+ ],
2355
+ value=self.current_config.options.misc.log_level,
2356
+ label="日志等级",
2357
+ info=MiscConfig.model_fields['log_level'].description,
2358
+ interactive=True
2359
+ )
2360
+ with gr.Row():
2361
+ gr.Button("创建桌面快捷方式").click(
2362
+ fn=self._create_shortcut_button_click(False),
2363
+ outputs=[]
2364
+ )
2365
+ gr.Button("创建一键启动快捷方式").click(
2366
+ fn=self._create_shortcut_button_click(True),
2367
+ outputs=[]
2368
+ )
2369
+
2370
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2371
+ config.misc.check_update = data['check_update']
2372
+ config.misc.auto_install_update = data['auto_install_update']
2373
+ config.misc.expose_to_lan = data['expose_to_lan']
2374
+ config.misc.update_channel = data['update_channel']
2375
+ config.misc.log_level = data['log_level']
2376
+
2377
+ return set_config, {
2378
+ 'check_update': check_update,
2379
+ 'auto_install_update': auto_install_update,
2380
+ 'expose_to_lan': expose_to_lan,
2381
+ 'update_channel': update_channel,
2382
+ 'log_level': log_level
2383
+ }
2384
+
2385
+ def _create_shortcut_button_click(self, start_immediately: bool) -> Callable[[], None]:
2386
+ def _inner():
2387
+ create_desktop_shortcut(start_immediately)
2388
+ gr.Success("快捷方式创建成功")
2389
+ return _inner
2390
+
2391
+ def _create_idle_settings(self) -> ConfigBuilderReturnValue:
2392
+ with gr.Column():
2393
+ gr.Markdown("### 闲置挂机")
2394
+ idle_enabled = gr.Checkbox(
2395
+ label="启用闲置挂机(任意键暂停、闲置自动恢复)",
2396
+ value=self.current_config.options.idle.enabled,
2397
+ info=IdleModeConfig.model_fields['enabled'].description,
2398
+ interactive=True
2399
+ )
2400
+ idle_seconds = gr.Slider(
2401
+ label="自动恢复触发时间(秒)",
2402
+ minimum=30,
2403
+ maximum=3600,
2404
+ step=10,
2405
+ value=self.current_config.options.idle.idle_seconds,
2406
+ info=IdleModeConfig.model_fields['idle_seconds'].description,
2407
+ interactive=True
2408
+ )
2409
+ idle_minimize_on_pause = gr.Checkbox(
2410
+ label="暂停时最小化游戏窗口",
2411
+ value=self.current_config.options.idle.minimize_on_pause,
2412
+ info=IdleModeConfig.model_fields['minimize_on_pause'].description,
2413
+ interactive=True
2414
+ )
2415
+
2416
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2417
+ config.idle.enabled = data['idle_enabled']
2418
+ config.idle.idle_seconds = int(data['idle_seconds'])
2419
+ config.idle.minimize_on_pause = data['idle_minimize_on_pause']
2420
+
2421
+ return set_config, {
2422
+ 'idle_enabled': idle_enabled,
2423
+ 'idle_seconds': idle_seconds,
2424
+ 'idle_minimize_on_pause': idle_minimize_on_pause,
2425
+ }
2426
+
2427
+ def _create_debug_settings(self) -> ConfigBuilderReturnValue:
2428
+ """调试设置:仅在调试时使用"""
2429
+ with gr.Column():
2430
+ gr.Markdown("### 调试设置")
2431
+ gr.Markdown('<div style="color: red;">仅供调试使用。正常运行时务必关闭下面所有的选项。</div>')
2432
+
2433
+ keep_screenshots = gr.Checkbox(
2434
+ label="保留截图数据",
2435
+ value=self.current_config.keep_screenshots,
2436
+ info=UserConfig.model_fields['keep_screenshots'].description,
2437
+ interactive=True
2438
+ )
2439
+
2440
+ trace_recommend_card_detection = gr.Checkbox(
2441
+ label="跟踪推荐卡检测",
2442
+ value=self.current_config.options.trace.recommend_card_detection,
2443
+ info=TraceConfig.model_fields['recommend_card_detection'].description,
2444
+ interactive=True
2445
+ )
2446
+
2447
+ def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2448
+ # 保留截图数据属于 UserConfig
2449
+ self.current_config.keep_screenshots = data['keep_screenshots']
2450
+ # 跟踪推荐卡检测属于 BaseConfig.trace
2451
+ config.trace.recommend_card_detection = data['trace_recommend_card_detection']
2452
+
2453
+ return set_config, {
2454
+ 'keep_screenshots': keep_screenshots,
2455
+ 'trace_recommend_card_detection': trace_recommend_card_detection
2456
+ }
2457
+
2458
+ def _create_settings_tab(self) -> None:
2459
+ with gr.Tab("设置"):
2460
+ gr.Markdown("## 设置 <span style='color: red; font-size: 0.8rem; font-weight: normal;'>设置修改后需要保存(网页底部)才会生效!</span>")
2461
+
2462
+ # 模拟器设置
2463
+ emulator_settings = self._create_emulator_settings()
2464
+
2465
+ # 商店购买设置
2466
+ purchase_settings = self._create_purchase_settings()
2467
+
2468
+ # 活动费设置
2469
+ activity_funds_settings = self._create_activity_funds_settings()
2470
+
2471
+ # 礼物设置
2472
+ presents_settings = self._create_presents_settings()
2473
+
2474
+ # 工作设置
2475
+ work_settings = self._create_work_settings()
2476
+
2477
+ # 竞赛设置
2478
+ contest_settings = self._create_contest_settings()
2479
+
2480
+ # 培育设置
2481
+ produce_settings = self._create_produce_settings()
2482
+
2483
+ # 任务奖励设置
2484
+ mission_reward_settings = self._create_mission_reward_settings()
2485
+
2486
+ # 社团奖励设置
2487
+ club_reward_settings = self._create_club_reward_settings()
2488
+
2489
+ # 扭蛋设置
2490
+ capsule_toys_settings = self._create_capsule_toys_settings()
2491
+
2492
+ # 升级支援卡设置
2493
+ upgrade_support_card_settings = self._create_upgrade_support_card_settings()
2494
+
2495
+ # 启动游戏设置
2496
+ start_game_settings = self._create_start_game_settings()
2497
+
2498
+ # 关闭游戏设置
2499
+ end_game_settings = self._create_end_game_settings()
2500
+
2501
+ # 杂项设置
2502
+ misc_settings = self._create_misc_settings()
2503
+
2504
+ # 闲置挂机设置
2505
+ idle_settings = self._create_idle_settings()
2506
+
2507
+ # 调试设置(放在最后)
2508
+ debug_settings = self._create_debug_settings()
2509
+
2510
+ save_btn = gr.Button("保存设置")
2511
+ result = gr.Markdown()
2512
+
2513
+ # 收集所有设置组件
2514
+ all_return_values = [
2515
+ emulator_settings,
2516
+ purchase_settings,
2517
+ activity_funds_settings,
2518
+ presents_settings,
2519
+ work_settings,
2520
+ contest_settings,
2521
+ produce_settings,
2522
+ mission_reward_settings,
2523
+ club_reward_settings,
2524
+ capsule_toys_settings,
2525
+ upgrade_support_card_settings,
2526
+ start_game_settings,
2527
+ end_game_settings,
2528
+ misc_settings,
2529
+ idle_settings,
2530
+ debug_settings
2531
+ ] # list of (set_func, { 'key': component, ... })
2532
+ all_components = [list(ret[1].values()) for ret in all_return_values] # [[c1, c2], [c3], ...]
2533
+ all_components = list(chain(*all_components)) # [c1, c2, c3, ...]
2534
+ save_btn.click(
2535
+ fn=partial(self.save_settings2, all_return_values),
2536
+ inputs=all_components,
2537
+ outputs=result
2538
+ )
2539
+
2540
+ def _create_log_tab(self) -> None:
2541
+ with gr.Tab("反馈"):
2542
+ gr.Markdown("## 反馈")
2543
+ gr.Markdown('脚本报错或者卡住?在这里填写信息可以快速反馈!')
2544
+ with gr.Column():
2545
+ report_title = gr.Textbox(label="标题", placeholder="用一句话概括问题")
2546
+ report_type = gr.Dropdown(label="反馈类型", choices=["bug"], value="bug", interactive=False)
2547
+ report_description = gr.Textbox(label="描述", lines=5, placeholder="详细描述问题。例如:什么时候出错、是否每次都出错、出错时的步骤是什么")
2548
+ with gr.Row():
2549
+ upload_report_btn = gr.Button("上传")
2550
+ save_local_report_btn = gr.Button("保存至本地")
2551
+
2552
+ result_text = gr.Markdown("等待操作\n\n\n")
2553
+
2554
+ def create_report(title: str, description: str, upload: bool, progress=gr.Progress()):
2555
+
2556
+ def on_progress(data: Dict[str, Any]):
2557
+ progress_val = data['step'] / data['total_steps']
2558
+ if data['type'] == 'packing':
2559
+ desc = f"({data['step']}/{data['total_steps']}) 正在打包 {data['item']}"
2560
+ elif data['type'] == 'uploading':
2561
+ desc = f"({data['step']}/{data['total_steps']}) 正在上传 {data['item']}"
2562
+ elif data['type'] == 'done':
2563
+ if 'url' in data:
2564
+ desc = "上传完成"
2565
+ else:
2566
+ desc = "已保存至本地"
2567
+ else:
2568
+ desc = "正在处理..."
2569
+ progress(progress_val, desc=desc)
2570
+
2571
+ try:
2572
+ result = self.feedback_service.report(
2573
+ title=title,
2574
+ description=description,
2575
+ version=self._kaa.version,
2576
+ upload=upload,
2577
+ on_progress=on_progress
2578
+ )
2579
+ return result.message
2580
+ except FeedbackServiceError as e:
2581
+ gr.Error(str(e))
2582
+ return f"### 操作失败\n\n{e}"
2583
+
2584
+ upload_report_btn.click(
2585
+ fn=partial(create_report, upload=True),
2586
+ inputs=[report_title, report_description],
2587
+ outputs=[result_text]
2588
+ )
2589
+ save_local_report_btn.click(
2590
+ fn=partial(create_report, upload=False),
2591
+ inputs=[report_title, report_description],
2592
+ outputs=[result_text]
2593
+ )
2594
+
2595
+ def _create_whats_new_tab(self) -> None:
2596
+ """创建更新标签页"""
2597
+ with gr.Tab("更新"):
2598
+ gr.Markdown("## 版本管理")
2599
+
2600
+ with gr.Accordion("更新日志", open=False):
2601
+ from kaa.metadata import WHATS_NEW
2602
+ gr.Markdown(WHATS_NEW)
2603
+
2604
+ load_info_btn = gr.Button("载入信息", variant="primary")
2605
+ status_text = gr.Markdown("")
2606
+ version_dropdown = gr.Dropdown(
2607
+ label="选择要安装的版本",
2608
+ choices=[],
2609
+ value=None,
2610
+ visible=False,
2611
+ interactive=True
2612
+ )
2613
+ install_selected_btn = gr.Button("安装选定版本", visible=False)
2614
+
2615
+ def on_load_info():
2616
+ """使用 UpdateService 加载版本信息。"""
2617
+ yield (
2618
+ "正在载入版本信息...",
2619
+ gr.Button(value="载入中...", interactive=False),
2620
+ gr.Dropdown(visible=False),
2621
+ gr.Button(visible=False)
2622
+ )
2623
+ try:
2624
+ versions_data = self.update_service.list_remote_versions()
2625
+ except UpdateFetchListError as e:
2626
+ gr.Error(str(e))
2627
+ yield (
2628
+ str(e),
2629
+ gr.Button(value="载入信息", interactive=True),
2630
+ gr.Dropdown(visible=False),
2631
+ gr.Button(visible=False)
2632
+ )
2633
+ return
2634
+
2635
+ status_info = [
2636
+ f"**当前安装版本:** {versions_data.installed_version or '未知'}",
2637
+ f"**最新版本:** {versions_data.latest or '未知'}",
2638
+ ]
2639
+ if versions_data.launcher_version:
2640
+ if versions_data.launcher_version == "0.4.x":
2641
+ status_info.append("**启动器版本:** < v0.5.0 (旧版本)")
2642
+ else:
2643
+ status_info.append(f"**启动器版本:** v{versions_data.launcher_version}")
2644
+ else:
2645
+ status_info.append("**启动器版本:** 未知")
2646
+
2647
+ status_info.append(f"**找到 {len(versions_data.versions)} 个可用版本**")
2648
+ status_message = "\n\n".join(status_info)
2649
+
2650
+ yield (
2651
+ status_message,
2652
+ gr.Button(value="载入信息", interactive=True),
2653
+ gr.Dropdown(choices=versions_data.versions, value=versions_data.versions[0] if versions_data.versions else None, visible=True),
2654
+ gr.Button(visible=True)
2655
+ )
2656
+
2657
+ def on_install_selected(selected_version: str):
2658
+ """使用 UpdateService 安装所选版本。"""
2659
+ if not selected_version:
2660
+ gr.Warning("请先选择一个版本。")
2661
+ return
2662
+
2663
+ try:
2664
+ self.update_service.install_version(selected_version)
2665
+ gr.Info(f"正在启动器中安装版本 {selected_version},程序将自动重启...")
2666
+ except UpdateServiceError as e:
2667
+ gr.Error(str(e))
2668
+
2669
+ load_info_btn.click(
2670
+ fn=on_load_info,
2671
+ outputs=[status_text, load_info_btn, version_dropdown, install_selected_btn]
2672
+ )
2673
+
2674
+ install_selected_btn.click(
2675
+ fn=on_install_selected,
2676
+ inputs=[version_dropdown],
2677
+ outputs=[]
2678
+ )
2679
+
2680
+ def _load_config(self) -> None:
2681
+ # 加载配置文件
2682
+ config_path = "config.json"
2683
+ self.config = load_config(config_path, type=BaseConfig, use_default_if_not_found=True)
2684
+ if not self.config.user_configs:
2685
+ # 如果没有用户配置,创建一个默认配置
2686
+ default_config = UserConfig[BaseConfig](
2687
+ name="默认配置",
2688
+ category="default",
2689
+ description="默认配置",
2690
+ backend=BackendConfig(),
2691
+ options=BaseConfig()
2692
+ )
2693
+ self.config.user_configs.append(default_config)
2694
+ self.current_config = self.config.user_configs[0]
2695
+
2696
+
2697
+ def create_ui(self) -> gr.Blocks:
2698
+ custom_css = """
2699
+ #container { max-width: 800px; margin: auto; padding: 20px; }
2700
+ .quick-controls-row > div {
2701
+ display: flex !important;
2702
+ flex-wrap: nowrap !important;
2703
+ gap: 0 !important;
2704
+ overflow-x: auto !important;
2705
+ }
2706
+ .quick-controls-row > div > div {
2707
+ min-width: auto !important;
2708
+ padding: 0;
2709
+ }
2710
+ """
2711
+ with gr.Blocks(title="琴音小助手", css=custom_css) as app:
2712
+ with gr.Column(elem_id="container"):
2713
+ gr.Markdown(f"# 琴音小助手 v{self._kaa.version}")
2714
+
2715
+ with gr.Tabs():
2716
+ self._create_status_tab()
2717
+ self._create_task_tab()
2718
+ self._create_settings_tab()
2719
+ self._create_produce_tab()
2720
+ self._create_log_tab()
2721
+ self._create_whats_new_tab()
2722
+
2723
+ # 启动 IdleModeManager 后台线程
2724
+ self.idle_mgr.start()
2725
+
2726
+ return app
2727
+
2728
+ def main(kaa: Kaa | None = None, start_immidiately: bool = False) -> None:
2729
+ kaa = kaa or Kaa('./config.json')
2730
+ ui = KotoneBotUI(kaa)
2731
+ app = ui.create_ui()
2732
+
2733
+ if start_immidiately:
2734
+ ui.start_run()
2735
+
2736
+ server_name = "0.0.0.0" if ui.current_config.options.misc.expose_to_lan else "127.0.0.1"
2737
+ app.launch(inbrowser=True, show_error=True, server_name=server_name)
77
2738
 
78
- error_blocks.launch(inbrowser=True)
2739
+ if __name__ == "__main__":
2740
+ main()