ksaa 2025.11b1__py3-none-any.whl → 2025.11.post1__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 (352) hide show
  1. kaa/__init__.py +0 -0
  2. kaa/application/__init__.py +0 -0
  3. kaa/application/core/misc_core.py +4 -1
  4. kaa/application/services/__init__.py +0 -0
  5. kaa/application/services/config_service.py +112 -0
  6. kaa/application/services/feedback_service.py +121 -0
  7. kaa/application/services/produce_solution_service.py +83 -0
  8. kaa/application/services/task_service.py +145 -0
  9. kaa/application/services/update_service.py +208 -0
  10. kaa/application/ui/__init__.py +0 -0
  11. kaa/application/ui/common.py +123 -0
  12. kaa/application/ui/components/alert.py +251 -0
  13. kaa/application/ui/components/alert.pyi +257 -0
  14. kaa/application/ui/components/categorized_select.pyi +697 -0
  15. kaa/application/ui/facade.py +189 -0
  16. kaa/application/ui/gradio_view.py +135 -0
  17. kaa/application/ui/views/__init__.py +0 -0
  18. kaa/application/ui/views/feedback_view.py +65 -0
  19. kaa/application/ui/views/produce_view.py +251 -0
  20. kaa/application/ui/views/settings_view.py +581 -0
  21. kaa/application/ui/views/status_view.py +171 -0
  22. kaa/application/ui/views/task_view.py +102 -0
  23. kaa/application/ui/views/update_view.py +106 -0
  24. kaa/config/schema.py +8 -0
  25. kaa/db/constants.py +3 -2
  26. kaa/errors.py +60 -0
  27. kaa/main/gr.py +60 -3132
  28. kaa/main/kaa.py +11 -3
  29. kaa/metadata.py +80 -0
  30. kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  31. kaa/resources/game.db +0 -0
  32. kaa/resources/game_ver.txt +0 -0
  33. kaa/resources/idol_cards/i_card-skin-amao-3-014_1.png +0 -0
  34. kaa/resources/idol_cards/i_card-skin-amao-3-015_0.png +0 -0
  35. kaa/resources/idol_cards/i_card-skin-atbm-1-000_0.png +0 -0
  36. kaa/resources/idol_cards/i_card-skin-atbm-1-000_1.png +0 -0
  37. kaa/resources/idol_cards/i_card-skin-atbm-2-000_0.png +0 -0
  38. kaa/resources/idol_cards/i_card-skin-atbm-2-000_1.png +0 -0
  39. kaa/resources/idol_cards/i_card-skin-atbm-3-000_0.png +0 -0
  40. kaa/resources/idol_cards/i_card-skin-atbm-3-000_1.png +0 -0
  41. kaa/resources/idol_cards/i_card-skin-fktn-3-016_0.png +0 -0
  42. kaa/resources/idol_cards/i_card-skin-fktn-3-016_1.png +0 -0
  43. kaa/resources/idol_cards/i_card-skin-hmsz-3-003_0.png +0 -0
  44. kaa/resources/idol_cards/i_card-skin-hmsz-3-003_1.png +0 -0
  45. kaa/resources/idol_cards/i_card-skin-hmsz-3-009_0.png +0 -0
  46. kaa/resources/idol_cards/i_card-skin-hmsz-3-009_1.png +0 -0
  47. kaa/resources/idol_cards/i_card-skin-hmsz-3-013_0.png +0 -0
  48. kaa/resources/idol_cards/i_card-skin-hmsz-3-013_1.png +0 -0
  49. kaa/resources/idol_cards/i_card-skin-hrnm-3-015_0.png +0 -0
  50. kaa/resources/idol_cards/i_card-skin-hrnm-3-015_1.png +0 -0
  51. kaa/resources/idol_cards/i_card-skin-hski-3-015_0.png +0 -0
  52. kaa/resources/idol_cards/i_card-skin-hski-3-015_1.png +0 -0
  53. kaa/resources/idol_cards/i_card-skin-hume-3-009_0.png +0 -0
  54. kaa/resources/idol_cards/i_card-skin-hume-3-009_1.png +0 -0
  55. kaa/resources/idol_cards/i_card-skin-hume-3-015_0.png +0 -0
  56. kaa/resources/idol_cards/i_card-skin-hume-3-015_1.png +0 -0
  57. kaa/resources/idol_cards/i_card-skin-jsna-3-009_0.png +0 -0
  58. kaa/resources/idol_cards/i_card-skin-jsna-3-009_1.png +0 -0
  59. kaa/resources/idol_cards/i_card-skin-jsna-3-014_0.png +0 -0
  60. kaa/resources/idol_cards/i_card-skin-jsna-3-014_1.png +0 -0
  61. kaa/resources/idol_cards/i_card-skin-kcna-3-014_1.png +0 -0
  62. kaa/resources/idol_cards/i_card-skin-kcna-3-015_0.png +0 -0
  63. kaa/resources/idol_cards/i_card-skin-kcna-3-015_1.png +0 -0
  64. kaa/resources/idol_cards/i_card-skin-kllj-3-014_0.png +0 -0
  65. kaa/resources/idol_cards/i_card-skin-kllj-3-014_1.png +0 -0
  66. kaa/resources/idol_cards/i_card-skin-shro-3-015_0.png +0 -0
  67. kaa/resources/idol_cards/i_card-skin-shro-3-015_1.png +0 -0
  68. kaa/resources/idol_cards/i_card-skin-ssmk-3-015_0.png +0 -0
  69. kaa/resources/idol_cards/i_card-skin-ssmk-3-015_1.png +0 -0
  70. kaa/resources/idol_cards/i_card-skin-ttmr-3-015_0.png +0 -0
  71. kaa/resources/idol_cards/i_card-skin-ttmr-3-015_1.png +0 -0
  72. kaa/sprites/052ab0f5-8d95-4b5d-8678-3838322d9e5e.png +0 -0
  73. kaa/sprites/18adb1a5-b95d-41ff-9cb9-16613fa4cf6a.png +0 -0
  74. kaa/sprites/2aac3a97-435e-4c02-925f-e2d1673b7982.png +0 -0
  75. kaa/sprites/2cc63c6d-5988-4f72-ae88-b03b8246ad97.png +0 -0
  76. kaa/sprites/3208d803-2c92-4775-80dc-0527df4b63d9.png +0 -0
  77. kaa/sprites/345435d0-5a5c-494b-95ab-1e086fb88f51.png +0 -0
  78. kaa/sprites/3ad30d90-8b57-4641-a9e2-33970e5cea1d.png +0 -0
  79. kaa/sprites/3e0da5df-a4de-42bd-83bd-13df3c393af4.png +0 -0
  80. kaa/sprites/41904062-e218-4b28-972a-b5cfcd058d2c.png +0 -0
  81. kaa/sprites/44a50906-1835-4e29-ae97-496b4cdde9e9.png +0 -0
  82. kaa/sprites/488a1403-d300-4fa6-94c7-da71d09fd4bb.png +0 -0
  83. kaa/sprites/48c16d60-05d6-43bc-97c3-7f099bfc0881.png +0 -0
  84. kaa/sprites/4e3dbeb5-70bb-4642-9f73-01f9f98bc3aa.png +0 -0
  85. kaa/sprites/54987de4-3e1b-4640-b203-1e4c82d7e98d.png +0 -0
  86. kaa/sprites/5a0bfeac-fb22-4b0c-b775-4aa6220bae89.png +0 -0
  87. kaa/sprites/5aad0930-54a3-4b6d-bec8-250228963f69.png +0 -0
  88. kaa/sprites/5c49d3b3-656e-4c8c-ae1a-9b0209b9dcc3.png +0 -0
  89. kaa/sprites/5c9a1581-a14a-449b-a1b0-7a31a965facc.png +0 -0
  90. kaa/sprites/62411b55-7be8-46d9-aa4a-fb56dab6658c.png +0 -0
  91. kaa/sprites/6580ea89-553e-4445-a855-f834d580e6e6.png +0 -0
  92. kaa/sprites/686a2bf5-0498-4d53-8ba1-1110ff36f046.png +0 -0
  93. kaa/sprites/699ca639-3006-45c1-b5fd-8cf9631fd3ea.png +0 -0
  94. kaa/sprites/6e8325f9-7fdb-4729-ac13-ca67244d97bc.png +0 -0
  95. kaa/sprites/71051aa7-81f1-4144-9318-4405e10547ee.png +0 -0
  96. kaa/sprites/732ffc6c-6a17-41f8-9cd1-f218ebc0c23f.png +0 -0
  97. kaa/sprites/744ea25a-9b6e-4f8f-9a00-0ac714571b09.png +0 -0
  98. kaa/sprites/74b50642-0b18-45c3-8a5d-013ef1e0eb78.png +0 -0
  99. kaa/sprites/77e7262f-0d12-4bd9-9f22-e8f41a600bf8.png +0 -0
  100. kaa/sprites/78b928c6-0d24-4e87-a84f-0091d42f8e49.png +0 -0
  101. kaa/sprites/7c193df5-0a1d-444d-9f92-e9c0db79e53a.png +0 -0
  102. kaa/sprites/7e729638-f54a-48bb-8bbf-eeaa4d697502.png +0 -0
  103. kaa/sprites/802e717f-d70a-4f34-8b96-be586a8dfde9.png +0 -0
  104. kaa/sprites/831cac7e-49e9-45d1-bb65-b1db733cda90.png +0 -0
  105. kaa/sprites/8411b102-866d-4e41-ab12-507397b80ca5.png +0 -0
  106. kaa/sprites/84b3c8e2-6e37-443c-be9b-8a95aedc6f37.png +0 -0
  107. kaa/sprites/85308e03-ba72-499a-9d12-870269fa3cd0.png +0 -0
  108. kaa/sprites/8922feef-17db-4b10-93da-1bad30492ac2.png +0 -0
  109. kaa/sprites/8a6ed80c-79c6-4df6-ab65-64d67afb2d5b.png +0 -0
  110. kaa/sprites/8c0439d9-9eb7-43f3-8d1e-7e41c57c1bbd.png +0 -0
  111. kaa/sprites/8dd3de6d-07d5-4d93-9663-71a3a4c57a32.png +0 -0
  112. kaa/sprites/9009323d-1418-4aed-8a93-075b326d2e7a.png +0 -0
  113. kaa/sprites/904d22f7-51be-486f-8dd1-1871c0cf28e3.png +0 -0
  114. kaa/sprites/90943f62-0a03-4a75-804a-0c2fa7b27af4.png +0 -0
  115. kaa/sprites/9180e0e7-2cc4-46c7-95ff-fbe83806baf2.png +0 -0
  116. kaa/sprites/93f5c33e-ec81-4a70-a11f-b2e8cb1751ee.png +0 -0
  117. kaa/sprites/9558b2a5-3961-4ad0-9ce8-379d71920d78.png +0 -0
  118. kaa/sprites/968dff88-5a2f-42c7-b64b-406801e22c87.png +0 -0
  119. kaa/sprites/99d99d78-ee37-4e84-8768-b5a7f4f9bd51.png +0 -0
  120. kaa/sprites/9caa102c-70af-4a8c-b595-82df9f4c05d8.png +0 -0
  121. kaa/sprites/9e06b7d6-5d15-4844-bbfe-ad1af7da3113.png +0 -0
  122. kaa/sprites/__pycache__/__init__.cpython-310.pyc +0 -0
  123. kaa/sprites/a04ed558-25e3-42d7-a662-250cad9fa5df.png +0 -0
  124. kaa/sprites/a0a62dbf-a027-4b63-86e4-2e863b2edb01.png +0 -0
  125. kaa/sprites/a4b898aa-405e-4235-8075-a34640a2b023.png +0 -0
  126. kaa/sprites/a5c3108f-c92d-4ce7-8c4e-93b79f55ceb3.png +0 -0
  127. kaa/sprites/a7e6222f-f8ca-47c2-af1e-f158a87f6a03.png +0 -0
  128. kaa/sprites/ab3d3301-8297-43a8-bf0b-85413c115a7d.png +0 -0
  129. kaa/sprites/ad589feb-6d19-47c7-a582-55ee130ee89d.png +0 -0
  130. kaa/sprites/ae4742aa-acda-442d-bf73-b3fe7b66e85c.png +0 -0
  131. kaa/sprites/aed87cd4-eb05-4c24-8adf-cd3fab20e9a3.png +0 -0
  132. kaa/sprites/afb9e4a9-29a5-4202-9f75-e7d2f1b64aa4.png +0 -0
  133. kaa/sprites/b1f547d4-c1ba-43fc-9398-4ccdc25b8b62.png +0 -0
  134. kaa/sprites/b61bd48f-1fe7-49fd-a588-09aac3b66f5a.png +0 -0
  135. kaa/sprites/b6219452-323a-44eb-9f44-97b2ad0ee49a.png +0 -0
  136. kaa/sprites/b7550a50-5943-45e7-9c1b-771117364844.png +0 -0
  137. kaa/sprites/b865eedf-7bea-46f6-bf14-7f8f4e9ab000.png +0 -0
  138. kaa/sprites/b8f0ed47-6115-4b88-848a-2fc4a1ab25d8.png +0 -0
  139. kaa/sprites/b9224799-d0c1-43d9-9f11-1d35903fc4d9.png +0 -0
  140. kaa/sprites/baa9c530-68cb-4111-85f6-060823ad32c4.png +0 -0
  141. kaa/sprites/bed54f67-d828-403c-9cb3-6a7c85695c2a.png +0 -0
  142. kaa/sprites/befadc5e-862a-4818-9223-5d58e4ac03cb.png +0 -0
  143. kaa/sprites/c0d1896c-71a1-47d6-9847-b87543a942e9.png +0 -0
  144. kaa/sprites/c2984c0f-3e17-45c3-9d7c-dc4f14440b13.png +0 -0
  145. kaa/sprites/c6b53acc-bc89-465e-9814-0138513327ac.png +0 -0
  146. kaa/sprites/c72a4609-6bf0-4568-8ebc-73c30d67c61e.png +0 -0
  147. kaa/sprites/c7829f82-bfce-4259-9ec6-f1cfb7bac925.png +0 -0
  148. kaa/sprites/c82ebc01-0127-417f-831a-9d17a84b2691.png +0 -0
  149. kaa/sprites/c8747b3b-745c-45ed-84b6-dde1b2558ce8.png +0 -0
  150. kaa/sprites/c98ef89e-4a1c-40f0-a82e-6851f7b6bbc9.png +0 -0
  151. kaa/sprites/cb04d87c-f5a9-405c-92ac-b3603e482221.png +0 -0
  152. kaa/sprites/cb43f670-6623-4af9-ac1d-746372e40511.png +0 -0
  153. kaa/sprites/ce357023-436b-4a39-882f-8410acd8f0d7.png +0 -0
  154. kaa/sprites/cea9f5ea-5923-4c6e-bd24-90d2e084112c.png +0 -0
  155. kaa/sprites/cf62e2df-3be1-4d03-9f34-1518280abd90.png +0 -0
  156. kaa/sprites/d0a483a9-400f-4b6e-87a0-9781b4454da0.png +0 -0
  157. kaa/sprites/d10d480c-e96e-475e-815c-0969f37fe3bc.png +0 -0
  158. kaa/sprites/d1839450-8d60-41a4-b035-8e1acd0f1c7a.png +0 -0
  159. kaa/sprites/d19859f3-f7cc-46ef-8bc6-86df7856fd53.png +0 -0
  160. kaa/sprites/d2d3d77d-a06f-4167-b02b-5ca688c0c0c3.png +0 -0
  161. kaa/sprites/d38c5015-e461-4e09-8b56-731472bdf59e.png +0 -0
  162. kaa/sprites/d473b5bd-4be3-4f0f-bb92-b909050f273a.png +0 -0
  163. kaa/sprites/d609a029-5407-43c2-a9a9-07bfd43fcbb8.png +0 -0
  164. kaa/sprites/d6491f82-9637-47eb-8f35-eb272b5ef8df.png +0 -0
  165. kaa/sprites/d8dd8b59-c05c-4350-a15d-be75c94d46cc.png +0 -0
  166. kaa/sprites/d98263bf-15fb-430e-ba39-2558fa99cac0.png +0 -0
  167. kaa/sprites/d9bc9bbb-9a1a-4cbe-b6ed-24112d7c5ac7.png +0 -0
  168. kaa/sprites/da922566-97ba-4477-bcad-35ea02a3ee48.png +0 -0
  169. kaa/sprites/dac8d8fd-006c-4f50-a4e0-d79dbf3f5915.png +0 -0
  170. kaa/sprites/db33ab8d-160d-48c7-b464-dfe194e762d4.png +0 -0
  171. kaa/sprites/dcfdfab4-8dfb-4069-bf88-8e8401ef86f3.png +0 -0
  172. kaa/sprites/de354b53-2fd0-4039-87e2-fcad2e070579.png +0 -0
  173. kaa/sprites/e28ea25e-0bcf-4d52-b501-9ef7a4863870.png +0 -0
  174. kaa/sprites/e2ac927f-257e-4537-995d-fbfbe4983b70.png +0 -0
  175. kaa/sprites/e642b9ef-c8a7-41d5-8d71-5cfbaf3017a1.png +0 -0
  176. kaa/sprites/e749feec-6bf2-4c24-bb12-948e696b8116.png +0 -0
  177. kaa/sprites/ed3aa6e6-4256-416b-9d04-d57ca340e39d.png +0 -0
  178. kaa/sprites/ed4c0554-84df-4c29-af18-7c38523cc59b.png +0 -0
  179. kaa/sprites/ef1bfd47-78e9-4cb7-a344-53d980fc3e5e.png +0 -0
  180. kaa/sprites/ef1ddc7d-75f9-4798-afa1-733e16794a81.png +0 -0
  181. kaa/sprites/efb01328-b6e1-438e-9471-bef3ea77a22c.png +0 -0
  182. kaa/sprites/f06b1a4f-eeb6-4dea-9543-5ec27640bdd9.png +0 -0
  183. kaa/sprites/f07bac27-9201-498f-9f26-a58291a70542.png +0 -0
  184. kaa/sprites/f0fd613b-d2f8-4b18-90d8-0b328373e715.png +0 -0
  185. kaa/sprites/f1fbe46a-759d-467d-b8ca-2158f530f7c5.png +0 -0
  186. kaa/sprites/f2314c76-3a2b-4644-831a-343a601145df.png +0 -0
  187. kaa/sprites/f329d4ba-e836-4be6-a05b-879b53667df3.png +0 -0
  188. kaa/sprites/f469d235-552f-4b07-9d03-9bcae425b6d8.png +0 -0
  189. kaa/sprites/f5177258-a3cf-4d4f-9c69-0b49556f754f.png +0 -0
  190. kaa/sprites/f5dcac82-7c54-4020-84c5-b1334b595612.png +0 -0
  191. kaa/sprites/f6656842-1460-41ab-95ed-561aefaf08e2.png +0 -0
  192. kaa/sprites/f70e9455-7cc8-449c-8282-89a9e69f7ac3.png +0 -0
  193. kaa/sprites/f7fd469f-791e-4bf7-ae3f-b5aaed92f999.png +0 -0
  194. kaa/sprites/f873280f-bc68-40d1-8d8d-3d86964264f0.png +0 -0
  195. kaa/sprites/f9da6eeb-ff9e-45b3-94b0-e7ba34938dec.png +0 -0
  196. kaa/sprites/fa24f5d5-d7e5-4421-8cbc-2ce3a0189654.png +0 -0
  197. kaa/sprites/fb61dbf3-fbc1-400a-adde-83ea08b5027d.png +0 -0
  198. kaa/sprites/fc8457b6-f8a3-4b01-9864-0cd624c312b6.png +0 -0
  199. kaa/sprites/fdbf6a89-b95a-481f-a269-be53e05b18de.png +0 -0
  200. kaa/sprites/fed11307-2186-4ede-875f-ffa0383f410c.png +0 -0
  201. kaa/sprites/ff0d3db2-c4e5-41e7-a85b-df4fd392924c.png +0 -0
  202. kaa/sprites/ff40ff91-4e8c-46cb-a254-66cd5ffd363e.png +0 -0
  203. kaa/sprites/ff614071-c318-4263-af3e-01ea0ab45b73.png +0 -0
  204. kaa/tasks/R.py +136 -128
  205. kaa/tasks/R2.py +24 -0
  206. kaa/tasks/common.py +5 -0
  207. kaa/tasks/daily/club_reward.py +1 -0
  208. kaa/tasks/daily/contest.py +2 -1
  209. kaa/tasks/daily/purchase.py +33 -1
  210. kaa/tasks/daily/upgrade_support_card.py +12 -2
  211. kaa/tasks/produce/cards.py +5 -4
  212. kaa/tasks/produce/common.py +202 -105
  213. kaa/tasks/produce/in_purodyuusu.py +35 -17
  214. kaa/tasks/produce/non_lesson_actions.py +10 -33
  215. kaa/tasks/produce/p_drink.py +5 -1
  216. kaa/tasks/produce/produce.py +2 -1
  217. kaa/tasks/start_game.py +2 -1
  218. kaa/util/reactive.py +182 -0
  219. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/METADATA +27 -24
  220. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/RECORD +352 -181
  221. /kaa/sprites/{35341839-4469-4ab6-aa40-33642fe43e82.png → 01e93c2c-28dc-41f8-8f9a-ec62ec12aa01.png} +0 -0
  222. /kaa/sprites/{68d9f340-a3d2-4708-8eb1-c1ed857decda.png → 02ac01e0-00b0-46d8-a6db-56248c843c6d.png} +0 -0
  223. /kaa/sprites/{39f6d657-3b3e-40aa-849b-7ad87a0be1d0.png → 033e73d1-9f45-4bca-bf96-6778410f2fc4.png} +0 -0
  224. /kaa/sprites/{799fe395-2264-4bc5-b515-813dadda8fe1.png → 04036690-5b0f-4ee0-a9d0-022ee822bf0e.png} +0 -0
  225. /kaa/sprites/{256ffeba-8e9e-4cc5-8a1c-9eaee427f1a5.png → 041bd3fc-d040-4773-8641-4e4509300d2f.png} +0 -0
  226. /kaa/sprites/{5ffd3f29-db4f-4f66-b9ed-be5a70b7ebf9.png → 04974917-b1e8-4ccc-807b-6f651026c955.png} +0 -0
  227. /kaa/sprites/{8be81341-a74d-42c8-8231-5d1854bc08e7.png → 04c4e4c2-9908-4be7-bcbe-4d926ca76737.png} +0 -0
  228. /kaa/sprites/{043e753e-db15-45ba-8513-53c3fc9c7f3c.png → 05b97cdf-f7df-4d91-9ec8-5e9594a46382.png} +0 -0
  229. /kaa/sprites/{5431dc38-c257-40b3-8f7b-8bceaa7fe587.png → 061b032f-860b-4c53-889b-aa126797f062.png} +0 -0
  230. /kaa/sprites/{cb6dffd9-6474-4d09-aee6-26352ea51d31.png → 07b43eb9-db32-4046-96d3-9f4314bfd80a.png} +0 -0
  231. /kaa/sprites/{3e087b8d-420d-48ff-b526-29c4a65d8cbe.png → 08b12ded-8230-4c7e-bb9f-27c85b0fdabb.png} +0 -0
  232. /kaa/sprites/{68e20ebf-02fd-46a1-88d7-b4472824e7bc.png → 09364c3a-f892-4911-abdc-2f5fea09289f.png} +0 -0
  233. /kaa/sprites/{c69dcdcf-42a7-420e-aa8e-bb8639ea80b6.png → 09d2a63d-d2f0-4b97-b3cb-22a61b1530f7.png} +0 -0
  234. /kaa/sprites/{d644faa3-ff16-417f-928c-4c62b040bfdb.png → 0cad6e46-276f-424e-a79e-d272ebaa7c8e.png} +0 -0
  235. /kaa/sprites/{db6707a2-2964-4919-9992-4d801fa43dae.png → 0e49fbde-6be6-42b3-b8d1-36e0cb9cc35d.png} +0 -0
  236. /kaa/sprites/{cd00a2d7-af48-48b1-b2e1-5360ceed249f.png → 0e51d671-928c-4972-8ac0-de446202070b.png} +0 -0
  237. /kaa/sprites/{736a9daf-05d6-46ba-810b-5a8f509152d1.png → 0e6f8981-f46c-4c7d-9d90-fe349f0a1d77.png} +0 -0
  238. /kaa/sprites/{92c3de1b-eed1-45b9-82e0-c9e97bb479e7.png → 0e9e3dbf-6e94-4a12-b558-3d8ab500c0a6.png} +0 -0
  239. /kaa/sprites/{913dbd4e-ebb1-4c9f-b145-9960cde59c17.png → 0eafb6c1-9d85-4fe0-8011-7235704ca9d3.png} +0 -0
  240. /kaa/sprites/{ec3cad5f-e0c9-4577-8499-79b50248344e.png → 1002a830-1ad5-47a7-b7dd-ed2989e6fc1b.png} +0 -0
  241. /kaa/sprites/{e5e49b44-dcc2-4e77-9180-0d2beaa3f9ad.png → 10943367-c1d8-41c7-a831-d5d05288875a.png} +0 -0
  242. /kaa/sprites/{c1d502eb-c77f-45cb-86b5-11b4a8a3aef5.png → 10f37db0-8c3e-4790-a5c3-cdb841f106a7.png} +0 -0
  243. /kaa/sprites/{802db04f-77c2-46a4-a6eb-f81d48ea03ce.png → 11f65378-b982-4936-9ac7-68560a74a042.png} +0 -0
  244. /kaa/sprites/{1befe09b-cb77-417a-b21e-7746804d4e03.png → 1660acf7-a420-4d92-a90c-55c5ff208da6.png} +0 -0
  245. /kaa/sprites/{16a0fd47-e85b-4848-8894-9d071ea993ac.png → 170d28bf-d262-4bf1-b35f-8f3d4b4fff6e.png} +0 -0
  246. /kaa/sprites/{5906d986-d0e5-452d-a7c4-ddeb7af8751d.png → 182d8c71-5ce5-4ee1-9f66-ef99aadae654.png} +0 -0
  247. /kaa/sprites/{cfe3563d-a24b-4197-a0cb-5628bd990c08.png → 18d26894-fd12-4644-8269-a09d11be9520.png} +0 -0
  248. /kaa/sprites/{8e5e968b-edad-4c03-abc8-3f2a7e8879ae.png → 1b0dd18b-eb41-4c9c-b031-bab91c93fd5d.png} +0 -0
  249. /kaa/sprites/{3fa5f81c-9223-44ba-bf07-71932350e845.png → 1e7a6e19-760c-4fbc-b0f1-f620f51056b9.png} +0 -0
  250. /kaa/sprites/{9e7eaa8e-3178-44da-a5ec-34b889f8facf.png → 1fbe13d6-3a4d-4935-b687-a76a76a1e32c.png} +0 -0
  251. /kaa/sprites/{1ad89ee4-1bb1-41d2-a92c-79caf6baa38a.png → 1fe31432-a050-48db-acc1-becde42deaf9.png} +0 -0
  252. /kaa/sprites/{94d54ba7-0ccf-4fd9-a5bf-488bac1364c8.png → 20146fe6-e62b-4c57-b229-901994903b70.png} +0 -0
  253. /kaa/sprites/{6c926e92-9af9-402d-8512-6417ca116928.png → 20c673e9-fcbe-469b-be35-7844a1d5a9cd.png} +0 -0
  254. /kaa/sprites/{572a921d-633f-4465-b177-7a0d9ad7e099.png → 21747ae5-d94d-45d1-bda8-939389dc4874.png} +0 -0
  255. /kaa/sprites/{aa1624ff-88b0-447b-8e2b-8a50fd9e4062.png → 21fbbe96-02bf-4835-aa04-068bc5cc6ca3.png} +0 -0
  256. /kaa/sprites/{3b4dc708-1d34-4a88-9fe3-d8de2ec1d5c8.png → 24be7d90-7944-4a0a-a60c-67c9737b322c.png} +0 -0
  257. /kaa/sprites/{b59c0e9b-1f20-4194-ab37-286f505fa656.png → 24d6a47c-2099-45e2-9a67-b37767f459fc.png} +0 -0
  258. /kaa/sprites/{023b8f4c-15ab-4845-b141-446c861ad475.png → 2568d3d3-b642-4ea4-b929-4dee0b09738a.png} +0 -0
  259. /kaa/sprites/{7d7df15f-707e-4e27-826f-21e42af1dae0.png → 263b1037-fb51-4eb1-8c71-5232b03235d2.png} +0 -0
  260. /kaa/sprites/{f0aef696-2106-46a4-8b7f-9f16b6c89d3f.png → 27dbd535-7f95-4698-8fea-2bed67dd6968.png} +0 -0
  261. /kaa/sprites/{81686816-c723-47d1-9824-a68d9ebe1bd4.png → 286801f7-3af5-4cd0-9c25-46c12c2cd8f8.png} +0 -0
  262. /kaa/sprites/{dc97fea3-898f-4135-9c40-ad41570ab4ae.png → 298182ae-747a-44e3-8511-a64d3532b8ac.png} +0 -0
  263. /kaa/sprites/{8e019262-ad23-4cbb-84da-86b34775a91e.png → 2d3470f8-a714-4175-a8fe-71eef80921b6.png} +0 -0
  264. /kaa/sprites/{01247ebe-8d63-4573-90f4-d8dc2b7ca14c.png → 2e3b2982-d14b-4b49-bb64-da8178311d75.png} +0 -0
  265. /kaa/sprites/{fcc72120-2a77-4c26-a27e-e1027c6088c0.png → 2e41df89-a8d7-4e7e-96cb-7f551414823b.png} +0 -0
  266. /kaa/sprites/{31f2de0a-567b-425a-9be6-536e4532d813.png → 31e02e56-f266-47b5-8b10-fe79fe06d614.png} +0 -0
  267. /kaa/sprites/{7daaca55-e3fd-4e12-bf31-872105ceb726.png → 3314dc6c-868f-4045-9f60-1f3a17fed324.png} +0 -0
  268. /kaa/sprites/{fdf0da06-f088-424f-9941-8c883fea3db3.png → 358f1561-c16f-45a0-842d-d62fc735dc80.png} +0 -0
  269. /kaa/sprites/{51b95c81-4ff9-434c-ade2-3518e63337cb.png → 3782897a-f1c2-4f05-a02d-4c760672f52a.png} +0 -0
  270. /kaa/sprites/{847eb5c0-0350-4b7c-9c9c-43c796709795.png → 385e60df-2063-4caf-b1ea-e6ac909bc49d.png} +0 -0
  271. /kaa/sprites/{8e4d8a0f-4a9d-4562-9234-63806b11d85f.png → 39450419-26b5-4fbd-a5d4-1bab369ced30.png} +0 -0
  272. /kaa/sprites/{424a0ef5-102d-4734-a731-2b3b3c7c9682.png → 397c0e24-f35a-48c9-a576-aa1fcfc517b5.png} +0 -0
  273. /kaa/sprites/{23b9cda9-8be8-438e-8e54-57d91a4bd875.png → 3adef8f4-629d-4cb7-b54a-dadabfea155e.png} +0 -0
  274. /kaa/sprites/{2234f709-1703-4589-905c-175f4db09b1b.png → 3b86b4ac-aa29-4309-82da-f47a174d88cc.png} +0 -0
  275. /kaa/sprites/{e933cd55-e47a-4327-89d7-b25995cb4a3f.png → 3c6bbcea-488d-4df8-ab40-21e263aa0b16.png} +0 -0
  276. /kaa/sprites/{49bde219-f9a3-41b5-84b8-e417160a0f40.png → 3db54e0b-672d-4dfd-8a9b-5db37a08be74.png} +0 -0
  277. /kaa/sprites/{2204967a-a0ee-4dd3-aecb-4d531e8e954c.png → 402d430d-b507-4aa6-b3e1-db66b38c71b4.png} +0 -0
  278. /kaa/sprites/{70768479-5d7a-4941-8774-ad556c47bee2.png → 414140a2-de67-4c7f-a7f6-c8b30a6c8d6a.png} +0 -0
  279. /kaa/sprites/{dd7bd6cd-90f1-4773-9b68-5df7f2f84da5.png → 4147514f-4f3f-4dd5-952b-0f2cf5f62181.png} +0 -0
  280. /kaa/sprites/{0562f69f-0300-4a2b-8252-63f3e7180e4c.png → 43e53b71-9d69-4f4f-b217-a1abeea8991e.png} +0 -0
  281. /kaa/sprites/{55c3af20-58dc-4407-a383-312a57417c39.png → 44fab632-3920-4cd8-9967-f7a8a1ee29a0.png} +0 -0
  282. /kaa/sprites/{645909d7-b960-46c7-b208-db44b6b7feec.png → 45f705f9-58fb-4729-ae55-ee10848678d6.png} +0 -0
  283. /kaa/sprites/{da48db0d-09d9-47c3-9e08-8b69ad547be6.png → 4678a5fa-5b1c-4fdc-b7c4-e0b820039546.png} +0 -0
  284. /kaa/sprites/{37215a56-f8e1-4f22-a148-9894136f6ab1.png → 4738974e-60b8-4583-8548-94211c164291.png} +0 -0
  285. /kaa/sprites/{338d7e71-98ce-4cd1-9954-bf7155150780.png → 474c1678-9c36-495b-aef2-cadbb47b237d.png} +0 -0
  286. /kaa/sprites/{01256311-237c-476f-96ce-4136dc4bfbd3.png → 47783412-9cc7-4ba0-904d-8cd300222889.png} +0 -0
  287. /kaa/sprites/{9f7150bc-c0b8-48d6-862c-456aaf518b2b.png → 47ed66a4-9e7e-4646-b554-469dc86322db.png} +0 -0
  288. /kaa/sprites/{5930140e-effe-4bcf-b0be-c20d3990d833.png → 4d0074ad-6db4-4060-b71a-05c0e36e57a6.png} +0 -0
  289. /kaa/sprites/{22dc4cfe-8c66-4461-8660-e2d11bbe6154.png → 4f2f8328-a698-40d3-a40f-1f1ae77dc843.png} +0 -0
  290. /kaa/sprites/{694879e8-ecca-45e2-8ea4-1b447fd43ace.png → 52d0ff68-7a55-4d53-bc20-a1fa05cfe876.png} +0 -0
  291. /kaa/sprites/{962aa0b9-1dd3-4260-a4fb-c069c26e5a84.png → 52d737a7-5b0b-4ce0-ae3f-5d4048060f12.png} +0 -0
  292. /kaa/sprites/{87f3c1ae-5117-4871-a7d6-3f5ff0c9f9fb.png → 54a9cae1-62e5-4546-a46a-a5157f51e8b4.png} +0 -0
  293. /kaa/sprites/{e7f2734f-9fdc-4521-8d7e-8b0fe270a223.png → 56d35e14-ada9-4bfa-a48f-e4601054e7eb.png} +0 -0
  294. /kaa/sprites/{3b4f90c9-6de7-4ac3-8e41-5f8757bf3f3e.png → 575784c1-7c4a-4484-9f38-aa3c280ce56e.png} +0 -0
  295. /kaa/sprites/{57c81080-7b53-4286-8fa5-848ad63e4774.png → 577417ee-6635-4cb4-9f28-d1e1b47ae2cd.png} +0 -0
  296. /kaa/sprites/{72634cb3-4c76-4ca2-8898-ebb837ec19e3.png → 58358122-984b-4c31-91d7-eaf5c8c9353d.png} +0 -0
  297. /kaa/sprites/{179c0fff-ebd2-4a16-8db2-297552cca218.png → 5a06a662-ffa9-49a9-a86d-136e5983abdd.png} +0 -0
  298. /kaa/sprites/{2b6d2eb0-bcf7-4bea-b037-6293d9c6d728.png → 5c8eebc5-6d5e-445e-a3d1-66dce2bb6338.png} +0 -0
  299. /kaa/sprites/{2a8626df-056c-46a2-aaf3-0060a5aef6a3.png → 5d2b03a2-acb6-4bd2-86e8-1b203d1aeaec.png} +0 -0
  300. /kaa/sprites/{0af1d573-1dc7-427e-997d-173308ea3649.png → 5f02656f-1314-44d0-ae31-a176c24d758c.png} +0 -0
  301. /kaa/sprites/{84cabff2-7a1b-4018-b74b-46f4db06304f.png → 608c12e2-95dd-4efa-93b2-e648e9583062.png} +0 -0
  302. /kaa/sprites/{8f7b55d8-0143-4d27-bef8-560d4d510500.png → 647bb09a-13f8-4d54-a8ac-5b534f1a8d87.png} +0 -0
  303. /kaa/sprites/{1cf03f98-63e9-467c-804d-c74dd97b69bd.png → 6483c406-26cc-44ee-ad12-2a30ec47aedb.png} +0 -0
  304. /kaa/sprites/{889a4153-b713-49cb-a4d5-e302bcdd002d.png → 67d6a201-dfb9-4c88-b42b-d968cf9a034c.png} +0 -0
  305. /kaa/sprites/{ea430652-e263-4a15-bb6f-8a8807be6c8f.png → 6a93b483-0848-45e4-a937-51850f296c3d.png} +0 -0
  306. /kaa/sprites/{e4dc3b35-195c-415f-9484-c2459bd8675a.png → 6b4e4351-b370-48cd-8e8f-ca663d44aa0c.png} +0 -0
  307. /kaa/sprites/{f6b042a8-a91e-4811-99f9-18bbb5197468.png → 6c961dee-87f3-42f1-acef-9fe7d6be4df1.png} +0 -0
  308. /kaa/sprites/{ab237c6d-c4d8-4716-8ef7-99b8e995c9a7.png → 6d22b7ee-6e0f-4f5d-96a4-d4cba26a4d04.png} +0 -0
  309. /kaa/sprites/{d7750f81-de10-4d6f-9cc7-35223bda9d70.png → 6d5a3e08-257c-4394-96a6-e0719a66e5ae.png} +0 -0
  310. /kaa/sprites/{847226a3-b7d2-4156-b732-9215a3e14f3a.png → 71d0d086-50f1-4f50-bb24-a0190827bd08.png} +0 -0
  311. /kaa/sprites/{2cec1d6a-9e8c-49b3-b885-0b0d7c4b95ee.png → 71f87e1c-77f1-45c7-bb9c-16f14498682b.png} +0 -0
  312. /kaa/sprites/{30e12bf1-8fa5-4de1-8d7b-28fa0fec0cb6.png → 737570a7-b8f9-4cd7-b5bd-f40cd51b49f8.png} +0 -0
  313. /kaa/sprites/{ae1e70bc-f735-4749-8c23-95735b91897a.png → 745ee069-0f22-4b99-9327-513f365eb922.png} +0 -0
  314. /kaa/sprites/{8bd22fe1-b544-4d2e-b74a-34f3cf0eb512.png → 7601152e-a216-4bf8-92a6-4e3e6ea01d3d.png} +0 -0
  315. /kaa/sprites/{bfdc9b53-2fe5-43f1-8588-7ab71217d31c.png → 7a23d548-4395-40cc-ac19-4497fa3d9fe2.png} +0 -0
  316. /kaa/sprites/{96704aa3-b5ad-4e34-bb88-fad766415362.png → 7a7871dd-fd07-4234-bd01-c3c9e41bad45.png} +0 -0
  317. /kaa/sprites/{43a367e1-91dd-495e-8c30-41903f75a99a.png → 7d3c3a12-8d0f-48a7-9fd4-66db5e58488f.png} +0 -0
  318. /kaa/sprites/{756f4ca7-acac-48e1-8bcd-1c5cd8937e3a.png → 7d9f8ea3-1fdf-4675-8ddf-38393e235746.png} +0 -0
  319. /kaa/sprites/{31e3ac2b-ecd4-4c37-88bc-985ef32e73a3.png → 83f258af-35b8-4918-a971-80ea938a854e.png} +0 -0
  320. /kaa/sprites/{91b30f5e-31b2-4756-a3f7-f0f82a335442.png → 848a6c94-97a5-44f7-b84b-2f3283dc446b.png} +0 -0
  321. /kaa/sprites/{8ed51be4-815c-4a3d-805e-030fdfa341e2.png → 86a56df9-47ff-4741-822d-5b7e024d1598.png} +0 -0
  322. /kaa/sprites/{f0462447-5226-4819-a39b-19ab90583f83.png → 8b04d1a8-6cd6-4d2e-91e7-bdc58c46d939.png} +0 -0
  323. /kaa/sprites/{4a081370-b0a1-4067-8470-e3007cc68a12.png → 8b60ce9f-09c0-48a0-93f0-d51e0aa16afb.png} +0 -0
  324. /kaa/sprites/{91a50322-52d9-4516-9e46-bc121f20667d.png → 8c59d3ca-baff-4260-aa0a-6cedac1c2771.png} +0 -0
  325. /kaa/sprites/{f635499a-943d-47da-952a-1feb58daa25b.png → 8d7b4761-2555-4398-bb74-b70de3b75e22.png} +0 -0
  326. /kaa/sprites/{13cb0309-b713-4725-b10b-26afe46eb9c0.png → 901a2e7a-37f3-4dda-a58b-5a7644ead6b9.png} +0 -0
  327. /kaa/sprites/{cd0e8663-2ea1-436a-b9fb-3caa0c3ed9e8.png → 9cc77f5e-e7d0-4de4-aeb8-587ab8cb212a.png} +0 -0
  328. /kaa/sprites/{9991b19c-7330-42c4-9d7d-db3cb00103b7.png → 9e45adeb-4637-4b8b-a687-f1d2cd8c6c18.png} +0 -0
  329. /kaa/sprites/{5734e48a-1e58-42de-be7b-b7db67db4a01.png → a006054e-61fb-4d2b-a98e-45ce3c663662.png} +0 -0
  330. /kaa/sprites/{d1ea0ea6-4a6a-4036-b140-922c95a648b5.png → a0f98517-e4cf-422c-b29f-395a55939772.png} +0 -0
  331. /kaa/sprites/{3aed50a2-d640-4178-9b8a-4dd7973eebf1.png → a94d76ef-db70-45a9-aeca-ba16344cc572.png} +0 -0
  332. /kaa/sprites/{978873bb-e4d0-451b-9126-6977144f5395.png → afe7f086-723e-4999-a7aa-3342a797d828.png} +0 -0
  333. /kaa/sprites/{dce410ee-fa56-41c4-b75e-e1bff79cd94f.png → b2036053-36fb-4aa7-8348-e31942731a93.png} +0 -0
  334. /kaa/sprites/{ad94041c-f30b-40b9-b11a-1a26e9962274.png → b6da02bf-3f43-4ed0-9b13-f15becb42ce9.png} +0 -0
  335. /kaa/sprites/{a5694c76-3c5d-462e-81d5-1e0e6cef0ec9.png → ba2fd21c-6228-4e2d-b2db-a6d085cb38a6.png} +0 -0
  336. /kaa/sprites/{cba0646d-dffe-4c5b-a44d-1f8d827624a6.png → bb224de8-828e-4bc5-a986-632c72fc9157.png} +0 -0
  337. /kaa/sprites/{5bf2a814-df85-4d0a-b4b5-8883ac2ee434.png → bcc6ee72-ad5a-4d5c-b6c7-1b3fdfc5fa81.png} +0 -0
  338. /kaa/sprites/{edcec31d-d5de-4f22-b666-3e3c656e06d8.png → bebf9f6d-7cca-44c5-9b9f-fa36e29e97b3.png} +0 -0
  339. /kaa/sprites/{9008fbb2-f23f-424f-9be7-5bae6a3697ab.png → c4d0b0c9-ffbe-4b6c-9bf3-b05919e1a07c.png} +0 -0
  340. /kaa/sprites/{f32a41a2-c482-41ba-b718-28f048f5148c.png → c893e762-f328-4ef6-b862-5da8eb049f9c.png} +0 -0
  341. /kaa/sprites/{2d0e9d49-365a-4bcb-a416-38b87defaa3c.png → c944d298-a46b-4799-aea3-eec22fc7d4ac.png} +0 -0
  342. /kaa/sprites/{5c310fe5-7e92-4167-9eba-6e8e14a2863a.png → d28a18db-0f66-45a6-b3cd-69173ecdd790.png} +0 -0
  343. /kaa/sprites/{464b1dd8-ca2e-41e9-a5b5-90545c584db7.png → d8d5019c-52e6-4767-a9aa-d8da6ffcbd00.png} +0 -0
  344. /kaa/sprites/{dd873ac0-ffba-4335-9cf2-2162c6835ed8.png → d9cb2f43-2786-48d8-90f8-8cf1929f6172.png} +0 -0
  345. /kaa/sprites/{e13fc34b-accc-4986-865d-f8d8410b45e9.png → dbc5db78-81f5-41e6-bdc4-dafaa9173320.png} +0 -0
  346. /kaa/sprites/{e9310e38-a418-4561-bf33-eb3e6dccf477.png → e2ac106f-edeb-4375-af8c-45f33e9bfae6.png} +0 -0
  347. /kaa/sprites/{d81b1325-e57a-4f22-93e1-6ad4baa28935.png → e8a33d3f-a4fb-4851-b47a-b5f3d85e882f.png} +0 -0
  348. /kaa/sprites/{2289d38f-dc98-4310-9f34-4cf5023f8545.png → f7facb3a-1b41-4409-b128-d4ad03245845.png} +0 -0
  349. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/WHEEL +0 -0
  350. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/entry_points.txt +0 -0
  351. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/licenses/LICENSE +0 -0
  352. {ksaa-2025.11b1.dist-info → ksaa-2025.11.post1.dist-info}/top_level.txt +0 -0
kaa/main/gr.py CHANGED
@@ -1,3150 +1,78 @@
1
- import os
2
- import traceback
3
- import zipfile
4
1
  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
2
+ import traceback
15
3
  import gradio as gr
16
4
 
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
-
38
- logger = logging.getLogger(__name__)
39
- GradioInput = gr.Textbox | gr.Number | gr.Checkbox | gr.Dropdown | gr.Radio | gr.Slider | gr.Tabs | gr.Tab
40
- ConfigKey = Literal[
41
- # backend
42
- 'adb_ip', 'adb_port',
43
- 'screenshot_method', 'keep_screenshots',
44
- 'check_emulator', 'emulator_path',
45
- 'adb_emulator_name', 'emulator_args',
46
- '_mumu_index', '_mumu12v5_index', '_leidian_index',
47
- 'mumu_background_mode', 'target_screenshot_interval',
48
-
49
- # purchase
50
- 'purchase_enabled',
51
- 'money_enabled', 'ap_enabled',
52
- 'ap_items', 'money_items', 'money_refresh',
53
-
54
- # assignment
55
- 'assignment_enabled',
56
- 'mini_live_reassign', 'mini_live_duration',
57
- 'online_live_reassign', 'online_live_duration',
58
- 'contest_enabled',
59
- 'select_which_contestant', 'when_no_set',
60
-
61
- # produce
62
- 'produce_enabled', 'selected_solution_id', 'produce_count', 'produce_timeout_cd', 'enable_fever_month',
63
- 'mission_reward_enabled',
64
-
65
- # club reward
66
- 'club_reward_enabled',
67
- 'selected_note',
68
-
69
- # upgrade support card
70
- 'upgrade_support_card_enabled',
71
-
72
- # capsule toys
73
- 'capsule_toys_enabled', 'friend_capsule_toys_count',
74
- 'sense_capsule_toys_count', 'logic_capsule_toys_count',
75
- 'anomaly_capsule_toys_count',
76
-
77
- # start game
78
- 'start_game_enabled', 'start_through_kuyo',
79
- 'game_package_name', 'kuyo_package_name',
80
- 'disable_gakumas_localify', 'dmm_game_path', 'dmm_bypass',
81
-
82
- # end game
83
- 'exit_kaa', 'kill_game', 'kill_dmm',
84
- 'kill_emulator', 'shutdown', 'hibernate',
85
- 'restore_gakumas_localify',
86
-
87
- 'activity_funds',
88
- 'presents',
89
- 'mission_reward',
90
- 'activity_funds_enabled',
91
- 'presents_enabled',
92
- 'trace_recommend_card_detection',
93
-
94
- # misc
95
- 'check_update', 'auto_install_update', 'expose_to_lan', 'update_channel',
96
-
97
- # idle
98
- 'idle_enabled', 'idle_seconds', 'idle_minimize_on_pause',
99
-
100
- '_selected_backend_index'
101
-
102
- ]
103
- CONFIG_KEY_VALUE: tuple[str] = get_args(ConfigKey)
104
- ConfigSetFunction = Callable[[BaseConfig, Dict[ConfigKey, Any]], None]
105
- ConfigBuilderReturnValue = Tuple[ConfigSetFunction, Dict[ConfigKey, GradioInput]]
106
-
107
- def _save_bug_report(
108
- title: str,
109
- description: str,
110
- version: str,
111
- upload: bool,
112
- path: str | None = None
113
- ) -> Generator[str, None, str]:
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):
114
22
  """
115
- 保存报告
116
-
117
- :param title: 标题
118
- :param description: 描述
119
- :param version: 版本号
120
- :param upload: 是否上传
121
- :param path: 保存的路径。若为 `None`,则保存到 `./reports/bug-YY-MM-DD HH-MM-SS_标题.zip`。
122
- :return: 保存的路径
23
+ Main entry point for the KAA Gradio application.
24
+ Initializes the core application, facade, and UI view, then launches the UI.
123
25
  """
124
- from kotonebot import device
125
- from kotonebot.backend.context import ContextStackVars
126
- import re
127
-
128
- # 过滤标题中的非法文件名字符
129
- def sanitize_filename(s: str) -> str:
130
- # 替换 \/:*?"<>| 为空或下划线
131
- return re.sub(r'[\\/:*?"<>|]', '_', s)
132
-
133
- # 确保目录存在
134
- os.makedirs('logs', exist_ok=True)
135
- os.makedirs('reports', exist_ok=True)
136
-
137
- error = ""
138
- if path is None:
139
- safe_title = sanitize_filename(title)[:30] or "无标题"
140
- timestamp = datetime.now().strftime("%y-%m-%d-%H-%M-%S")
141
- path = f'./reports/bug_{timestamp}_{safe_title}.zip'
142
- with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
143
- # 打包描述文件
144
- yield "### 打包描述文件..."
145
- try:
146
- description_content = f"标题:{title}\n类型:bug\n内容:\n{description}"
147
- zipf.writestr('description.txt', description_content.encode('utf-8'))
148
- except Exception as e:
149
- error += f"保存描述文件失败:{str(e)}\n"
150
-
151
- # 打包截图
152
- yield "### 打包上次截图..."
153
- try:
154
- stack = ContextStackVars.current()
155
- screenshot = None
156
- if stack is not None:
157
- screenshot = stack._screenshot
158
- if screenshot is not None:
159
- img = cv2.imencode('.png', screenshot)[1].tobytes()
160
- zipf.writestr('last_screenshot.png', img)
161
- if screenshot is None:
162
- error += "无上次截图数据\n"
163
- except Exception as e:
164
- error += f"保存上次截图失败:{str(e)}\n"
26
+ # Configure basic logging
27
+ logging.basicConfig(level=logging.INFO, format='[%(asctime)s][%(levelname)s][%(name)s] %(message)s')
28
+ logger = logging.getLogger(__name__)
165
29
 
166
- # 打包当前截图
167
- yield "### 打包当前截图..."
168
- try:
169
- screenshot = device.screenshot()
170
- img = cv2.imencode('.png', screenshot)[1].tobytes()
171
- zipf.writestr('current_screenshot.png', img)
172
- except Exception as e:
173
- error += f"保存当前截图失败:{str(e)}\n"
174
-
175
- # 打包配置文件
176
- yield "### 打包配置文件..."
177
- try:
178
- with open('config.json', 'r', encoding='utf-8') as f:
179
- zipf.writestr('config.json', f.read())
180
- except Exception as e:
181
- error += f"保存配置文件失败:{str(e)}\n"
182
-
183
- # 打包 logs 文件夹
184
- if os.path.exists('logs'):
185
- for root, dirs, files in os.walk('logs'):
186
- for file in files:
187
- file_path = os.path.join(root, file)
188
- arcname = os.path.join('logs', os.path.relpath(file_path, 'logs'))
189
- zipf.write(file_path, arcname)
190
- yield f"### 打包 log 文件:{arcname}"
191
-
192
- # 打包 conf 文件夹
193
- if os.path.exists('conf'):
194
- for root, dirs, files in os.walk('conf'):
195
- for file in files:
196
- file_path = os.path.join(root, file)
197
- arcname = os.path.join('conf', os.path.relpath(file_path, 'conf'))
198
- zipf.write(file_path, arcname)
199
- yield f"### 打包配置文件:{arcname}"
200
-
201
- # 写出版本号
202
- zipf.writestr('version.txt', version)
203
-
204
- if not upload:
205
- yield f"### 报告已保存至 {os.path.abspath(path)}"
206
- return path
207
-
208
- # 上传报告
209
- from kotonebot.ui.file_host.sensio import upload as upload_file
210
- yield "### 上传报告..."
211
- url = ''
212
30
  try:
213
- url = upload_file(path)
214
- except Exception as e:
215
- yield f"### 上传报告失败:{str(e)}\n\n"
216
- return ''
217
-
218
- final_msg = f"### 报告导出成功:{url}\n\n"
219
- expire_time = datetime.now() + timedelta(days=7)
220
- if error:
221
- final_msg += "### 但发生了以下错误\n\n"
222
- final_msg += '\n* '.join(error.strip().split('\n'))
223
- final_msg += '\n'
224
- final_msg += f"### 此链接将于 {expire_time.strftime('%Y-%m-%d %H:%M:%S')}(7 天后)过期\n\n"
225
- final_msg += '### 复制以上文本并反馈给开发者'
226
- yield final_msg
227
- return path
228
-
229
- class KotoneBotUI:
230
- def __init__(self, kaa: Kaa) -> None:
231
- self.is_running: bool = False
232
- self.single_task_running: bool = False
233
- self.is_stopping: bool = False # 新增:标记是否正在停止过程中
234
- self.is_single_task_stopping: bool = False # 新增:标记单个任务是否正在停止
235
- self._kaa = kaa
236
- self._load_config()
237
- # IdleModeManager 空闲检测
238
- def safe_get_is_running():
239
- try:
240
- 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
241
- except ContextNotInitializedError:
242
- return False
243
-
244
- def safe_get_is_paused():
245
- try:
246
- return vars.flow.is_paused
247
- except ContextNotInitializedError:
248
- return False
249
-
250
- self.idle_mgr = IdleModeManager(
251
- get_is_running=safe_get_is_running,
252
- get_is_paused=safe_get_is_paused,
253
- get_config=lambda: self.current_config.options.idle,
254
- )
255
- self._setup_kaa()
256
-
257
- def _setup_kaa(self) -> None:
258
- from kotonebot.backend.debug.vars import debug, clear_saved
259
-
260
- self._kaa.initialize()
261
- if self.current_config.keep_screenshots:
262
- debug.auto_save_to_folder = 'dumps'
263
- debug.enabled = True
264
- clear_saved()
265
- else:
266
- debug.auto_save_to_folder = None
267
- debug.enabled = False
268
-
269
- def export_dumps(self) -> str:
270
- """导出 dumps 文件夹为 zip 文件"""
271
- if not os.path.exists('dumps'):
272
- return "dumps 文件夹不存在"
273
-
274
- timestamp = datetime.now().strftime('%y-%m-%d-%H-%M-%S')
275
- zip_filename = f'dumps-{timestamp}.zip'
276
-
277
- with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
278
- for root, dirs, files in os.walk('dumps'):
279
- for file in files:
280
- file_path = os.path.join(root, file)
281
- arcname = os.path.relpath(file_path, 'dumps')
282
- zipf.write(file_path, arcname)
283
-
284
- return f"已导出到 {zip_filename}"
285
-
286
- def export_logs(self) -> str:
287
- """导出 logs 文件夹为 zip 文件"""
288
- if not os.path.exists('logs'):
289
- return "logs 文件夹不存在"
290
-
291
- timestamp = datetime.now().strftime('%y-%m-%d-%H-%M-%S')
292
- zip_filename = f'logs-{timestamp}.zip'
293
-
294
- with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
295
- for root, dirs, files in os.walk('logs'):
296
- for file in files:
297
- file_path = os.path.join(root, file)
298
- arcname = os.path.relpath(file_path, 'logs')
299
- zipf.write(file_path, arcname)
300
-
301
- return f"已导出到 {zip_filename}"
302
-
303
- def get_button_status(self) -> Tuple[str, bool]:
304
- """获取按钮状态和交互性"""
305
- if not hasattr(self, 'run_status'):
306
- return "启动", True
307
-
308
- if not self.run_status.running:
309
- self.is_running = False
310
- self.is_stopping = False # 重置停止状态
311
- return "启动", True
312
-
313
- if self.is_stopping:
314
- return "停止中...", False # 停止中时禁用按钮
315
-
316
- return "停止", True
317
-
318
- def update_task_status(self) -> List[List[str]]:
319
- status_list: List[List[str]] = []
320
- if not hasattr(self, 'run_status'):
321
- for task_name, task in task_registry.items():
322
- status_list.append([task.name, "等待中"])
323
- return status_list
324
-
325
- for task_status in self.run_status.tasks:
326
- status_text = {
327
- 'pending': '等待中',
328
- 'running': '运行中',
329
- 'finished': '已完成',
330
- 'error': '出错',
331
- 'cancelled': '已取消'
332
- }.get(task_status.status, '未知')
333
- status_list.append([task_status.task.name, status_text])
334
- return status_list
335
-
336
- def toggle_run(self) -> Tuple[str, List[List[str]]]:
337
- if not self.is_running:
338
- return self.start_run()
339
-
340
- # 如果正在停止过程中,忽略重复点击
341
- if self.is_stopping:
342
- return "停止中...", self.update_task_status()
343
-
344
- return self.stop_run()
345
-
346
- def start_run(self) -> Tuple[str, List[List[str]]]:
347
- self.is_running = True
348
- self.run_status = self._kaa.start_all()
349
- # 通知 Idle 管理器进入运行态
350
- self.idle_mgr.notify_on_start()
351
- return "停止", self.update_task_status()
352
-
353
- def stop_run(self) -> Tuple[str, List[List[str]]]:
354
- self.is_stopping = True # 设置停止状态
355
-
356
- # 如果当前处于暂停状态,先恢复再停止
357
- if vars.flow.is_paused:
358
- gr.Info("检测到任务暂停,正在恢复后停止...")
359
- vars.flow.request_resume()
360
-
361
- self.is_running = False
362
- if self._kaa:
363
- self.run_status.interrupt()
364
- gr.Info("正在停止任务...")
365
- # 通知 Idle 管理器停止态
366
- self.idle_mgr.notify_on_stop()
367
-
368
- return "停止中...", self.update_task_status()
369
-
370
- def start_single_task(self, task_name: str) -> Tuple[str, str]:
371
- if not task_name:
372
- gr.Warning("请先选择一个任务")
373
- return "执行任务", ""
374
- task = None
375
- for name, t in task_registry.items():
376
- if name == task_name:
377
- task = t
378
- break
379
- if task is None:
380
- gr.Warning(f"任务 {task_name} 未找到")
381
- return "执行任务", ""
382
-
383
- gr.Info(f"任务 {task_name} 开始执行")
384
- self.single_task_running = True
385
- self.run_status = self._kaa.start([task])
386
- return "停止任务", f"正在执行任务: {task_name}"
387
-
388
- def stop_single_task(self) -> Tuple[str, str]:
389
- self.is_single_task_stopping = True # 设置单个任务停止状态
390
-
391
- # 如果当前处于暂停状态,先恢复再停止
392
- if vars.flow.is_paused:
393
- gr.Info("检测到任务暂停,正在恢复后停止...")
394
- vars.flow.request_resume()
395
-
396
- self.single_task_running = False
397
- if hasattr(self, 'run_status') and self._kaa:
398
- self.run_status.interrupt()
399
- gr.Info("正在停止任务...")
400
- # 通知 Idle 管理器停止态
401
- self.idle_mgr.notify_on_stop()
402
- return "停止中...", "正在停止任务..."
403
-
404
- def toggle_pause(self) -> str:
405
- """切换暂停/恢复状态"""
406
- if vars.flow.is_paused:
407
- vars.flow.request_resume()
408
- gr.Info("任务已恢复")
409
- return "暂停"
410
- else:
411
- vars.flow.request_pause()
412
- gr.Info("任务已暂停")
413
- return "恢复"
414
-
415
- def get_pause_button_status(self) -> str:
416
- """获取暂停按钮的状态"""
417
- if vars.flow.is_paused:
418
- return "恢复"
419
- else:
420
- return "暂停"
421
-
422
- def get_pause_button_with_interactive(self) -> gr.Button:
423
- """获取暂停按钮的状态和交互性"""
424
- try:
425
- text = "恢复" if vars.flow.is_paused else "暂停"
426
- except ContextNotInitializedError:
427
- # ContextNotInitializedError: Forwarded object vars called before initialization.
428
- # TODO: vars.flow.is_paused 应该要可以在脚本正式启动前就能访问
429
- text = '未启动'
430
- # 如果正在停止过程中,禁用暂停按钮
431
- interactive = not (self.is_stopping or self.is_single_task_stopping)
432
- return gr.Button(value=text, interactive=interactive)
433
-
434
- def reload_config(self) -> bool:
435
- """
436
- 重新加载配置并重新初始化相关组件
437
-
438
- :return: 是否成功重新加载
439
- """
440
- try:
441
- # 重新加载配置文件
442
- self._load_config()
443
-
444
- # 重新设置 kaa 的配置
445
- self._kaa.config_path = "config.json"
446
- self._kaa.config_type = BaseConfig
447
-
448
- # 重新初始化 kaa(这会重新创建设备和 Context)
449
- self._setup_kaa()
450
-
451
- # 重新加载 Context 中的配置数据
452
- from kotonebot.backend.context.context import config
453
- try:
454
- config.load()
455
- except ContextNotInitializedError:
456
- pass
457
-
458
- logger.info("配置已成功重新加载")
459
- return True
460
- except Exception as e:
461
- logger.error(f"重新加载配置失败:{str(e)}")
462
- return False
463
-
464
- def save_settings2(self, return_values: list[ConfigBuilderReturnValue], *args) -> str:
465
- options = BaseConfig()
466
- # return_values: (set_func, { 'key': component })
467
- keys = list(chain.from_iterable([list(data.keys()) for _, data in return_values]))
468
- # 根据 keys 与 *args 构建 data 字典
469
- data = dict[ConfigKey, Any]()
470
- assert len(keys) == len(args), "keys 与 args 长度不一致"
471
- for key, value in zip(keys, args):
472
- assert key in CONFIG_KEY_VALUE, f"未知的配置项:{key}"
473
- key = cast(ConfigKey, key)
474
- data[key] = value
475
-
476
- # 先设置options
477
- for (set_func, _) in return_values:
478
- set_func(options, data)
479
-
480
- # 验证规则1:截图方法验证
481
- screenshot_method = self.current_config.backend.screenshot_impl
482
- backend_type = self.current_config.backend.type
483
-
484
- valid_screenshot_methods = {
485
- 'mumu12': ['adb', 'adb_raw', 'uiautomator2', 'nemu_ipc'],
486
- 'mumu12v5': ['adb', 'adb_raw', 'uiautomator2', 'nemu_ipc'],
487
- 'leidian': ['adb', 'adb_raw', 'uiautomator2'],
488
- 'custom': ['adb', 'adb_raw', 'uiautomator2'],
489
- 'dmm': ['remote_windows', 'windows']
490
- }
491
-
492
- if screenshot_method not in valid_screenshot_methods.get(backend_type, []):
493
- gr.Warning(f"截图方法 '{screenshot_method}' 不适用于当前选择的模拟器类型,配置未保存。")
494
- return ""
495
-
496
- # 验证规则2:若启用培育,那么必须选择培育方案
497
- if options.produce.enabled and not options.produce.selected_solution_id:
498
- gr.Warning("启用培育时,必须选择培育方案,配置未保存。")
499
- return ""
500
-
501
- # 验证规则3:若启用AP/金币购买,对应的商品不能为空
502
- if options.purchase.ap_enabled and not options.purchase.ap_items:
503
- gr.Warning("启用AP购买时,AP商店购买物品不能为空,配置未保存。")
504
- return ""
505
-
506
- if options.purchase.money_enabled and not options.purchase.money_items:
507
- gr.Warning("启用金币购买时,金币商店购买物品不能为空,配置未保存。")
508
- return ""
509
-
510
- # 验证通过,保存配置
511
- self.current_config.options = options
512
- try:
513
- save_config(self.config, "config.json")
514
-
515
- # 尝试热重载配置
516
- if self.reload_config():
517
- gr.Success("设置已保存并应用!")
518
- else:
519
- gr.Warning("设置已保存,但重新加载失败,请重启程序。")
520
- return ""
521
- except Exception as e:
522
- gr.Warning(f"保存设置失败:{str(e)}")
523
- return ""
524
-
525
- def _create_status_tab(self) -> None:
526
- with gr.Tab("状态"):
527
- gr.Markdown("## 状态")
528
-
529
- with gr.Row():
530
- run_btn = gr.Button("启动", scale=2)
531
- pause_btn = gr.Button("暂停", scale=1)
532
-
533
- # 快速设置控制区域
534
- gr.Markdown("### 快速设置")
535
-
536
- with gr.Row():
537
- select_all_btn = gr.Button("全选", scale=1)
538
- unselect_all_btn = gr.Button("清空", scale=1)
539
- select_produce_only_btn = gr.Button("只选培育", scale=1)
540
- unselect_produce_only_btn = gr.Button("只不选培育", scale=1)
541
-
542
- with gr.Row(elem_classes=["quick-controls-row"]):
543
- purchase_quick = gr.Checkbox(
544
- label="商店",
545
- value=self.current_config.options.purchase.enabled,
546
- interactive=True,
547
- elem_classes=["quick-checkbox"]
548
- )
549
- assignment_quick = gr.Checkbox(
550
- label="工作",
551
- value=self.current_config.options.assignment.enabled,
552
- interactive=True,
553
- elem_classes=["quick-checkbox"]
554
- )
555
- contest_quick = gr.Checkbox(
556
- label="竞赛",
557
- value=self.current_config.options.contest.enabled,
558
- interactive=True,
559
- elem_classes=["quick-checkbox"]
560
- )
561
- produce_quick = gr.Checkbox(
562
- label="培育",
563
- value=self.current_config.options.produce.enabled,
564
- interactive=True,
565
- elem_classes=["quick-checkbox"]
566
- )
567
- mission_reward_quick = gr.Checkbox(
568
- label="任务",
569
- value=self.current_config.options.mission_reward.enabled,
570
- interactive=True,
571
- elem_classes=["quick-checkbox"]
572
- )
573
- club_reward_quick = gr.Checkbox(
574
- label="社团",
575
- value=self.current_config.options.club_reward.enabled,
576
- interactive=True,
577
- elem_classes=["quick-checkbox"]
578
- )
579
- activity_funds_quick = gr.Checkbox(
580
- label="活动费",
581
- value=self.current_config.options.activity_funds.enabled,
582
- interactive=True,
583
- elem_classes=["quick-checkbox"]
584
- )
585
- presents_quick = gr.Checkbox(
586
- label="礼物",
587
- value=self.current_config.options.presents.enabled,
588
- interactive=True,
589
- elem_classes=["quick-checkbox"]
590
- )
591
- capsule_toys_quick = gr.Checkbox(
592
- label="扭蛋",
593
- value=self.current_config.options.capsule_toys.enabled,
594
- interactive=True,
595
- elem_classes=["quick-checkbox"]
596
- )
597
- upgrade_support_card_quick = gr.Checkbox(
598
- label="支援卡",
599
- value=self.current_config.options.upgrade_support_card.enabled,
600
- interactive=True,
601
- elem_classes=["quick-checkbox"]
602
- )
603
-
604
- def _get_end_action_value() -> str:
605
- if self.current_config.options.end_game.shutdown:
606
- return "关机"
607
- if self.current_config.options.end_game.hibernate:
608
- return "休眠"
609
- return "什么都不做"
610
-
611
- end_action_dropdown = gr.Dropdown(
612
- label="完成后:",
613
- choices=["什么都不做", "关机", "休眠"],
614
- value=_get_end_action_value(),
615
- interactive=True
616
- )
617
-
618
- if self._kaa.upgrade_msg:
619
- gr.Markdown('### 配置升级报告')
620
- gr.Markdown(self._kaa.upgrade_msg)
621
- gr.Markdown('脚本报错或者卡住?前往"反馈"选项卡可以快速导出报告!')
622
-
623
- # 添加调试模式警告
624
- if self.current_config.keep_screenshots:
625
- gr.Markdown(
626
- '<div style="color: red; font-size: larger;">当前启用了调试功能「保留截图数据」,调试结束后正常使用时建议关闭此选项!</div>',
627
- elem_classes=["debug-warning"]
628
- )
629
-
630
- task_status = gr.Dataframe(
631
- headers=["任务", "状态"],
632
- value=self.update_task_status(),
633
- label="任务状态"
634
- )
635
-
636
- # MARK: 状态 - 监听函数
637
-
638
- def on_run_click(evt: gr.EventData) -> Tuple[gr.Button, List[List[str]]]:
639
- result = self.toggle_run()
640
- # 如果正在停止,禁用按钮
641
- interactive = not self.is_stopping
642
- button = gr.Button(value=result[0], interactive=interactive)
643
- return button, result[1]
644
-
645
- def on_pause_click(evt: gr.EventData) -> str:
646
- return self.toggle_pause()
647
-
648
- # 快速设置控制的事件处理函数
649
- def save_quick_setting(success_msg: str, failed_msg: str):
650
- """保存快速设置并立即应用"""
651
- try:
652
- # 保存配置
653
- save_config(self.config, "config.json")
654
-
655
- # 尝试热重载配置
656
- if self.reload_config():
657
- gr.Success(success_msg)
658
- else:
659
- gr.Warning(failed_msg)
660
- except Exception as e:
661
- gr.Error(f"✗ 保存失败:{str(e)}")
662
-
663
- def update_and_save_quick_setting(field_name: str, value: bool, display_name: str):
664
- """更新字段,并保存快速设置并立即应用"""
665
-
666
- # 更新配置
667
- if field_name == 'purchase':
668
- self.current_config.options.purchase.enabled = value
669
- elif field_name == 'assignment':
670
- self.current_config.options.assignment.enabled = value
671
- elif field_name == 'contest':
672
- self.current_config.options.contest.enabled = value
673
- elif field_name == 'produce':
674
- self.current_config.options.produce.enabled = value
675
- elif field_name == 'mission_reward':
676
- self.current_config.options.mission_reward.enabled = value
677
- elif field_name == 'club_reward':
678
- self.current_config.options.club_reward.enabled = value
679
- elif field_name == 'activity_funds':
680
- self.current_config.options.activity_funds.enabled = value
681
- elif field_name == 'presents':
682
- self.current_config.options.presents.enabled = value
683
- elif field_name == 'capsule_toys':
684
- self.current_config.options.capsule_toys.enabled = value
685
- elif field_name == 'upgrade_support_card':
686
- self.current_config.options.upgrade_support_card.enabled = value
687
-
688
- # 保存并重载配置
689
- save_quick_setting(
690
- f"✓ {display_name} 已{'启用' if value else '禁用'}",
691
- f"⚠ {display_name} 已保存,但重新加载失败"
692
- )
693
-
694
- def batch_select(default: bool, produce_only: bool, success_msg: str) -> None:
695
- self.current_config.options.purchase.enabled = default
696
- self.current_config.options.assignment.enabled = default
697
- self.current_config.options.contest.enabled = default
698
- self.current_config.options.produce.enabled = produce_only
699
- self.current_config.options.mission_reward.enabled = default
700
- self.current_config.options.club_reward.enabled = default
701
- self.current_config.options.activity_funds.enabled = default
702
- self.current_config.options.presents.enabled = default
703
- self.current_config.options.capsule_toys.enabled = default
704
- self.current_config.options.upgrade_support_card.enabled = default
705
-
706
- save_quick_setting(success_msg, f"⚠ 数据已保存,但重新加载失败")
707
-
708
- # MARK: 状态 - UI回调函数
709
-
710
- run_btn.click(
711
- fn=on_run_click,
712
- outputs=[run_btn, task_status]
713
- )
714
-
715
- pause_btn.click(
716
- fn=on_pause_click,
717
- outputs=[pause_btn]
718
- )
719
-
720
- select_all_btn.click(
721
- fn=lambda: batch_select(True, True, f"✓ 全选成功"),
722
- outputs=[]
723
- )
724
-
725
- unselect_all_btn.click(
726
- fn=lambda: batch_select(False, False, f"✓ 清空成功"),
727
- outputs=[]
728
- )
729
-
730
- select_produce_only_btn.click(
731
- fn=lambda: batch_select(False, True, f"✓ 只选培育成功"),
732
- outputs=[]
733
- )
734
-
735
- unselect_produce_only_btn.click(
736
- fn=lambda: batch_select(True, False, f"✓ 只不选培育成功"),
737
- outputs=[]
738
- )
739
-
740
- # 绑定快速设置控制的事件
741
-
742
- # 以下回调函数均由.change修改为.input,这样回调函数就只会在用户操作时触发,避免全选功能导致反复触发
743
- purchase_quick.input(
744
- fn=lambda x: update_and_save_quick_setting('purchase', x, '商店'),
745
- inputs=[purchase_quick]
746
- )
747
- assignment_quick.input(
748
- fn=lambda x: update_and_save_quick_setting('assignment', x, '工作'),
749
- inputs=[assignment_quick]
750
- )
751
- contest_quick.input(
752
- fn=lambda x: update_and_save_quick_setting('contest', x, '竞赛'),
753
- inputs=[contest_quick]
754
- )
755
- produce_quick.input(
756
- fn=lambda x: update_and_save_quick_setting('produce', x, '培育'),
757
- inputs=[produce_quick]
758
- )
759
- mission_reward_quick.input(
760
- fn=lambda x: update_and_save_quick_setting('mission_reward', x, '任务奖励'),
761
- inputs=[mission_reward_quick]
762
- )
763
- club_reward_quick.input(
764
- fn=lambda x: update_and_save_quick_setting('club_reward', x, '社团奖励'),
765
- inputs=[club_reward_quick]
766
- )
767
- activity_funds_quick.input(
768
- fn=lambda x: update_and_save_quick_setting('activity_funds', x, '活动费'),
769
- inputs=[activity_funds_quick]
770
- )
771
- presents_quick.input(
772
- fn=lambda x: update_and_save_quick_setting('presents', x, '礼物'),
773
- inputs=[presents_quick]
774
- )
775
- capsule_toys_quick.input(
776
- fn=lambda x: update_and_save_quick_setting('capsule_toys', x, '扭蛋'),
777
- inputs=[capsule_toys_quick]
778
- )
779
- upgrade_support_card_quick.input(
780
- fn=lambda x: update_and_save_quick_setting('upgrade_support_card', x, '支援卡升级'),
781
- inputs=[upgrade_support_card_quick]
782
- )
783
-
784
- # 处理完成后操作下拉框
785
- def save_quick_end_action(action: str):
786
- try:
787
- if action == "关机":
788
- self.current_config.options.end_game.shutdown = True
789
- self.current_config.options.end_game.hibernate = False
790
- elif action == "休眠":
791
- self.current_config.options.end_game.shutdown = False
792
- self.current_config.options.end_game.hibernate = True
793
- else: # 什么都不做
794
- self.current_config.options.end_game.shutdown = False
795
- self.current_config.options.end_game.hibernate = False
796
-
797
- save_config(self.config, "config.json")
798
-
799
- # 尝试热重载配置
800
- if self.reload_config():
801
- gr.Success(f"✓ 完成后操作已设置为 {action}")
802
- else:
803
- gr.Warning("⚠ 设置已保存,但重新加载失败")
804
- except Exception as e:
805
- gr.Error(f"✗ 保存失败:{str(e)}")
806
-
807
- end_action_dropdown.change(
808
- fn=save_quick_end_action,
809
- inputs=[end_action_dropdown]
810
- )
811
-
812
- # 添加定时器,分别更新按钮状态和任务状态
813
- def update_run_button_status():
814
- text, interactive = self.get_button_status()
815
- return gr.Button(value=text, interactive=interactive)
816
-
817
- def update_quick_checkboxes():
818
- """更新快速设置区域控件的状态,确保与设置同步"""
819
- end_action_val = _get_end_action_value()
820
- return [
821
- gr.Checkbox(value=self.current_config.options.purchase.enabled),
822
- gr.Checkbox(value=self.current_config.options.assignment.enabled),
823
- gr.Checkbox(value=self.current_config.options.contest.enabled),
824
- gr.Checkbox(value=self.current_config.options.produce.enabled),
825
- gr.Checkbox(value=self.current_config.options.mission_reward.enabled),
826
- gr.Checkbox(value=self.current_config.options.club_reward.enabled),
827
- gr.Checkbox(value=self.current_config.options.activity_funds.enabled),
828
- gr.Checkbox(value=self.current_config.options.presents.enabled),
829
- gr.Checkbox(value=self.current_config.options.capsule_toys.enabled),
830
- gr.Checkbox(value=self.current_config.options.upgrade_support_card.enabled),
831
- gr.Dropdown(value=end_action_val),
832
- ]
833
-
834
- gr.Timer(1.0).tick(
835
- fn=update_run_button_status,
836
- outputs=[run_btn]
837
- )
838
- gr.Timer(1.0).tick(
839
- fn=self.get_pause_button_with_interactive,
840
- outputs=[pause_btn]
841
- )
842
- gr.Timer(2.0).tick(
843
- fn=update_quick_checkboxes,
844
- outputs=[
845
- purchase_quick, assignment_quick, contest_quick, produce_quick,
846
- mission_reward_quick, club_reward_quick, activity_funds_quick, presents_quick,
847
- capsule_toys_quick, upgrade_support_card_quick, end_action_dropdown
848
- ]
849
- )
850
- gr.Timer(1.0).tick(
851
- fn=self.update_task_status,
852
- outputs=[task_status]
853
- )
854
-
855
- def _create_task_tab(self) -> None:
856
- with gr.Tab("任务"):
857
- gr.Markdown("## 执行任务")
858
-
859
- # 第一行:统一的停止和暂停按钮
860
- with gr.Row():
861
- stop_all_btn = gr.Button("停止任务", variant="stop", scale=1)
862
- pause_btn = gr.Button("暂停", scale=1)
863
-
864
- task_result = gr.Markdown("")
865
-
866
- # 获取所有任务并创建列表布局
867
- tasks = list(task_registry.values())
868
- task_buttons = [] # 存储所有任务按钮的引用
869
-
870
- # 为每个任务创建一行:左侧启动按钮,右侧任务名
871
- for task in tasks:
872
- with gr.Row():
873
- with gr.Column(scale=1, min_width=50):
874
- task_btn = gr.Button("启动", variant="primary", size="sm")
875
- task_buttons.append((task_btn, task.name))
876
- with gr.Column(scale=7):
877
- gr.Markdown(f"{task.name}")
878
-
879
- # 事件处理函数
880
- def start_single_task_by_name(task_name: str):
881
- """启动指定任务并返回状态信息及所有按钮的更新状态"""
882
- if self.single_task_running:
883
- return ["已有任务正在运行"] + [gr.Button(interactive=False) for _ in task_buttons]
884
-
885
- # 启动任务
886
- result = self.start_single_task(task_name)
887
- status_msg = result[1]
888
-
889
- # 如果成功启动,禁用所有任务按钮
890
- if self.single_task_running:
891
- disabled_buttons = [gr.Button(value="运行中", interactive=False) for _ in task_buttons]
892
- else:
893
- disabled_buttons = [gr.Button(value="启动", interactive=True) for _ in task_buttons]
894
-
895
- return [status_msg] + disabled_buttons
896
-
897
- def stop_all_tasks():
898
- """停止所有任务并重置按钮状态"""
899
- if not self.single_task_running:
900
- return ["没有正在运行的任务"] + [gr.Button(value="启动", interactive=True) for _ in task_buttons]
901
-
902
- # 停止任务
903
- result = self.stop_single_task()
904
- status_msg = result[1]
905
-
906
- # 如果正在停止中,显示停止中状态
907
- if self.is_single_task_stopping:
908
- disabled_buttons = [gr.Button(value="停止中", interactive=False) for _ in task_buttons]
909
- else:
910
- disabled_buttons = [gr.Button(value="启动", interactive=True) for _ in task_buttons]
911
-
912
- return [status_msg] + disabled_buttons
913
-
914
- def get_task_buttons_status() -> List[gr.Button]:
915
- """获取所有任务按钮的状态"""
916
- if not hasattr(self, 'run_status') or not self.run_status.running:
917
- self.single_task_running = False
918
- self.is_single_task_stopping = False
919
- return [gr.Button(value="启动", interactive=True) for _ in task_buttons]
920
-
921
- if self.is_single_task_stopping:
922
- return [gr.Button(value="停止中", interactive=False) for _ in task_buttons]
923
-
924
- if self.single_task_running:
925
- return [gr.Button(value="运行中", interactive=False) for _ in task_buttons]
926
-
927
- return [gr.Button(value="启动", interactive=True) for _ in task_buttons]
928
-
929
- def get_single_task_status() -> str:
930
- """获取任务状态信息"""
931
- if not hasattr(self, 'run_status'):
932
- return ""
933
-
934
- if not self.run_status.running and self.single_task_running:
935
- # 任务已结束但状态未更新
936
- self.single_task_running = False
937
-
938
- # 检查任务状态
939
- for task_status in self.run_status.tasks:
940
- status = task_status.status
941
- task_name = task_status.task.name
942
-
943
- if status == 'finished':
944
- return f"任务 {task_name} 已完成"
945
- elif status == 'error':
946
- return f"任务 {task_name} 出错"
947
- elif status == 'cancelled':
948
- return f"任务 {task_name} 已取消"
949
-
950
- return "任务已结束"
951
-
952
- if self.single_task_running:
953
- for task_status in self.run_status.tasks:
954
- if task_status.status == 'running':
955
- return f"正在执行任务: {task_status.task.name}"
956
-
957
- return "正在准备执行任务..."
958
-
959
- return ""
960
-
961
- def on_pause_click() -> str:
962
- return self.toggle_pause()
963
-
964
- # 绑定事件 - 为每个任务按钮绑定点击事件
965
- def create_task_handler(task_name: str):
966
- """创建任务处理函数,避免闭包问题"""
967
- def handler():
968
- return start_single_task_by_name(task_name)
969
- return handler
970
-
971
- for task_btn, task_name in task_buttons:
972
- task_btn.click(
973
- fn=create_task_handler(task_name),
974
- outputs=[task_result] + [btn for btn, _ in task_buttons]
975
- )
976
-
977
- # 绑定停止按钮事件
978
- stop_all_btn.click(
979
- fn=stop_all_tasks,
980
- outputs=[task_result] + [btn for btn, _ in task_buttons]
981
- )
982
-
983
- # 绑定暂停按钮事件
984
- pause_btn.click(
985
- fn=on_pause_click,
986
- outputs=[pause_btn]
987
- )
988
-
989
- # 添加定时器更新按钮状态和任务状态
990
- gr.Timer(1.0).tick(
991
- fn=get_task_buttons_status,
992
- outputs=[btn for btn, _ in task_buttons]
993
- )
994
- gr.Timer(1.0).tick(
995
- fn=self.get_pause_button_with_interactive,
996
- outputs=[pause_btn]
997
- )
998
- gr.Timer(1.0).tick(
999
- fn=get_single_task_status,
1000
- outputs=[task_result]
1001
- )
1002
-
1003
- def _create_emulator_settings(self) -> ConfigBuilderReturnValue:
1004
- gr.Markdown("### 模拟器设置")
1005
- current_tab = 0
1006
-
1007
- def _update_emulator_tab_options(impl_value: str, selected_index: int):
1008
- nonlocal current_tab
1009
- current_tab = selected_index
1010
- choices = ['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
1011
- if impl_value not in choices:
1012
- new_value = choices[0]
1013
- else:
1014
- new_value = impl_value
1015
- return new_value
1016
-
1017
- with gr.Tabs(selected=self.current_config.backend.type):
1018
- with gr.Tab("MuMu 12 v4.x", id="mumu12") as tab_mumu12:
1019
- gr.Markdown("已选中 MuMu 12 v4.x 模拟器")
1020
- mumu_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
1021
- mumu_instance = gr.Dropdown(
1022
- label="选择多开实例",
1023
- choices=[],
1024
- interactive=True
1025
- )
1026
- mumu_refresh_btn = gr.Button("刷新")
1027
-
1028
- def refresh_mumu_instances():
1029
- try:
1030
- instances = Mumu12Host.list()
1031
- is_mumu12 = self.current_config.backend.type == 'mumu12'
1032
- current_id = self.current_config.backend.instance_id if is_mumu12 else None
1033
- choices = [(i.name, i.id) for i in instances]
1034
- return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
1035
- except Exception as e:
1036
- logger.exception('Failed to list installed MuMu12')
1037
- gr.Error("获取 MuMu12 模拟器列表失败,请升级模拟器到最新版本。若问题依旧,前往 QQ 群、QQ 频道或 Github 反馈 bug。")
1038
- return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
1039
-
1040
- mumu_refresh_btn.click(
1041
- fn=refresh_mumu_instances,
1042
- outputs=[mumu_instance, mumu_refresh_message]
1043
- )
1044
-
1045
- # 如果当前是 MuMu 模拟器且有配置的 instance_id,立即加载实例列表
1046
- if self.current_config.backend.type == 'mumu12' and self.current_config.backend.instance_id:
1047
- try:
1048
- instances = Mumu12Host.list()
1049
- choices = [(i.name, i.id) for i in instances]
1050
- mumu_instance.choices = choices
1051
- mumu_instance.value = self.current_config.backend.instance_id
1052
- mumu_refresh_message.visible = False
1053
- except Exception as e:
1054
- logger.exception('Failed to auto-load MuMu12 instances')
1055
-
1056
- mumu_background_mode = gr.Checkbox(
1057
- label="MuMu12 模拟器后台保活模式",
1058
- value=self.current_config.backend.mumu_background_mode,
1059
- info=BackendConfig.model_fields['mumu_background_mode'].description,
1060
- interactive=True
1061
- )
1062
-
1063
- with gr.Tab("MuMu 12 v5.x", id="mumu12v5") as tab_mumu12v5:
1064
- gr.Markdown("已选中 MuMu 12 v5.x 模拟器")
1065
- mumu_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
1066
- mumu_instance12v5 = gr.Dropdown(
1067
- label="选择多开实例",
1068
- choices=[],
1069
- interactive=True
1070
- )
1071
- mumu_refresh_btn = gr.Button("刷新")
1072
-
1073
- def refresh_mumu12v5_instances():
1074
- try:
1075
- instances = Mumu12V5Host.list()
1076
- is_mumu12v5 = self.current_config.backend.type == 'mumu12v5'
1077
- current_id = self.current_config.backend.instance_id if is_mumu12v5 else None
1078
- choices = [(i.name, i.id) for i in instances]
1079
- return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
1080
- except Exception as e:
1081
- logger.exception('Failed to list installed MuMu12v5')
1082
- gr.Error("获取 MuMu12 模拟器列表失败,请升级模拟器到最新版本。若问题依旧,前往 QQ 群、QQ 频道或 Github 反馈 bug。")
1083
- return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
1084
-
1085
- mumu_refresh_btn.click(
1086
- fn=refresh_mumu12v5_instances,
1087
- outputs=[mumu_instance12v5, mumu_refresh_message]
1088
- )
1089
-
1090
- # 如果当前是 MuMu 模拟器且有配置的 instance_id,立即加载实例列表
1091
- if self.current_config.backend.type == 'mumu12v5' and self.current_config.backend.instance_id:
1092
- try:
1093
- instances = Mumu12V5Host.list()
1094
- choices = [(i.name, i.id) for i in instances]
1095
- mumu_instance12v5.choices = choices
1096
- mumu_instance12v5.value = self.current_config.backend.instance_id
1097
- mumu_refresh_message.visible = False
1098
- except Exception as e:
1099
- logger.exception('Failed to auto-load MuMu12v5 instances')
1100
-
1101
- mumu_background_mode = gr.Checkbox(
1102
- label="MuMu12v5 模拟器后台保活模式",
1103
- value=self.current_config.backend.mumu_background_mode,
1104
- info=BackendConfig.model_fields['mumu_background_mode'].description,
1105
- interactive=True
1106
- )
1107
-
1108
- with gr.Tab("雷电", id="leidian") as tab_leidian:
1109
- gr.Markdown("已选中雷电模拟器")
1110
- leidian_refresh_message = gr.Markdown("<div style='color: red;'>点击下方「刷新」按钮载入信息</div>", visible=True)
1111
- leidian_instance = gr.Dropdown(
1112
- label="选择多开实例",
1113
- choices=[],
1114
- interactive=True
1115
- )
1116
- leidian_refresh_btn = gr.Button("刷新")
1117
-
1118
- def refresh_leidian_instances():
1119
- try:
1120
- instances = LeidianHost.list()
1121
- is_leidian = self.current_config.backend.type == 'leidian'
1122
- current_id = self.current_config.backend.instance_id if is_leidian else None
1123
- choices = [(i.name, i.id) for i in instances]
1124
- return gr.Dropdown(choices=choices, value=current_id, interactive=True), gr.Markdown(visible=False)
1125
- except Exception as e:
1126
- logger.exception('Failed to list installed Leidian')
1127
- gr.Error("获取雷电模拟器列表失败,请前往 QQ 群、QQ 频道或 Github 反馈 bug。")
1128
- return gr.Dropdown(choices=[], interactive=True), gr.Markdown(visible=True)
1129
-
1130
- leidian_refresh_btn.click(
1131
- fn=refresh_leidian_instances,
1132
- outputs=[leidian_instance, leidian_refresh_message]
1133
- )
1134
-
1135
- # 如果当前是雷电模拟器且有配置的 instance_id,立即加载实例列表
1136
- if self.current_config.backend.type == 'leidian' and self.current_config.backend.instance_id:
1137
- try:
1138
- instances = LeidianHost.list()
1139
- choices = [(i.name, i.id) for i in instances]
1140
- leidian_instance.choices = choices
1141
- leidian_instance.value = self.current_config.backend.instance_id
1142
- leidian_refresh_message.visible = False
1143
- except Exception as e:
1144
- logger.exception('Failed to auto-load Leidian instances')
1145
- # 保持显示刷新提示
1146
-
1147
- with gr.Tab("自定义", id="custom") as tab_custom:
1148
- gr.Markdown("已选中自定义模拟器")
1149
- adb_ip = gr.Textbox(
1150
- value=self.current_config.backend.adb_ip,
1151
- label="ADB IP 地址",
1152
- info=BackendConfig.model_fields['adb_ip'].description,
1153
- interactive=True
1154
- )
1155
- adb_port = gr.Number(
1156
- value=self.current_config.backend.adb_port,
1157
- label="ADB 端口",
1158
- info=BackendConfig.model_fields['adb_port'].description,
1159
- minimum=1,
1160
- maximum=65535,
1161
- step=1,
1162
- interactive=True
1163
- )
1164
- check_emulator = gr.Checkbox(
1165
- label="检查并启动模拟器",
1166
- value=self.current_config.backend.check_emulator,
1167
- info=BackendConfig.model_fields['check_emulator'].description,
1168
- interactive=True
1169
- )
1170
- with gr.Group(visible=self.current_config.backend.check_emulator) as check_emulator_group:
1171
- emulator_path = gr.Textbox(
1172
- value=self.current_config.backend.emulator_path,
1173
- label="模拟器 exe 文件路径",
1174
- info=BackendConfig.model_fields['emulator_path'].description,
1175
- interactive=True
1176
- )
1177
- adb_emulator_name = gr.Textbox(
1178
- value=self.current_config.backend.adb_emulator_name,
1179
- label="ADB 模拟器名称",
1180
- info=BackendConfig.model_fields['adb_emulator_name'].description,
1181
- interactive=True
1182
- )
1183
- emulator_args = gr.Textbox(
1184
- value=self.current_config.backend.emulator_args,
1185
- label="模拟器启动参数",
1186
- info=BackendConfig.model_fields['emulator_args'].description,
1187
- interactive=True
1188
- )
1189
- check_emulator.change(
1190
- fn=lambda x: gr.Group(visible=x),
1191
- inputs=[check_emulator],
1192
- outputs=[check_emulator_group]
1193
- )
1194
-
1195
- with gr.Tab("DMM", id="dmm") as tab_dmm:
1196
- gr.Markdown("已选中 DMM")
1197
- gr.Markdown("**暂停快捷键 <kbd>Ctrl</kbd> + <kbd>F4</kbd>,停止快捷键 <kbd>Ctrl</kbd> + <kbd>F3</kbd>**")
1198
-
1199
- choices = ['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
1200
- screenshot_impl = gr.Dropdown(
1201
- choices=choices,
1202
- value=self.current_config.backend.screenshot_impl,
1203
- label="截图方法",
1204
- info=BackendConfig.model_fields['screenshot_impl'].description,
1205
- interactive=True
1206
- )
1207
-
1208
-
1209
- target_screenshot_interval = gr.Number(
1210
- label="最小截图间隔(秒)",
1211
- value=self.current_config.backend.target_screenshot_interval,
1212
- info=BackendConfig.model_fields['target_screenshot_interval'].description,
1213
- minimum=0,
1214
- step=0.1,
1215
- interactive=True
1216
- )
1217
-
1218
- tab_mumu12.select(fn=partial(_update_emulator_tab_options, selected_index=0), inputs=[screenshot_impl], outputs=[screenshot_impl])
1219
- tab_mumu12v5.select(fn=partial(_update_emulator_tab_options, selected_index=1), inputs=[screenshot_impl], outputs=[screenshot_impl])
1220
- tab_leidian.select(fn=partial(_update_emulator_tab_options, selected_index=2), inputs=[screenshot_impl], outputs=[screenshot_impl])
1221
- tab_custom.select(fn=partial(_update_emulator_tab_options, selected_index=3), inputs=[screenshot_impl], outputs=[screenshot_impl])
1222
- tab_dmm.select(fn=partial(_update_emulator_tab_options, selected_index=4), inputs=[screenshot_impl], outputs=[screenshot_impl])
1223
-
1224
- # 初值
1225
- if self.current_config.backend.type == 'mumu12':
1226
- _update_emulator_tab_options(
1227
- impl_value=self.current_config.backend.screenshot_impl,
1228
- selected_index=0
1229
- )
1230
- elif self.current_config.backend.type == 'mumu12v5':
1231
- _update_emulator_tab_options(
1232
- impl_value=self.current_config.backend.screenshot_impl,
1233
- selected_index=1
1234
- )
1235
- elif self.current_config.backend.type == 'leidian':
1236
- _update_emulator_tab_options(
1237
- impl_value=self.current_config.backend.screenshot_impl,
1238
- selected_index=2
1239
- )
1240
- elif self.current_config.backend.type == 'custom':
1241
- _update_emulator_tab_options(
1242
- impl_value=self.current_config.backend.screenshot_impl,
1243
- selected_index=3
1244
- )
1245
- elif self.current_config.backend.type == 'dmm':
1246
- _update_emulator_tab_options(
1247
- impl_value=self.current_config.backend.screenshot_impl,
1248
- selected_index=4
1249
- )
1250
- else:
1251
- gr.Warning(f"未知的模拟器类型:{self.current_config.backend.type}")
1252
-
1253
- def set_config(_: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1254
- # current_tab is updated by _update_emulator_tab_options
1255
- if current_tab == 0: # Mumu
1256
- self.current_config.backend.type = 'mumu12'
1257
- self.current_config.backend.instance_id = data['_mumu_index']
1258
- self.current_config.backend.mumu_background_mode = data['mumu_background_mode']
1259
- elif current_tab == 1: # Mumu12v5
1260
- self.current_config.backend.type = 'mumu12v5'
1261
- self.current_config.backend.instance_id = data['_mumu12v5_index']
1262
- self.current_config.backend.mumu_background_mode = data['mumu_background_mode']
1263
- elif current_tab == 2: # Leidian
1264
- self.current_config.backend.type = 'leidian'
1265
- self.current_config.backend.instance_id = data['_leidian_index']
1266
- elif current_tab == 3: # Custom
1267
- self.current_config.backend.type = 'custom'
1268
- self.current_config.backend.instance_id = None
1269
- self.current_config.backend.adb_ip = data['adb_ip']
1270
- self.current_config.backend.adb_port = data['adb_port']
1271
- self.current_config.backend.adb_emulator_name = data['adb_emulator_name']
1272
- self.current_config.backend.check_emulator = data['check_emulator']
1273
- self.current_config.backend.emulator_path = data['emulator_path']
1274
- self.current_config.backend.emulator_args = data['emulator_args']
1275
- elif current_tab == 4: # DMM
1276
- self.current_config.backend.type = 'dmm'
1277
- self.current_config.backend.instance_id = None # DMM doesn't use instance_id here
1278
-
1279
- # Common settings for all backend types
1280
- self.current_config.backend.screenshot_impl = data['screenshot_method']
1281
- self.current_config.backend.target_screenshot_interval = data['target_screenshot_interval']
1282
-
1283
- return set_config, {
1284
- 'adb_ip': adb_ip,
1285
- 'adb_port': adb_port,
1286
- 'screenshot_method': screenshot_impl,
1287
- 'target_screenshot_interval': target_screenshot_interval,
1288
- 'check_emulator': check_emulator,
1289
- 'emulator_path': emulator_path,
1290
- 'adb_emulator_name': adb_emulator_name,
1291
- 'emulator_args': emulator_args,
1292
- '_mumu_index': mumu_instance,
1293
- '_leidian_index': leidian_instance,
1294
- '_mumu12v5_index': mumu_instance12v5,
1295
- 'mumu_background_mode': mumu_background_mode
1296
- }
1297
-
1298
- def _create_purchase_settings(self) -> ConfigBuilderReturnValue:
1299
- with gr.Column():
1300
- gr.Markdown("### 商店购买设置")
1301
- purchase_enabled = gr.Checkbox(
1302
- label="启用商店购买",
1303
- value=self.current_config.options.purchase.enabled,
1304
- info=PurchaseConfig.model_fields['enabled'].description
1305
- )
1306
- with gr.Group(visible=self.current_config.options.purchase.enabled) as purchase_group:
1307
- money_enabled = gr.Checkbox(
1308
- label="启用金币购买",
1309
- value=self.current_config.options.purchase.money_enabled,
1310
- info=PurchaseConfig.model_fields['money_enabled'].description
1311
- )
1312
-
1313
- # 添加金币商店商品选择
1314
- money_items = gr.Dropdown(
1315
- multiselect=True,
1316
- choices=list(DailyMoneyShopItems.all()),
1317
- value=self.current_config.options.purchase.money_items,
1318
- label="金币商店购买物品",
1319
- info=PurchaseConfig.model_fields['money_items'].description
1320
- )
1321
-
1322
- money_refresh = gr.Checkbox(
1323
- label="每日一次免费刷新金币商店",
1324
- value=self.current_config.options.purchase.money_refresh,
1325
- info=PurchaseConfig.model_fields['money_refresh'].description
1326
- )
1327
-
1328
- ap_enabled = gr.Checkbox(
1329
- label="启用AP购买",
1330
- value=self.current_config.options.purchase.ap_enabled,
1331
- info=PurchaseConfig.model_fields['ap_enabled'].description
1332
- )
1333
-
1334
- # 转换枚举值为显示文本
1335
- selected_items: List[str] = []
1336
- ap_items_map = {
1337
- APShopItems.PRODUCE_PT_UP: "支援强化点数提升",
1338
- APShopItems.PRODUCE_NOTE_UP: "笔记数提升",
1339
- APShopItems.RECHALLENGE: "重新挑战券",
1340
- APShopItems.REGENERATE_MEMORY: "回忆再生成券"
1341
- }
1342
- for item_value in self.current_config.options.purchase.ap_items:
1343
- item_enum = APShopItems(item_value)
1344
- if item_enum in ap_items_map:
1345
- selected_items.append(ap_items_map[item_enum])
1346
-
1347
- ap_items = gr.Dropdown(
1348
- multiselect=True,
1349
- choices=list(ap_items_map.values()),
1350
- value=selected_items,
1351
- label="AP商店购买物品",
1352
- info=PurchaseConfig.model_fields['ap_items'].description
1353
- )
1354
-
1355
- purchase_enabled.change(
1356
- fn=lambda x: gr.Group(visible=x),
1357
- inputs=[purchase_enabled],
1358
- outputs=[purchase_group]
1359
- )
1360
-
1361
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1362
- config.purchase.enabled = data['purchase_enabled']
1363
- config.purchase.money_enabled = data['money_enabled']
1364
- config.purchase.money_items = [DailyMoneyShopItems(x) for x in data['money_items']]
1365
- config.purchase.money_refresh = data['money_refresh']
1366
- config.purchase.ap_enabled = data['ap_enabled']
1367
- ap_items_enum: List[Literal[0, 1, 2, 3]] = []
1368
- ap_items_map: Dict[str, APShopItems] = {
1369
- "支援强化点数提升": APShopItems.PRODUCE_PT_UP,
1370
- "笔记数提升": APShopItems.PRODUCE_NOTE_UP,
1371
- "重新挑战券": APShopItems.RECHALLENGE,
1372
- "回忆再生成券": APShopItems.REGENERATE_MEMORY
1373
- }
1374
- for item in data['ap_items']:
1375
- if item in ap_items_map:
1376
- ap_items_enum.append(ap_items_map[item].value) # type: ignore
1377
- config.purchase.ap_items = ap_items_enum
1378
-
1379
- return set_config, {
1380
- 'purchase_enabled': purchase_enabled,
1381
- 'money_enabled': money_enabled,
1382
- 'ap_enabled': ap_enabled,
1383
- 'ap_items': ap_items,
1384
- 'money_items': money_items,
1385
- 'money_refresh': money_refresh
1386
- }
1387
-
1388
- def _create_work_settings(self) -> ConfigBuilderReturnValue:
1389
- with gr.Column():
1390
- gr.Markdown("### 工作设置")
1391
- assignment_enabled = gr.Checkbox(
1392
- label="启用工作",
1393
- value=self.current_config.options.assignment.enabled,
1394
- info=AssignmentConfig.model_fields['enabled'].description
1395
- )
1396
- with gr.Group(visible=self.current_config.options.assignment.enabled) as work_group:
1397
- with gr.Row():
1398
- with gr.Column():
1399
- mini_live_reassign = gr.Checkbox(
1400
- label="启用重新分配 MiniLive",
1401
- value=self.current_config.options.assignment.mini_live_reassign_enabled,
1402
- info=AssignmentConfig.model_fields['mini_live_reassign_enabled'].description
1403
- )
1404
- mini_live_duration = gr.Dropdown(
1405
- choices=[4, 6, 12],
1406
- value=self.current_config.options.assignment.mini_live_duration,
1407
- label="MiniLive 工作时长",
1408
- interactive=True,
1409
- info=AssignmentConfig.model_fields['mini_live_duration'].description
1410
- )
1411
- with gr.Column():
1412
- online_live_reassign = gr.Checkbox(
1413
- label="启用重新分配 OnlineLive",
1414
- value=self.current_config.options.assignment.online_live_reassign_enabled,
1415
- info=AssignmentConfig.model_fields['online_live_reassign_enabled'].description
1416
- )
1417
- online_live_duration = gr.Dropdown(
1418
- choices=[4, 6, 12],
1419
- value=self.current_config.options.assignment.online_live_duration,
1420
- label="OnlineLive 工作时长",
1421
- interactive=True,
1422
- info=AssignmentConfig.model_fields['online_live_duration'].description
1423
- )
1424
-
1425
- assignment_enabled.change(
1426
- fn=lambda x: gr.Group(visible=x),
1427
- inputs=[assignment_enabled],
1428
- outputs=[work_group]
1429
- )
1430
- # return assignment_enabled, mini_live_reassign, mini_live_duration, online_live_reassign, online_live_duration
1431
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1432
- config.assignment.enabled = data['assignment_enabled']
1433
- config.assignment.mini_live_reassign_enabled = data['mini_live_reassign']
1434
- config.assignment.mini_live_duration = data['mini_live_duration']
1435
- config.assignment.online_live_reassign_enabled = data['online_live_reassign']
1436
- config.assignment.online_live_duration = data['online_live_duration']
1437
-
1438
- return set_config, {
1439
- 'assignment_enabled': assignment_enabled,
1440
- 'mini_live_reassign': mini_live_reassign,
1441
- 'mini_live_duration': mini_live_duration,
1442
- 'online_live_reassign': online_live_reassign,
1443
- 'online_live_duration': online_live_duration
1444
- }
1445
-
1446
-
1447
- def _create_contest_settings(self) -> ConfigBuilderReturnValue:
1448
- with gr.Column():
1449
- gr.Markdown("### 竞赛设置")
1450
- contest_enabled = gr.Checkbox(
1451
- label="启用竞赛",
1452
- value=self.current_config.options.contest.enabled,
1453
- info=ContestConfig.model_fields['enabled'].description
1454
- )
1455
- with gr.Group(visible=self.current_config.options.contest.enabled) as contest_group:
1456
- select_which_contestant = gr.Dropdown(
1457
- choices=[1, 2, 3],
1458
- value=self.current_config.options.contest.select_which_contestant,
1459
- label="选择第几位竞赛目标",
1460
- interactive=True,
1461
- info=ContestConfig.model_fields['select_which_contestant'].description
1462
- )
1463
-
1464
- when_no_set_choices = [
1465
- ("通知我并跳过竞赛", "remind"),
1466
- ("提醒我并等待手动编成", "wait"),
1467
- ("使用自动编成并提醒我", "auto_set"),
1468
- ("使用自动编成", "auto_set_silent")
1469
- ]
1470
- when_no_set = gr.Dropdown(
1471
- choices=when_no_set_choices,
1472
- value=self.current_config.options.contest.when_no_set,
1473
- label="竞赛队伍未编成时",
1474
- interactive=True,
1475
- info=ContestConfig.model_fields['when_no_set'].description
1476
- )
1477
- contest_enabled.change(
1478
- fn=lambda x: gr.Group(visible=x),
1479
- inputs=[contest_enabled],
1480
- outputs=[contest_group]
1481
- )
31
+ # 1. Initialize Model/Core Application
32
+ # The Kaa instance holds the core application logic and state.
33
+ kaa_instance = kaa_ins
1482
34
 
1483
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1484
- config.contest.enabled = data['contest_enabled']
1485
- config.contest.select_which_contestant = data['select_which_contestant']
1486
- config.contest.when_no_set = data['when_no_set']
1487
-
1488
- return set_config, {
1489
- 'contest_enabled': contest_enabled,
1490
- 'select_which_contestant': select_which_contestant,
1491
- 'when_no_set': when_no_set
1492
- }
1493
-
1494
- def _create_produce_settings(self) -> ConfigBuilderReturnValue:
1495
- with gr.Column():
1496
- gr.Markdown("### 培育设置")
1497
- produce_enabled = gr.Checkbox(
1498
- label="启用培育",
1499
- value=self.current_config.options.produce.enabled,
1500
- info=ProduceConfig.model_fields['enabled'].description
1501
- )
1502
-
1503
- with gr.Group(visible=self.current_config.options.produce.enabled) as produce_group:
1504
- # 培育方案管理区域
1505
- solution_manager = ProduceSolutionManager()
1506
- solutions = solution_manager.list()
1507
- solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1508
-
1509
- selected_solution_id = self.current_config.options.produce.selected_solution_id
1510
- solution_dropdown = gr.Dropdown(
1511
- choices=solution_choices,
1512
- value=selected_solution_id,
1513
- label="当前使用的培育方案",
1514
- interactive=True
1515
- )
1516
-
1517
- produce_count = gr.Number(
1518
- minimum=1,
1519
- value=self.current_config.options.produce.produce_count,
1520
- label="培育次数",
1521
- interactive=True,
1522
- info=ProduceConfig.model_fields['produce_count'].description
1523
- )
1524
-
1525
- produce_timeout_cd = gr.Number(
1526
- minimum=20,
1527
- value=self.current_config.options.produce.produce_timeout_cd,
1528
- label="推荐卡检测用时上限",
1529
- interactive=True,
1530
- info=ProduceConfig.model_fields['produce_timeout_cd'].description
1531
- )
1532
-
1533
- enable_fever_month = gr.Radio(
1534
- label="自动启用强化月间",
1535
- choices=[("不操作", "ignore"), ("自动启用", "on"), ("自动禁用", "off")],
1536
- value=self.current_config.options.produce.enable_fever_month,
1537
- info=ProduceConfig.model_fields['enable_fever_month'].description,
1538
- interactive=True
1539
- )
1540
-
1541
- # 绑定启用状态变化事件
1542
- def on_produce_enabled_change(enabled):
1543
- return gr.Group(visible=enabled)
1544
-
1545
- produce_enabled.change(
1546
- fn=on_produce_enabled_change,
1547
- inputs=[produce_enabled],
1548
- outputs=[produce_group]
1549
- )
1550
-
1551
- # 存储设置Tab中的下拉框引用,供培育Tab使用
1552
- self._settings_solution_dropdown = solution_dropdown
1553
-
1554
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
1555
- config.produce.enabled = data['produce_enabled']
1556
- config.produce.selected_solution_id = data.get('selected_solution_id')
1557
- config.produce.produce_count = data.get('produce_count', 1)
1558
- config.produce.produce_timeout_cd = data.get('produce_timeout_cd', 60)
1559
- config.produce.enable_fever_month = data.get('enable_fever_month', 'ignore')
1560
-
1561
- return set_config, {
1562
- 'produce_enabled': produce_enabled,
1563
- 'selected_solution_id': solution_dropdown,
1564
- 'produce_count': produce_count,
1565
- 'produce_timeout_cd': produce_timeout_cd,
1566
- 'enable_fever_month': enable_fever_month
1567
- }
1568
-
1569
-
1570
- def _create_produce_tab(self) -> None:
1571
- """创建培育Tab"""
1572
- with gr.Tab("培育"):
1573
- gr.Markdown("## 培育管理")
1574
-
1575
- # 培育方案管理区域
1576
- solution_manager = ProduceSolutionManager()
1577
- solutions = solution_manager.list()
1578
- solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1579
-
1580
- selected_solution_id = self.current_config.options.produce.selected_solution_id
1581
- solution_dropdown = gr.Dropdown(
1582
- choices=solution_choices,
1583
- value=selected_solution_id,
1584
- label="选择培育方案",
1585
- interactive=True
1586
- )
1587
-
1588
- # 获取设置Tab中的下拉框引用
1589
- settings_dropdown = getattr(self, '_settings_solution_dropdown', None)
1590
-
1591
- with gr.Row():
1592
- new_solution_btn = gr.Button("新建培育", scale=1)
1593
- delete_solution_btn = gr.Button("删除当前培育", scale=1)
1594
-
1595
- # 培育方案详细设置区域
1596
- with gr.Group() as solution_settings_group:
1597
- # 获取当前选中的方案数据
1598
- current_solution = None
1599
- if selected_solution_id:
1600
- try:
1601
- current_solution = solution_manager.read(selected_solution_id)
1602
- except ProduceSolutionNotFoundError:
1603
- pass
1604
-
1605
- if current_solution is None:
1606
- # 如果没有选中方案,隐藏所有设置组件
1607
- solution_name = gr.Textbox(label="方案名称", value="", visible=False)
1608
- solution_description = gr.Textbox(label="方案描述", value="", visible=False)
1609
- # 其他设置组件都设为不可见
1610
- produce_mode = gr.Dropdown(
1611
- choices=["regular", "pro", "master"],
1612
- label="培育模式",
1613
- info="进行一次 REGULAR 培育需要 ~30min,进行一次 PRO 培育需要 ~1h(具体视设备性能而定)。",
1614
- visible=False
1615
- )
1616
-
1617
- # 添加偶像选择
1618
- idol_choices = []
1619
- for idol in IdolCard.all():
1620
- if idol.is_another:
1621
- idol_choices.append((f'{idol.name} 「{idol.another_name}」', idol.skin_id))
1622
- else:
1623
- idol_choices.append((f'{idol.name}', idol.skin_id))
1624
-
1625
- produce_idols = gr.Dropdown(
1626
- choices=idol_choices,
1627
- label="选择要培育的偶像",
1628
- multiselect=False,
1629
- info="要培育偶像的 IdolCardSkin.id。",
1630
- visible=False
1631
- )
1632
- kotone_warning = gr.Markdown(visible=False)
1633
- auto_set_memory = gr.Checkbox(
1634
- label="自动编成回忆",
1635
- info="是否自动编成回忆。此选项优先级高于回忆编成编号。",
1636
- visible=False
1637
- )
1638
- with gr.Group(visible=False) as memory_sets_group:
1639
- memory_sets = gr.Dropdown(
1640
- choices=[str(i) for i in range(1, 11)],
1641
- label="回忆编成编号",
1642
- multiselect=False,
1643
- info="要使用的回忆编成编号,从 1 开始。",
1644
- visible=False
1645
- )
1646
- auto_set_support = gr.Checkbox(
1647
- label="自动编成支援卡",
1648
- info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。",
1649
- visible=False
1650
- )
1651
- with gr.Group(visible=False) as support_card_sets_group:
1652
- support_card_sets = gr.Dropdown(
1653
- choices=[str(i) for i in range(1, 11)],
1654
- label="支援卡编成编号",
1655
- multiselect=False,
1656
- info="要使用的支援卡编成编号,从 1 开始。",
1657
- visible=False
1658
- )
1659
- use_pt_boost = gr.Checkbox(
1660
- label="使用支援强化 Pt 提升",
1661
- info="是否使用支援强化 Pt 提升。",
1662
- visible=False
1663
- )
1664
- use_note_boost = gr.Checkbox(
1665
- label="使用笔记数提升",
1666
- info="是否使用笔记数提升。",
1667
- visible=False
1668
- )
1669
- follow_producer = gr.Checkbox(
1670
- label="关注租借了支援卡的制作人",
1671
- info="是否关注租借了支援卡的制作人。",
1672
- visible=False
1673
- )
1674
- self_study_lesson = gr.Dropdown(
1675
- choices=['dance', 'visual', 'vocal'],
1676
- label='文化课自习时选项',
1677
- info='自习课类型。',
1678
- visible=False
1679
- )
1680
- prefer_lesson_ap = gr.Checkbox(
1681
- label="SP 课程优先",
1682
- info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。",
1683
- visible=False
1684
- )
1685
- actions_order = gr.Dropdown(
1686
- choices=[(action.display_name, action.value) for action in ProduceAction],
1687
- label="行动优先级",
1688
- info="每一周的行动将会按这里设置的优先级执行。",
1689
- multiselect=True,
1690
- visible=False
1691
- )
1692
- recommend_card_detection_mode = gr.Dropdown(
1693
- choices=[
1694
- (RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
1695
- (RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
1696
- ],
1697
- label="推荐卡检测模式",
1698
- info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。推荐Kotone使用严格模式,其他角色使用正常模式。",
1699
- visible=False
1700
- )
1701
- use_ap_drink = gr.Checkbox(
1702
- label="AP 不足时自动使用 AP 饮料",
1703
- info="AP 不足时自动使用 AP 饮料",
1704
- visible=False
1705
- )
1706
- skip_commu = gr.Checkbox(
1707
- label="检测并跳过交流",
1708
- info="检测并跳过交流",
1709
- visible=False
1710
- )
1711
- save_solution_btn = gr.Button("保存培育方案", variant="primary", visible=False)
1712
- else:
1713
- # 显示选中方案的设置
1714
- solution_name = gr.Textbox(
1715
- label="方案名称",
1716
- value=current_solution.name,
1717
- interactive=True
1718
- )
1719
- solution_description = gr.Textbox(
1720
- label="方案描述",
1721
- value=current_solution.description or "",
1722
- interactive=True
1723
- )
1724
-
1725
- produce_mode = gr.Dropdown(
1726
- choices=["regular", "pro", "master"],
1727
- value=current_solution.data.mode,
1728
- label="培育模式",
1729
- info="进行一次 REGULAR 培育需要 ~30min,进行一次 PRO 培育需要 ~1h(具体视设备性能而定)。"
1730
- )
1731
-
1732
- # 添加偶像选择
1733
- idol_choices = []
1734
- for idol in IdolCard.all():
1735
- if idol.is_another:
1736
- idol_choices.append((f'{idol.name} 「{idol.another_name}」', idol.skin_id))
1737
- else:
1738
- idol_choices.append((f'{idol.name}', idol.skin_id))
1739
-
1740
- produce_idols = gr.Dropdown(
1741
- choices=idol_choices,
1742
- value=current_solution.data.idol,
1743
- label="选择要培育的偶像",
1744
- multiselect=False,
1745
- interactive=True,
1746
- info="要培育偶像的 IdolCardSkin.id。"
1747
- )
1748
-
1749
- has_kotone = bool(current_solution.data.idol and "藤田ことね" in current_solution.data.idol)
1750
- is_strict_mode = current_solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
1751
- kotone_warning = gr.Markdown(
1752
- visible=has_kotone and not is_strict_mode,
1753
- value="使用「藤田ことね」进行培育时,确保将「推荐卡检测模式」设置为「严格模式」"
1754
- )
1755
-
1756
- auto_set_memory = gr.Checkbox(
1757
- label="自动编成回忆",
1758
- value=current_solution.data.auto_set_memory,
1759
- info="是否自动编成回忆。此选项优先级高于回忆编成编号。"
1760
- )
1761
-
1762
- with gr.Group(visible=not current_solution.data.auto_set_memory) as memory_sets_group:
1763
- memory_sets = gr.Dropdown(
1764
- choices=[str(i) for i in range(1, 21)], # 最多20个编成位
1765
- value=str(current_solution.data.memory_set) if current_solution.data.memory_set else None,
1766
- label="回忆编成编号",
1767
- multiselect=False,
1768
- interactive=True,
1769
- info="要使用的回忆编成编号,从 1 开始。"
1770
- )
1771
-
1772
- auto_set_support = gr.Checkbox(
1773
- label="自动编成支援卡",
1774
- value=current_solution.data.auto_set_support_card,
1775
- info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。"
1776
- )
1777
-
1778
- with gr.Group(visible=not current_solution.data.auto_set_support_card) as support_card_sets_group:
1779
- support_card_sets = gr.Dropdown(
1780
- choices=[str(i) for i in range(1, 21)], # 最多20个编成位
1781
- value=str(current_solution.data.support_card_set) if current_solution.data.support_card_set else None,
1782
- label="支援卡编成编号",
1783
- multiselect=False,
1784
- interactive=True,
1785
- info="要使用的支援卡编成编号,从 1 开始。"
1786
- )
1787
- use_pt_boost = gr.Checkbox(
1788
- label="使用支援强化 Pt 提升",
1789
- value=current_solution.data.use_pt_boost,
1790
- info="是否使用支援强化 Pt 提升。"
1791
- )
1792
- use_note_boost = gr.Checkbox(
1793
- label="使用笔记数提升",
1794
- value=current_solution.data.use_note_boost,
1795
- info="是否使用笔记数提升。"
1796
- )
1797
- follow_producer = gr.Checkbox(
1798
- label="关注租借了支援卡的制作人",
1799
- value=current_solution.data.follow_producer,
1800
- info="是否关注租借了支援卡的制作人。"
1801
- )
1802
- self_study_lesson = gr.Dropdown(
1803
- choices=['dance', 'visual', 'vocal'],
1804
- value=current_solution.data.self_study_lesson,
1805
- label='文化课自习时选项',
1806
- info='自习课类型。'
1807
- )
1808
- prefer_lesson_ap = gr.Checkbox(
1809
- label="SP 课程优先",
1810
- value=current_solution.data.prefer_lesson_ap,
1811
- info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。"
1812
- )
1813
- actions_order = gr.Dropdown(
1814
- choices=[(action.display_name, action.value) for action in ProduceAction],
1815
- value=[action.value for action in current_solution.data.actions_order],
1816
- label="行动优先级",
1817
- info="每一周的行动将会按这里设置的优先级执行。",
1818
- multiselect=True
1819
- )
1820
- recommend_card_detection_mode = gr.Dropdown(
1821
- choices=[
1822
- (RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
1823
- (RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
1824
- ],
1825
- value=current_solution.data.recommend_card_detection_mode.value,
1826
- label="推荐卡检测模式",
1827
- info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。推荐Kotone使用严格模式,其他角色使用正常模式。"
1828
- )
1829
- use_ap_drink = gr.Checkbox(
1830
- label="AP 不足时自动使用 AP 饮料",
1831
- value=current_solution.data.use_ap_drink,
1832
- info="AP 不足时自动使用 AP 饮料"
1833
- )
1834
- skip_commu = gr.Checkbox(
1835
- label="检测并跳过交流",
1836
- value=current_solution.data.skip_commu,
1837
- info="检测并跳过交流"
1838
- )
1839
-
1840
- # 添加保存按钮
1841
- save_solution_btn = gr.Button("保存培育方案", variant="primary")
1842
-
1843
- # 添加事件处理
1844
- def update_kotone_warning(selected_idol, recommend_card_detection_mode):
1845
- # Handle case where selected_idol is None (no selection)
1846
- # selected_idol is a single string (skin_id), not a list
1847
- if selected_idol is None:
1848
- has_kotone = False
1849
- else:
1850
- has_kotone = "藤田ことね" in selected_idol
1851
- is_strict_mode = recommend_card_detection_mode == RecommendCardDetectionMode.STRICT.value
1852
- return gr.Markdown(visible=has_kotone and not is_strict_mode)
1853
-
1854
- def on_solution_change(solution_id):
1855
- """当选择的培育方案改变时,更新所有设置组件"""
1856
- if not solution_id:
1857
- return [
1858
- gr.Textbox(value="", visible=False), # solution_name
1859
- gr.Textbox(value="", visible=False), # solution_description
1860
- gr.Dropdown(visible=False), # produce_mode
1861
- gr.Dropdown(visible=False), # produce_idols
1862
- gr.Markdown(visible=False), # kotone_warning
1863
- gr.Checkbox(visible=False), # auto_set_memory
1864
- gr.Group(visible=False), # memory_sets_group
1865
- gr.Dropdown(visible=False), # memory_sets
1866
- gr.Checkbox(visible=False), # auto_set_support
1867
- gr.Group(visible=False), # support_card_sets_group
1868
- gr.Dropdown(visible=False), # support_card_sets
1869
- gr.Checkbox(visible=False), # use_pt_boost
1870
- gr.Checkbox(visible=False), # use_note_boost
1871
- gr.Checkbox(visible=False), # follow_producer
1872
- gr.Dropdown(visible=False), # self_study_lesson
1873
- gr.Checkbox(visible=False), # prefer_lesson_ap
1874
- gr.Dropdown(visible=False), # actions_order
1875
- gr.Dropdown(visible=False), # recommend_card_detection_mode
1876
- gr.Checkbox(visible=False), # use_ap_drink
1877
- gr.Checkbox(visible=False), # skip_commu
1878
- gr.Button(visible=False), # save_solution_btn
1879
- ]
1880
-
1881
- try:
1882
- solution = solution_manager.read(solution_id)
1883
- has_kotone = bool(solution.data.idol and "藤田ことね" in solution.data.idol)
1884
- is_strict_mode = solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
1885
-
1886
- return [
1887
- gr.Textbox(value=solution.name, visible=True),
1888
- gr.Textbox(value=solution.description or "", visible=True),
1889
- gr.Dropdown(value=solution.data.mode, visible=True),
1890
- gr.Dropdown(value=solution.data.idol, visible=True),
1891
- gr.Markdown(visible=has_kotone and not is_strict_mode),
1892
- gr.Checkbox(value=solution.data.auto_set_memory, visible=True),
1893
- gr.Group(visible=not solution.data.auto_set_memory),
1894
- gr.Dropdown(value=str(solution.data.memory_set) if solution.data.memory_set else None, visible=True),
1895
- gr.Checkbox(value=solution.data.auto_set_support_card, visible=True),
1896
- gr.Group(visible=not solution.data.auto_set_support_card),
1897
- gr.Dropdown(value=str(solution.data.support_card_set) if solution.data.support_card_set else None, visible=True),
1898
- gr.Checkbox(value=solution.data.use_pt_boost, visible=True),
1899
- gr.Checkbox(value=solution.data.use_note_boost, visible=True),
1900
- gr.Checkbox(value=solution.data.follow_producer, visible=True),
1901
- gr.Dropdown(value=solution.data.self_study_lesson, visible=True),
1902
- gr.Checkbox(value=solution.data.prefer_lesson_ap, visible=True),
1903
- gr.Dropdown(value=[action.value for action in solution.data.actions_order], visible=True),
1904
- gr.Dropdown(value=solution.data.recommend_card_detection_mode.value, visible=True),
1905
- gr.Checkbox(value=solution.data.use_ap_drink, visible=True),
1906
- gr.Checkbox(value=solution.data.skip_commu, visible=True),
1907
- gr.Button(visible=True), # save_solution_btn
1908
- ]
1909
- except ProduceSolutionNotFoundError:
1910
- gr.Warning(f"培育方案 {solution_id} 不存在")
1911
- return on_solution_change(None)
1912
- except Exception as e:
1913
- gr.Error(f"加载培育方案失败:{str(e)}")
1914
- return on_solution_change(None)
1915
-
1916
- def on_new_solution():
1917
- """创建新的培育方案"""
1918
- try:
1919
- new_solution = solution_manager.new("新培育方案")
1920
- solution_manager.save(new_solution.id, new_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
-
1928
- # 更新培育 Tab 下拉框并保持设置 Tab 当前选中值
1929
- updated_dropdown_produce = gr.Dropdown(choices=solution_choices, value=new_solution.id)
1930
- if settings_dropdown is not None:
1931
- current_selected = self.current_config.options.produce.selected_solution_id
1932
- updated_dropdown_settings = gr.Dropdown(choices=solution_choices, value=current_selected)
1933
- return [updated_dropdown_produce, updated_dropdown_settings]
1934
- else:
1935
- return updated_dropdown_produce
1936
- except Exception as e:
1937
- gr.Error(f"创建培育方案失败:{str(e)}")
1938
- if settings_dropdown is not None:
1939
- return [gr.Dropdown(), gr.Dropdown()]
1940
- else:
1941
- return gr.Dropdown()
1942
-
1943
- def on_delete_solution(solution_id):
1944
- """删除当前培育方案"""
1945
- if not solution_id:
1946
- gr.Warning("请先选择要删除的培育方案")
1947
- if settings_dropdown is not None:
1948
- return [gr.Dropdown(), gr.Dropdown()]
1949
- else:
1950
- return gr.Dropdown()
1951
-
1952
- # 若尝试删除当前正在使用的培育方案,则拒绝并提示
1953
- if solution_id == self.current_config.options.produce.selected_solution_id:
1954
- gr.Warning("不可删除选中方案。请先在设置中选择其他方案,保存后再删除此方案。")
1955
- if settings_dropdown is not None:
1956
- return [gr.Dropdown(), gr.Dropdown()]
1957
- else:
1958
- return gr.Dropdown()
1959
-
1960
- try:
1961
- solution_manager.delete(solution_id)
1962
-
1963
- # 重新列出所有方案
1964
- solutions = solution_manager.list()
1965
- solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
1966
-
1967
- gr.Success("培育方案删除成功")
1968
- # 删除方案后,保持当前培育方案的选择不变
1969
- current_selected = self.current_config.options.produce.selected_solution_id
1970
- if current_selected not in [sol.id for sol in solutions]:
1971
- current_selected = None # 已不存在
1972
-
1973
- updated_dropdown = gr.Dropdown(choices=solution_choices, value=current_selected)
1974
- if settings_dropdown is not None:
1975
- return [updated_dropdown, updated_dropdown]
1976
- else:
1977
- return updated_dropdown
1978
- except Exception as e:
1979
- gr.Error(f"删除培育方案失败:{str(e)}")
1980
- if settings_dropdown is not None:
1981
- return [gr.Dropdown(), gr.Dropdown()]
1982
- else:
1983
- return gr.Dropdown()
1984
-
1985
- def on_save_solution(solution_id, name, description, mode, idols, auto_memory, memory_sets,
1986
- auto_support, support_card_sets, pt_boost, note_boost, follow_producer, study_lesson, prefer_ap,
1987
- actions, detection_mode, ap_drink, skip_commu_val):
1988
- """保存培育方案"""
1989
- if not solution_id:
1990
- gr.Warning("请先选择要保存的培育方案")
1991
- if settings_dropdown is not None:
1992
- return [gr.Dropdown(), gr.Dropdown()]
1993
- else:
1994
- return gr.Dropdown()
1995
-
1996
- try:
1997
- # 构建培育数据
1998
- produce_data = ProduceData(
1999
- mode=mode,
2000
- idol=idols if idols else None,
2001
- memory_set=int(memory_sets) if memory_sets else None,
2002
- support_card_set=int(support_card_sets) if support_card_sets else None,
2003
- auto_set_memory=auto_memory,
2004
- auto_set_support_card=auto_support,
2005
- use_pt_boost=pt_boost,
2006
- use_note_boost=note_boost,
2007
- follow_producer=follow_producer,
2008
- self_study_lesson=study_lesson,
2009
- prefer_lesson_ap=prefer_ap,
2010
- actions_order=[ProduceAction(action) for action in actions] if actions else [],
2011
- recommend_card_detection_mode=RecommendCardDetectionMode(detection_mode),
2012
- use_ap_drink=ap_drink,
2013
- skip_commu=skip_commu_val
2014
- )
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)
2015
39
 
2016
- # 构建方案对象
2017
- solution = ProduceSolution(
2018
- id=solution_id,
2019
- name=name or "未命名方案",
2020
- description=description,
2021
- data=produce_data
2022
- )
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()}")
2023
45
 
2024
- # 保存方案
2025
- solution_manager.save(solution_id, solution)
2026
46
 
2027
- # 重新列出所有方案(确保没有重复项)
2028
- solutions = solution_manager.list()
2029
- solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
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)
2030
51
 
2031
- gr.Success("培育方案保存成功")
2032
- # 根据是否有设置Tab的下拉框来决定返回值
2033
- updated_dropdown = gr.Dropdown(choices=solution_choices, value=solution_id)
2034
- if settings_dropdown is not None:
2035
- return [updated_dropdown, updated_dropdown]
2036
- else:
2037
- return updated_dropdown
2038
- except Exception as e:
2039
- gr.Error(f"保存培育方案失败:{str(e)}")
2040
- if settings_dropdown is not None:
2041
- return [gr.Dropdown(), gr.Dropdown()]
2042
- else:
2043
- return gr.Dropdown()
2044
-
2045
- # 绑定事件
2046
- # 为所有控件绑定change事件,无论是否有选中方案
2047
- auto_set_memory.change(
2048
- fn=lambda x: gr.Group(visible=not x),
2049
- inputs=[auto_set_memory],
2050
- outputs=[memory_sets_group]
2051
- )
2052
-
2053
- auto_set_support.change(
2054
- fn=lambda x: gr.Group(visible=not x),
2055
- inputs=[auto_set_support],
2056
- outputs=[support_card_sets_group]
2057
- )
2058
-
2059
- if current_solution is not None:
2060
- recommend_card_detection_mode.change(
2061
- fn=update_kotone_warning,
2062
- inputs=[produce_idols, recommend_card_detection_mode],
2063
- outputs=kotone_warning
2064
- )
2065
- produce_idols.change(
2066
- fn=update_kotone_warning,
2067
- inputs=[produce_idols, recommend_card_detection_mode],
2068
- outputs=kotone_warning
2069
- )
2070
-
2071
- # 绑定方案管理事件
2072
- solution_dropdown.change(
2073
- fn=on_solution_change,
2074
- inputs=[solution_dropdown],
2075
- outputs=[
2076
- solution_name, solution_description,
2077
- produce_mode, produce_idols, kotone_warning,
2078
- auto_set_memory, memory_sets_group, memory_sets,
2079
- auto_set_support, support_card_sets_group, support_card_sets,
2080
- use_pt_boost, use_note_boost, follow_producer,
2081
- self_study_lesson, prefer_lesson_ap, actions_order,
2082
- recommend_card_detection_mode, use_ap_drink, skip_commu,
2083
- save_solution_btn
2084
- ]
2085
- )
2086
-
2087
- # 准备输出列表,如果设置Tab的下拉框存在,则同时更新
2088
- outputs_for_new = [solution_dropdown]
2089
- outputs_for_delete = [solution_dropdown]
2090
- outputs_for_save = [solution_dropdown]
2091
-
2092
- if settings_dropdown is not None:
2093
- outputs_for_new.append(settings_dropdown)
2094
- outputs_for_delete.append(settings_dropdown)
2095
- outputs_for_save.append(settings_dropdown)
2096
-
2097
- new_solution_btn.click(
2098
- fn=on_new_solution,
2099
- outputs=outputs_for_new
2100
- )
2101
-
2102
- delete_solution_btn.click(
2103
- fn=on_delete_solution,
2104
- inputs=[solution_dropdown],
2105
- outputs=outputs_for_delete
2106
- )
2107
-
2108
- # 绑定保存按钮事件
2109
- save_solution_btn.click(
2110
- fn=on_save_solution,
2111
- inputs=[
2112
- solution_dropdown, solution_name, solution_description,
2113
- produce_mode, produce_idols,
2114
- auto_set_memory, memory_sets, auto_set_support, support_card_sets,
2115
- use_pt_boost, use_note_boost, follow_producer,
2116
- self_study_lesson, prefer_lesson_ap, actions_order,
2117
- recommend_card_detection_mode, use_ap_drink, skip_commu
2118
- ],
2119
- outputs=outputs_for_save
2120
- )
2121
-
2122
- def _create_club_reward_settings(self) -> ConfigBuilderReturnValue:
2123
- with gr.Column():
2124
- gr.Markdown("### 社团奖励设置")
2125
- club_reward_enabled = gr.Checkbox(
2126
- label="启用社团奖励",
2127
- value=self.current_config.options.club_reward.enabled,
2128
- info=ClubRewardConfig.model_fields['enabled'].description
2129
- )
2130
- with gr.Group(visible=self.current_config.options.club_reward.enabled) as club_reward_group:
2131
- selected_note = gr.Dropdown(
2132
- choices=list(DailyMoneyShopItems.note_items()),
2133
- value=self.current_config.options.club_reward.selected_note,
2134
- label="想在社团奖励中获取到的笔记",
2135
- interactive=True,
2136
- info=ClubRewardConfig.model_fields['selected_note'].description
2137
- )
2138
- club_reward_enabled.change(
2139
- fn=lambda x: gr.Group(visible=x),
2140
- inputs=[club_reward_enabled],
2141
- outputs=[club_reward_group]
2142
- )
2143
-
2144
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2145
- config.club_reward.enabled = data['club_reward_enabled']
2146
- config.club_reward.selected_note = DailyMoneyShopItems(data['selected_note'])
2147
-
2148
- return set_config, {
2149
- 'club_reward_enabled': club_reward_enabled,
2150
- 'selected_note': selected_note
2151
- }
2152
-
2153
- def _create_capsule_toys_settings(self) -> ConfigBuilderReturnValue:
2154
- with gr.Column():
2155
- gr.Markdown("### 扭蛋设置")
2156
- capsule_toys_enabled = gr.Checkbox(
2157
- label="是否启用自动扭蛋机",
2158
- value=self.current_config.options.capsule_toys.enabled,
2159
- info=CapsuleToysConfig.model_fields['enabled'].description
2160
- )
2161
- min_value = 0
2162
- max_value = 10
2163
- with gr.Group(visible=self.current_config.options.capsule_toys.enabled) as capsule_toys_group:
2164
- friend_capsule_toys_count = gr.Number(
2165
- value=self.current_config.options.capsule_toys.friend_capsule_toys_count,
2166
- label="好友扭蛋机的扭蛋次数",
2167
- info=CapsuleToysConfig.model_fields['friend_capsule_toys_count'].description,
2168
- minimum=0,
2169
- maximum=5
2170
- )
2171
- sense_capsule_toys_count = gr.Number(
2172
- value=self.current_config.options.capsule_toys.sense_capsule_toys_count,
2173
- label="感性扭蛋机的扭蛋次数",
2174
- info=CapsuleToysConfig.model_fields['sense_capsule_toys_count'].description,
2175
- minimum=0,
2176
- maximum=5
2177
- )
2178
- logic_capsule_toys_count = gr.Number(
2179
- value=self.current_config.options.capsule_toys.logic_capsule_toys_count,
2180
- label="逻辑扭蛋机的扭蛋次数",
2181
- info=CapsuleToysConfig.model_fields['logic_capsule_toys_count'].description,
2182
- minimum=0,
2183
- maximum=5
2184
- )
2185
- anomaly_capsule_toys_count = gr.Number(
2186
- value=self.current_config.options.capsule_toys.anomaly_capsule_toys_count,
2187
- label="非凡扭蛋机的扭蛋次数",
2188
- info=CapsuleToysConfig.model_fields['anomaly_capsule_toys_count'].description,
2189
- minimum=0,
2190
- maximum=5
2191
- )
2192
- capsule_toys_enabled.change(
2193
- fn=lambda x: gr.Group(visible=x),
2194
- inputs=[capsule_toys_enabled],
2195
- outputs=[capsule_toys_group]
2196
- )
2197
-
2198
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2199
- config.capsule_toys.enabled = data['capsule_toys_enabled']
2200
- config.capsule_toys.friend_capsule_toys_count = data['friend_capsule_toys_count']
2201
- config.capsule_toys.sense_capsule_toys_count = data['sense_capsule_toys_count']
2202
- config.capsule_toys.logic_capsule_toys_count = data['logic_capsule_toys_count']
2203
- config.capsule_toys.anomaly_capsule_toys_count = data['anomaly_capsule_toys_count']
2204
-
2205
- return set_config, {
2206
- 'capsule_toys_enabled': capsule_toys_enabled,
2207
- 'friend_capsule_toys_count': friend_capsule_toys_count,
2208
- 'sense_capsule_toys_count': sense_capsule_toys_count,
2209
- 'logic_capsule_toys_count': logic_capsule_toys_count,
2210
- 'anomaly_capsule_toys_count': anomaly_capsule_toys_count
2211
- }
2212
-
2213
-
2214
- def _create_start_game_settings(self) -> ConfigBuilderReturnValue:
2215
- with gr.Column():
2216
- gr.Markdown("### 启动游戏设置")
2217
- start_game_enabled = gr.Checkbox(
2218
- label="是否启用 自动启动游戏",
2219
- value=self.current_config.options.start_game.enabled,
2220
- info=StartGameConfig.model_fields['enabled'].description
2221
- )
2222
- with gr.Group(visible=self.current_config.options.start_game.enabled) as start_game_group:
2223
- start_through_kuyo = gr.Checkbox(
2224
- label="是否通过Kuyo启动游戏",
2225
- value=self.current_config.options.start_game.start_through_kuyo,
2226
- info=StartGameConfig.model_fields['start_through_kuyo'].description
2227
- )
2228
- game_package_name = gr.Textbox(
2229
- value=self.current_config.options.start_game.game_package_name,
2230
- label="游戏包名",
2231
- info=StartGameConfig.model_fields['game_package_name'].description
2232
- )
2233
- kuyo_package_name = gr.Textbox(
2234
- value=self.current_config.options.start_game.kuyo_package_name,
2235
- label="Kuyo包名",
2236
- info=StartGameConfig.model_fields['kuyo_package_name'].description
2237
- )
2238
- disable_gakumas_localify = gr.Checkbox(
2239
- label="禁用 Gakumas Localify 汉化插件",
2240
- value=self.current_config.options.start_game.disable_gakumas_localify,
2241
- info=StartGameConfig.model_fields['disable_gakumas_localify'].description
2242
- )
2243
- dmm_game_path = gr.Textbox(
2244
- value=self.current_config.options.start_game.dmm_game_path or "",
2245
- label="DMM 版游戏路径",
2246
- info=StartGameConfig.model_fields['dmm_game_path'].description,
2247
- placeholder="例:F:\\Games\\gakumas\\gakumas.exe"
2248
- )
2249
- dmm_bypass = gr.Checkbox(
2250
- label="绕过 DMM 启动器直接启动游戏(实验性)",
2251
- value=self.current_config.options.start_game.dmm_bypass,
2252
- info=StartGameConfig.model_fields['dmm_bypass'].description
2253
- )
2254
- start_game_enabled.change(
2255
- fn=lambda x: gr.Group(visible=x),
2256
- inputs=[start_game_enabled],
2257
- outputs=[start_game_group]
2258
- )
2259
-
2260
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2261
- config.start_game.enabled = data['start_game_enabled']
2262
- config.start_game.start_through_kuyo = data['start_through_kuyo']
2263
- config.start_game.game_package_name = data['game_package_name']
2264
- config.start_game.kuyo_package_name = data['kuyo_package_name']
2265
- config.start_game.disable_gakumas_localify = data['disable_gakumas_localify']
2266
- config.start_game.dmm_game_path = data['dmm_game_path'] if data['dmm_game_path'] else None
2267
- config.start_game.dmm_bypass = data['dmm_bypass']
2268
-
2269
- return set_config, {
2270
- 'start_game_enabled': start_game_enabled,
2271
- 'start_through_kuyo': start_through_kuyo,
2272
- 'game_package_name': game_package_name,
2273
- 'kuyo_package_name': kuyo_package_name,
2274
- 'disable_gakumas_localify': disable_gakumas_localify,
2275
- 'dmm_game_path': dmm_game_path,
2276
- 'dmm_bypass': dmm_bypass
2277
- }
2278
-
2279
-
2280
- def _create_end_game_settings(self) -> ConfigBuilderReturnValue:
2281
- with gr.Column():
2282
- gr.Markdown("### 关闭游戏设置")
2283
- gr.Markdown("在所有任务执行完毕后执行下面这些操作:\n(执行单个任务时不会触发)")
2284
- exit_kaa = gr.Checkbox(
2285
- label="退出 kaa",
2286
- value=self.current_config.options.end_game.exit_kaa,
2287
- info=EndGameConfig.model_fields['exit_kaa'].description
2288
- )
2289
- kill_game = gr.Checkbox(
2290
- label="关闭游戏",
2291
- value=self.current_config.options.end_game.kill_game,
2292
- info=EndGameConfig.model_fields['kill_game'].description
2293
- )
2294
- kill_dmm = gr.Checkbox(
2295
- label="关闭 DMM",
2296
- value=self.current_config.options.end_game.kill_dmm,
2297
- info=EndGameConfig.model_fields['kill_dmm'].description
2298
- )
2299
- kill_emulator = gr.Checkbox(
2300
- label="关闭模拟器",
2301
- value=self.current_config.options.end_game.kill_emulator,
2302
- info=EndGameConfig.model_fields['kill_emulator'].description
2303
- )
2304
- shutdown = gr.Checkbox(
2305
- label="关闭系统",
2306
- value=self.current_config.options.end_game.shutdown,
2307
- info=EndGameConfig.model_fields['shutdown'].description
2308
- )
2309
- hibernate = gr.Checkbox(
2310
- label="休眠系统",
2311
- value=self.current_config.options.end_game.hibernate,
2312
- info=EndGameConfig.model_fields['hibernate'].description
2313
- )
2314
- restore_gakumas_localify = gr.Checkbox(
2315
- label="恢复 Gakumas Localify 汉化插件状态",
2316
- value=self.current_config.options.end_game.restore_gakumas_localify,
2317
- info=EndGameConfig.model_fields['restore_gakumas_localify'].description
2318
- )
52
+ # 4. Create and launch the Gradio UI
53
+ blocks = view.create_ui()
2319
54
 
2320
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2321
- config.end_game.exit_kaa = data['exit_kaa']
2322
- config.end_game.kill_game = data['kill_game']
2323
- config.end_game.kill_dmm = data['kill_dmm']
2324
- config.end_game.kill_emulator = data['kill_emulator']
2325
- config.end_game.shutdown = data['shutdown']
2326
- config.end_game.hibernate = data['hibernate']
2327
- config.end_game.restore_gakumas_localify = data['restore_gakumas_localify']
2328
-
2329
- return set_config, {
2330
- 'exit_kaa': exit_kaa,
2331
- 'kill_game': kill_game,
2332
- 'kill_dmm': kill_dmm,
2333
- 'kill_emulator': kill_emulator,
2334
- 'shutdown': shutdown,
2335
- 'hibernate': hibernate,
2336
- 'restore_gakumas_localify': restore_gakumas_localify
2337
- }
55
+ if start_immediately:
56
+ facade.start_all_tasks()
2338
57
 
2339
- def _create_activity_funds_settings(self) -> ConfigBuilderReturnValue:
2340
- with gr.Column():
2341
- gr.Markdown("### 活动费设置")
2342
- activity_funds = gr.Checkbox(
2343
- label="启用收取活动费",
2344
- value=self.current_config.options.activity_funds.enabled,
2345
- info=ActivityFundsConfig.model_fields['enabled'].description
2346
- )
2347
-
2348
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2349
- config.activity_funds.enabled = data['activity_funds']
2350
-
2351
- return set_config, {
2352
- 'activity_funds': activity_funds
2353
- }
2354
-
2355
- def _create_presents_settings(self) -> ConfigBuilderReturnValue:
2356
- with gr.Column():
2357
- gr.Markdown("### 礼物设置")
2358
- presents = gr.Checkbox(
2359
- label="启用收取礼物",
2360
- value=self.current_config.options.presents.enabled,
2361
- info=PresentsConfig.model_fields['enabled'].description
2362
- )
2363
-
2364
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2365
- config.presents.enabled = data['presents']
2366
-
2367
- return set_config, {
2368
- 'presents': presents
2369
- }
2370
-
2371
- def _create_mission_reward_settings(self) -> ConfigBuilderReturnValue:
2372
- with gr.Column():
2373
- gr.Markdown("### 任务奖励设置")
2374
- mission_reward = gr.Checkbox(
2375
- label="启用领取任务奖励",
2376
- value=self.current_config.options.mission_reward.enabled,
2377
- info=MissionRewardConfig.model_fields['enabled'].description
2378
- )
2379
-
2380
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2381
- config.mission_reward.enabled = data['mission_reward']
2382
-
2383
- return set_config, {
2384
- 'mission_reward': mission_reward
2385
- }
2386
-
2387
- def _create_upgrade_support_card_settings(self) -> ConfigBuilderReturnValue:
2388
- with gr.Column():
2389
- gr.Markdown("### 升级支援卡设置")
2390
- upgrade_support_card_enabled = gr.Checkbox(
2391
- label="启用升级支援卡",
2392
- value=self.current_config.options.upgrade_support_card.enabled,
2393
- info=UpgradeSupportCardConfig.model_fields['enabled'].description
2394
- )
58
+ misc_opts = facade.config_service.get_options().misc
59
+ logger.info(f"Launching Gradio UI... LAN exposure: {misc_opts.expose_to_lan}")
2395
60
 
2396
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2397
- config.upgrade_support_card.enabled = data['upgrade_support_card_enabled']
2398
-
2399
- return set_config, {
2400
- 'upgrade_support_card_enabled': upgrade_support_card_enabled
2401
- }
2402
-
2403
- def _create_trace_settings(self) -> ConfigBuilderReturnValue:
2404
- with gr.Column():
2405
- gr.Markdown("### 跟踪设置")
2406
- trace_recommend_card_detection = gr.Checkbox(
2407
- label="跟踪推荐卡检测",
2408
- value=self.current_config.options.trace.recommend_card_detection,
2409
- info=TraceConfig.model_fields['recommend_card_detection'].description,
2410
- interactive=True
2411
- )
2412
-
2413
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2414
- config.trace.recommend_card_detection = data['trace_recommend_card_detection']
2415
-
2416
- return set_config, {
2417
- 'trace_recommend_card_detection': trace_recommend_card_detection
2418
- }
2419
-
2420
- def _create_misc_settings(self) -> ConfigBuilderReturnValue:
2421
- with gr.Column():
2422
- gr.Markdown("### 杂项设置")
2423
- check_update = gr.Dropdown(
2424
- choices=[
2425
- ("启动时检查更新", "startup"),
2426
- ("从不检查更新", "never")
2427
- ],
2428
- value=self.current_config.options.misc.check_update,
2429
- label="检查更新时机",
2430
- info=MiscConfig.model_fields['check_update'].description,
2431
- interactive=True
2432
- )
2433
- auto_install_update = gr.Checkbox(
2434
- label="自动安装更新",
2435
- value=self.current_config.options.misc.auto_install_update,
2436
- info=MiscConfig.model_fields['auto_install_update'].description,
2437
- interactive=True
2438
- )
2439
- expose_to_lan = gr.Checkbox(
2440
- label="允许局域网访问",
2441
- value=self.current_config.options.misc.expose_to_lan,
2442
- info=MiscConfig.model_fields['expose_to_lan'].description,
2443
- interactive=True
2444
- )
2445
- update_channel = gr.Dropdown(
2446
- choices=[
2447
- ("正式版", "release"),
2448
- ("测试版", "beta")
2449
- ],
2450
- value=self.current_config.options.misc.update_channel,
2451
- label="更新渠道",
2452
- info=MiscConfig.model_fields['update_channel'].description,
2453
- interactive=True
2454
- )
2455
- with gr.Row():
2456
- gr.Button("创建桌面快捷方式").click(
2457
- fn=self._create_shortcut_button_click(False),
2458
- outputs=[]
2459
- )
2460
- gr.Button("创建一键启动快捷方式").click(
2461
- fn=self._create_shortcut_button_click(True),
2462
- outputs=[]
2463
- )
2464
-
2465
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2466
- config.misc.check_update = data['check_update']
2467
- config.misc.auto_install_update = data['auto_install_update']
2468
- config.misc.expose_to_lan = data['expose_to_lan']
2469
- config.misc.update_channel = data['update_channel']
2470
-
2471
- return set_config, {
2472
- 'check_update': check_update,
2473
- 'auto_install_update': auto_install_update,
2474
- 'expose_to_lan': expose_to_lan,
2475
- 'update_channel': update_channel
2476
- }
2477
-
2478
- def _create_shortcut_button_click(self, start_immediately: bool) -> Callable[[], None]:
2479
- def _inner():
2480
- create_desktop_shortcut(start_immediately)
2481
- gr.Success("快捷方式创建成功")
2482
- return _inner
2483
-
2484
- def _create_idle_settings(self) -> ConfigBuilderReturnValue:
2485
- with gr.Column():
2486
- gr.Markdown("### 闲置挂机")
2487
- idle_enabled = gr.Checkbox(
2488
- label="启用闲置挂机(任意键暂停、闲置自动恢复)",
2489
- value=self.current_config.options.idle.enabled,
2490
- info=IdleModeConfig.model_fields['enabled'].description,
2491
- interactive=True
2492
- )
2493
- idle_seconds = gr.Slider(
2494
- label="自动恢复触发时间(秒)",
2495
- minimum=30,
2496
- maximum=3600,
2497
- step=10,
2498
- value=self.current_config.options.idle.idle_seconds,
2499
- info=IdleModeConfig.model_fields['idle_seconds'].description,
2500
- interactive=True
2501
- )
2502
- idle_minimize_on_pause = gr.Checkbox(
2503
- label="暂停时最小化游戏窗口",
2504
- value=self.current_config.options.idle.minimize_on_pause,
2505
- info=IdleModeConfig.model_fields['minimize_on_pause'].description,
2506
- interactive=True
2507
- )
2508
-
2509
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2510
- config.idle.enabled = data['idle_enabled']
2511
- config.idle.idle_seconds = int(data['idle_seconds'])
2512
- config.idle.minimize_on_pause = data['idle_minimize_on_pause']
2513
-
2514
- return set_config, {
2515
- 'idle_enabled': idle_enabled,
2516
- 'idle_seconds': idle_seconds,
2517
- 'idle_minimize_on_pause': idle_minimize_on_pause,
2518
- }
2519
-
2520
- def _create_debug_settings(self) -> ConfigBuilderReturnValue:
2521
- """调试设置:仅在调试时使用"""
2522
- with gr.Column():
2523
- gr.Markdown("### 调试设置")
2524
- gr.Markdown('<div style="color: red;">仅供调试使用。正常运行时务必关闭下面所有的选项。</div>')
2525
-
2526
- keep_screenshots = gr.Checkbox(
2527
- label="保留截图数据",
2528
- value=self.current_config.keep_screenshots,
2529
- info=UserConfig.model_fields['keep_screenshots'].description,
2530
- interactive=True
2531
- )
2532
-
2533
- trace_recommend_card_detection = gr.Checkbox(
2534
- label="跟踪推荐卡检测",
2535
- value=self.current_config.options.trace.recommend_card_detection,
2536
- info=TraceConfig.model_fields['recommend_card_detection'].description,
2537
- interactive=True
2538
- )
2539
-
2540
- def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
2541
- # 保留截图数据属于 UserConfig
2542
- self.current_config.keep_screenshots = data['keep_screenshots']
2543
- # 跟踪推荐卡检测属于 BaseConfig.trace
2544
- config.trace.recommend_card_detection = data['trace_recommend_card_detection']
2545
-
2546
- return set_config, {
2547
- 'keep_screenshots': keep_screenshots,
2548
- 'trace_recommend_card_detection': trace_recommend_card_detection
2549
- }
2550
-
2551
- def _create_settings_tab(self) -> None:
2552
- with gr.Tab("设置"):
2553
- gr.Markdown("## 设置 <span style='color: red; font-size: 0.8rem; font-weight: normal;'>设置修改后需要保存(网页底部)才会生效!</span>")
2554
-
2555
- # 模拟器设置
2556
- emulator_settings = self._create_emulator_settings()
2557
-
2558
- # 商店购买设置
2559
- purchase_settings = self._create_purchase_settings()
2560
-
2561
- # 活动费设置
2562
- activity_funds_settings = self._create_activity_funds_settings()
2563
-
2564
- # 礼物设置
2565
- presents_settings = self._create_presents_settings()
2566
-
2567
- # 工作设置
2568
- work_settings = self._create_work_settings()
2569
-
2570
- # 竞赛设置
2571
- contest_settings = self._create_contest_settings()
2572
-
2573
- # 培育设置
2574
- produce_settings = self._create_produce_settings()
2575
-
2576
- # 任务奖励设置
2577
- mission_reward_settings = self._create_mission_reward_settings()
2578
-
2579
- # 社团奖励设置
2580
- club_reward_settings = self._create_club_reward_settings()
2581
-
2582
- # 扭蛋设置
2583
- capsule_toys_settings = self._create_capsule_toys_settings()
2584
-
2585
- # 升级支援卡设置
2586
- upgrade_support_card_settings = self._create_upgrade_support_card_settings()
2587
-
2588
- # 启动游戏设置
2589
- start_game_settings = self._create_start_game_settings()
2590
-
2591
- # 关闭游戏设置
2592
- end_game_settings = self._create_end_game_settings()
2593
-
2594
- # 杂项设置
2595
- misc_settings = self._create_misc_settings()
2596
-
2597
- # 闲置挂机设置
2598
- idle_settings = self._create_idle_settings()
2599
-
2600
- # 调试设置(放在最后)
2601
- debug_settings = self._create_debug_settings()
2602
-
2603
- save_btn = gr.Button("保存设置")
2604
- result = gr.Markdown()
2605
-
2606
- # 收集所有设置组件
2607
- all_return_values = [
2608
- emulator_settings,
2609
- purchase_settings,
2610
- activity_funds_settings,
2611
- presents_settings,
2612
- work_settings,
2613
- contest_settings,
2614
- produce_settings,
2615
- mission_reward_settings,
2616
- club_reward_settings,
2617
- capsule_toys_settings,
2618
- upgrade_support_card_settings,
2619
- start_game_settings,
2620
- end_game_settings,
2621
- misc_settings,
2622
- idle_settings,
2623
- debug_settings
2624
- ] # list of (set_func, { 'key': component, ... })
2625
- all_components = [list(ret[1].values()) for ret in all_return_values] # [[c1, c2], [c3], ...]
2626
- all_components = list(chain(*all_components)) # [c1, c2, c3, ...]
2627
- save_btn.click(
2628
- fn=partial(self.save_settings2, all_return_values),
2629
- inputs=all_components,
2630
- outputs=result
2631
- )
2632
-
2633
- def _create_log_tab(self) -> None:
2634
- with gr.Tab("反馈"):
2635
- gr.Markdown("## 反馈")
2636
- gr.Markdown('脚本报错或者卡住?在这里填写信息可以快速反馈!')
2637
- with gr.Column():
2638
- report_title = gr.Textbox(label="标题", placeholder="用一句话概括问题")
2639
- report_type = gr.Dropdown(label="反馈类型", choices=["bug"], value="bug", interactive=False)
2640
- report_description = gr.Textbox(label="描述", lines=5, placeholder="详细描述问题。例如:什么时候出错、是否每次都出错、出错时的步骤是什么")
2641
- with gr.Row():
2642
- upload_report_btn = gr.Button("上传")
2643
- save_local_report_btn = gr.Button("保存至本地")
2644
-
2645
- result_text = gr.Markdown("等待操作\n\n\n")
2646
-
2647
- def on_upload_click(title: str, description: str):
2648
- yield from _save_bug_report(title, description, self._kaa.version, upload=True)
2649
-
2650
- def on_save_local_click(title: str, description: str):
2651
- yield from _save_bug_report(title, description, self._kaa.version, upload=False)
2652
-
2653
- upload_report_btn.click(
2654
- fn=on_upload_click,
2655
- inputs=[report_title, report_description],
2656
- outputs=[result_text]
2657
- )
2658
- save_local_report_btn.click(
2659
- fn=on_save_local_click,
2660
- inputs=[report_title, report_description],
2661
- outputs=[result_text]
2662
- )
2663
-
2664
- def _create_whats_new_tab(self) -> None:
2665
- """创建更新标签页"""
2666
- with gr.Tab("更新"):
2667
- gr.Markdown("## 版本管理")
2668
-
2669
- # 更新日志
2670
- with gr.Accordion("更新日志", open=False):
2671
- from kaa.metadata import WHATS_NEW
2672
- gr.Markdown(WHATS_NEW)
2673
-
2674
- # 载入信息按钮
2675
- load_info_btn = gr.Button("载入信息", variant="primary")
2676
-
2677
- # 状态信息
2678
- status_text = gr.Markdown("")
2679
-
2680
- # 版本选择下拉框(用于安装)
2681
- version_dropdown = gr.Dropdown(
2682
- label="选择要安装的版本",
2683
- choices=[],
2684
- value=None,
2685
- visible=False,
2686
- interactive=True
2687
- )
2688
-
2689
- # 安装选定版本按钮
2690
- install_selected_btn = gr.Button("安装选定版本", visible=False)
2691
-
2692
- def list_all_versions():
2693
- """列出所有可用版本"""
2694
- import logging
2695
- import re
2696
- logger = logging.getLogger(__name__)
2697
-
2698
- # 检查启动器版本
2699
- def check_launcher_version():
2700
- """通过读取bootstrap.pyz/meta.py获取启动器版本"""
2701
- import zipfile
2702
- try:
2703
- bootstrap_path = os.path.join(os.getcwd(), "bootstrap.pyz")
2704
- if not os.path.exists(bootstrap_path):
2705
- logger.warning("启动器文件不存在")
2706
- gr.Warning("启动器文件不存在")
2707
- return None
2708
-
2709
- # 尝试从bootstrap.pyz中读取meta.py
2710
- try:
2711
- with zipfile.ZipFile(bootstrap_path, 'r') as zip_file:
2712
- if 'meta.py' in zip_file.namelist():
2713
- # 读取meta.py内容
2714
- meta_content = zip_file.read('meta.py').decode('utf-8')
2715
-
2716
- # 创建一个安全的执行环境
2717
- exec_globals = {}
2718
- exec_locals = {}
2719
-
2720
- # 执行meta.py内容
2721
- exec(meta_content, exec_globals, exec_locals)
2722
-
2723
- # 获取VERSION变量
2724
- if 'VERSION' in exec_locals:
2725
- version = exec_locals['VERSION']
2726
- # 移除v前缀(如果有)
2727
- if isinstance(version, str) and version.startswith('v'):
2728
- version = version[1:]
2729
- logger.info(f"从meta.py读取到启动器版本: {version}")
2730
- return str(version)
2731
- else:
2732
- logger.warning("meta.py中未找到VERSION变量")
2733
- gr.Warning("meta.py中未找到VERSION变量")
2734
- return None
2735
- else:
2736
- logger.warning("bootstrap.pyz中未找到meta.py文件")
2737
- gr.Warning("bootstrap.pyz中未找到meta.py文件")
2738
- return None
2739
- except zipfile.BadZipFile:
2740
- # 不是有效的zip文件,可能是旧版本启动器
2741
- logger.info("bootstrap.pyz不是有效的zip文件,判断为旧版本启动器")
2742
- return "0.4.x"
2743
- except Exception as e:
2744
- logger.warning(f"读取bootstrap.pyz失败: {str(e)}")
2745
- gr.Warning(f"读取bootstrap.pyz失败: {str(e)}")
2746
- return None
2747
-
2748
- except Exception as e:
2749
- logger.warning(f"检查启动器版本失败: {str(e)}")
2750
- gr.Warning(f"检查启动器版本失败: {str(e)}")
2751
- return None
2752
-
2753
- try:
2754
- # 构建命令,使用清华镜像源
2755
- cmd = [
2756
- sys.executable, "-m", "pip", "index", "versions", "ksaa", "--json", "--pre",
2757
- "--index-url", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple",
2758
- "--trusted-host", "mirrors.tuna.tsinghua.edu.cn"
2759
- ]
2760
- logger.info(f"执行命令: {' '.join(cmd)}")
2761
-
2762
- # 使用 pip index versions --json 来获取版本信息
2763
- result = subprocess.run(
2764
- cmd,
2765
- capture_output=True,
2766
- text=True,
2767
- timeout=30
2768
- )
2769
-
2770
- logger.info(f"命令返回码: {result.returncode}")
2771
- if result.stdout:
2772
- logger.info(f"命令输出: {result.stdout[:500]}...") # 只记录前500字符
2773
- if result.stderr:
2774
- logger.warning(f"命令错误输出: {result.stderr}")
2775
-
2776
- if result.returncode != 0:
2777
- error_msg = f"获取版本列表失败: {result.stderr}"
2778
- logger.error(error_msg)
2779
- return (
2780
- error_msg,
2781
- gr.Button(value="载入信息", interactive=True),
2782
- gr.Dropdown(visible=False),
2783
- gr.Button(visible=False)
2784
- )
2785
-
2786
- # 解析 JSON 输出
2787
- try:
2788
- data = json.loads(result.stdout)
2789
- versions = data.get("versions", [])
2790
- latest_version = data.get("latest", "")
2791
- installed_version = data.get("installed_version", "")
2792
-
2793
- logger.info(f"解析到 {len(versions)} 个版本")
2794
- logger.info(f"最新版本: {latest_version}")
2795
- logger.info(f"已安装版本: {installed_version}")
2796
-
2797
- except json.JSONDecodeError as e:
2798
- error_msg = f"解析版本信息失败: {str(e)}"
2799
- logger.error(error_msg)
2800
- return (
2801
- error_msg,
2802
- gr.Button(value="载入信息", interactive=True),
2803
- gr.Dropdown(visible=False),
2804
- gr.Button(visible=False)
2805
- )
2806
-
2807
- if not versions:
2808
- error_msg = "未找到可用版本"
2809
- logger.warning(error_msg)
2810
- return (
2811
- error_msg,
2812
- gr.Button(value="载入信息", interactive=True),
2813
- gr.Dropdown(visible=False),
2814
- gr.Button(visible=False)
2815
- )
2816
-
2817
- # 检查启动器版本
2818
- launcher_version = check_launcher_version()
2819
-
2820
- # 构建状态信息
2821
- status_info = []
2822
- if installed_version:
2823
- status_info.append(f"**当前安装版本:** {installed_version}")
2824
- if latest_version:
2825
- status_info.append(f"**最新版本:** {latest_version}")
2826
- if launcher_version:
2827
- if launcher_version == "0.4.x":
2828
- status_info.append(f"**启动器版本:** < v0.5.0 (旧版本)")
2829
- else:
2830
- status_info.append(f"**启动器版本:** v{launcher_version}")
2831
- else:
2832
- status_info.append(f"**启动器版本:** 未知")
2833
- status_info.append(f"**找到 {len(versions)} 个可用版本**")
2834
-
2835
- status_message = "\n\n".join(status_info)
2836
- logger.info(f"版本信息载入完成: {status_message}")
2837
-
2838
- # 返回更新后的组件
2839
- return (
2840
- status_message,
2841
- gr.Button(value="载入信息", interactive=True),
2842
- gr.Dropdown(choices=versions, value=versions[0] if versions else None, visible=True, label="选择要安装的版本"),
2843
- gr.Button(visible=True, value="安装选定版本")
2844
- )
2845
-
2846
- except subprocess.TimeoutExpired:
2847
- error_msg = "获取版本列表超时"
2848
- logger.error(error_msg)
2849
- return (
2850
- error_msg,
2851
- gr.Button(value="载入信息", interactive=True),
2852
- gr.Dropdown(visible=False),
2853
- gr.Button(visible=False)
2854
- )
2855
- except Exception as e:
2856
- error_msg = f"获取版本列表失败: {str(e)}"
2857
- logger.error(error_msg)
2858
- return (
2859
- error_msg,
2860
- gr.Button(value="载入信息", interactive=True),
2861
- gr.Dropdown(visible=False),
2862
- gr.Button(visible=False)
2863
- )
2864
-
2865
- def install_selected_version(selected_version: str):
2866
- """安装选定的版本"""
2867
- import logging
2868
- import threading
2869
- import time
2870
- import re
2871
- logger = logging.getLogger(__name__)
2872
-
2873
- if not selected_version:
2874
- error_msg = "请先选择一个版本"
2875
- logger.warning(error_msg)
2876
- return error_msg
2877
-
2878
-
2879
- def compare_version(version1: str, version2: str) -> int:
2880
- """比较版本号,返回-1(v1<v2), 0(v1==v2), 1(v1>v2)"""
2881
- # 处理特殊标记
2882
- if version1 == "0.4.x":
2883
- version1 = "0.4.0"
2884
-
2885
- def parse_version(v):
2886
- # 移除v前缀并解析版本号和预发布标识
2887
- v = v.lstrip('v')
2888
- if 'b' in v:
2889
- base, beta = v.split('b', 1)
2890
- return tuple(map(int, base.split('.'))) + (-1, int(beta))
2891
- elif 'a' in v:
2892
- base, alpha = v.split('a', 1)
2893
- return tuple(map(int, base.split('.'))) + (-2, int(alpha))
2894
- elif 'rc' in v:
2895
- base, rc = v.split('rc', 1)
2896
- return tuple(map(int, base.split('.'))) + (-0.5, int(rc))
2897
- else:
2898
- return tuple(map(int, v.split('.'))) + (0, 0)
2899
-
2900
- v1_parsed = parse_version(version1)
2901
- v2_parsed = parse_version(version2)
2902
-
2903
- if v1_parsed < v2_parsed:
2904
- return -1
2905
- elif v1_parsed > v2_parsed:
2906
- return 1
2907
- else:
2908
- return 0
2909
-
2910
- # 检查启动器版本进行兼容性验证
2911
- def check_launcher_version_simple():
2912
- """通过读取bootstrap.pyz/meta.py获取启动器版本"""
2913
- import zipfile
2914
- try:
2915
- bootstrap_path = os.path.join(os.getcwd(), "bootstrap.pyz")
2916
- if not os.path.exists(bootstrap_path):
2917
- logger.warning("启动器文件不存在")
2918
- gr.Warning("启动器文件不存在")
2919
- return None
2920
-
2921
- # 尝试从bootstrap.pyz中读取meta.py
2922
- try:
2923
- with zipfile.ZipFile(bootstrap_path, 'r') as zip_file:
2924
- if 'meta.py' in zip_file.namelist():
2925
- # 读取meta.py内容
2926
- meta_content = zip_file.read('meta.py').decode('utf-8')
2927
-
2928
- # 创建一个安全的执行环境
2929
- exec_globals = {}
2930
- exec_locals = {}
2931
-
2932
- # 执行meta.py内容
2933
- exec(meta_content, exec_globals, exec_locals)
2934
-
2935
- # 获取VERSION变量
2936
- if 'VERSION' in exec_locals:
2937
- version = exec_locals['VERSION']
2938
- # 移除v前缀(如果有)
2939
- if isinstance(version, str) and version.startswith('v'):
2940
- version = version[1:]
2941
- logger.info(f"从meta.py读取到启动器版本: {version}")
2942
- return str(version)
2943
- else:
2944
- logger.warning("meta.py中未找到VERSION变量")
2945
- gr.Warning("meta.py中未找到VERSION变量")
2946
- return None
2947
- else:
2948
- logger.warning("bootstrap.pyz中未找到meta.py文件")
2949
- gr.Warning("bootstrap.pyz中未找到meta.py文件")
2950
- return None
2951
- except zipfile.BadZipFile:
2952
- # 不是有效的zip文件,可能是旧版本启动器
2953
- logger.info("bootstrap.pyz不是有效的zip文件,判断为旧版本启动器")
2954
- return "0.4.x"
2955
- except Exception as e:
2956
- logger.warning(f"读取bootstrap.pyz失败: {str(e)}")
2957
- gr.Warning(f"读取bootstrap.pyz失败: {str(e)}")
2958
- return None
2959
-
2960
- except Exception as e:
2961
- logger.warning(f"检查启动器版本失败: {str(e)}")
2962
- gr.Warning(f"检查启动器版本失败: {str(e)}")
2963
- return None
2964
-
2965
- # 执行启动器版本检查
2966
- launcher_version = check_launcher_version_simple()
2967
- logger.info(f"检测到启动器版本: {launcher_version}")
2968
-
2969
- # 版本兼容性检查
2970
- if launcher_version:
2971
- if compare_version(launcher_version, "0.5.0") < 0:
2972
- # 启动器版本低于0.5.0
2973
- logger.warning(f"启动器版本 {launcher_version} 低于 v0.5.0")
2974
-
2975
- # 检查要安装的版本是否 >= v2025.9b1
2976
- if compare_version(selected_version, "2025.9b1") >= 0:
2977
- error_msg = (
2978
- f"❌ 版本兼容性错误\n\n"
2979
- f"启动器版本: {launcher_version}\n"
2980
- f"要安装的版本: {selected_version}\n\n"
2981
- f"v2025.9b1 及以上版本需要**启动器 v0.5.0*** 或更高版本支持。\n"
2982
- f"请先升级启动器到 v0.5.0 以上版本。[查看帮助](https://www.kdocs.cn/l/cetCY8mGKHLj?linkname=UhPH1itaSv)"
2983
- )
2984
- logger.error(error_msg)
2985
- return error_msg
2986
- else:
2987
- # 版本兼容,但给出警告
2988
- logger.warning(f"启动器版本较低,建议升级到 v0.5.0 以上")
2989
- gr.Warning("启动器版本较低,建议升级到 v0.5.0 以上")
2990
- else:
2991
- # 无法获取启动器版本,给出警告但继续
2992
- logger.warning("无法获取启动器版本,可以继续安装但可能存在兼容性问题")
2993
- gr.Warning("无法获取启动器版本,可以继续安装但可能存在兼容性问题")
2994
-
2995
- def install_and_exit():
2996
- """在后台线程中执行安装并退出程序"""
2997
- try:
2998
- # 等待一小段时间确保UI响应已返回
2999
- time.sleep(1)
3000
-
3001
- # 构建启动器命令
3002
- bootstrap_path = os.path.join(os.getcwd(), "bootstrap.pyz")
3003
- cmd = [sys.executable, bootstrap_path, f"--install-version={selected_version}"]
3004
- logger.info(f"开始通过启动器安装版本 {selected_version}")
3005
- logger.info(f"执行命令: {' '.join(cmd)}")
3006
-
3007
- # 启动启动器进程(不等待完成)
3008
- subprocess.Popen(
3009
- cmd,
3010
- cwd=os.getcwd(),
3011
- creationflags=subprocess.CREATE_NEW_CONSOLE if os.name == 'nt' else 0
3012
- )
3013
-
3014
- # 退出当前程序
3015
- logger.info("安装即将开始,正在退出当前程序...")
3016
- os._exit(0)
3017
-
3018
- except Exception as e:
3019
- raise
3020
-
3021
- try:
3022
- # 在后台线程中执行安装和退出
3023
- install_thread = threading.Thread(target=install_and_exit, daemon=True)
3024
- install_thread.start()
3025
-
3026
- return f"正在启动器中安装版本 {selected_version},程序将自动重启..."
3027
-
3028
- except Exception as e:
3029
- error_msg = f"启动安装进程失败: {str(e)}"
3030
- logger.error(error_msg)
3031
- return error_msg
3032
-
3033
- def load_info_with_button_state():
3034
- """载入信息并管理按钮状态"""
3035
- import logging
3036
- logger = logging.getLogger(__name__)
3037
-
3038
- logger.info("开始载入版本信息")
3039
-
3040
- # 先禁用按钮
3041
- yield (
3042
- "正在载入版本信息...",
3043
- gr.Button(value="载入中...", interactive=False),
3044
- gr.Dropdown(visible=False),
3045
- gr.Button(visible=False)
3046
- )
3047
-
3048
- # 执行载入操作
3049
- result = list_all_versions()
3050
- logger.info("版本信息载入操作完成")
3051
- yield result
3052
-
3053
- # 绑定事件
3054
- load_info_btn.click(
3055
- fn=load_info_with_button_state,
3056
- outputs=[status_text, load_info_btn, version_dropdown, install_selected_btn]
3057
- )
3058
-
3059
- install_selected_btn.click(
3060
- fn=install_selected_version,
3061
- inputs=[version_dropdown],
3062
- outputs=[status_text]
3063
- )
3064
-
3065
- def _create_screen_tab(self) -> None:
3066
- with gr.Tab("画面"):
3067
- gr.Markdown("## 当前设备画面")
3068
- refresh_btn = gr.Button("刷新画面", variant="primary")
3069
- WIDTH = 720 // 3
3070
- HEIGHT = 1280 // 3
3071
- last_update_text = gr.Markdown("上次更新时间:无数据")
3072
- screenshot_display = gr.Image(type="numpy", width=WIDTH, height=HEIGHT)
3073
-
3074
- def update_screenshot():
3075
- ctx = ContextStackVars.current()
3076
- if ctx is None:
3077
- return [None, "上次更新时间:无上下文数据"]
3078
- screenshot = ctx._screenshot
3079
- if screenshot is None:
3080
- return [None, "上次更新时间:无截图数据"]
3081
- screenshot = cv2.cvtColor(screenshot, cv2.COLOR_BGR2RGB)
3082
- return screenshot, f"上次更新时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
3083
-
3084
- refresh_btn.click(
3085
- fn=update_screenshot,
3086
- outputs=[screenshot_display, last_update_text]
3087
- )
61
+ blocks.launch(
62
+ share=misc_opts.expose_to_lan,
63
+ inbrowser=True,
64
+ css=custom_css
65
+ )
3088
66
 
3089
- def _load_config(self) -> None:
3090
- # 加载配置文件
3091
- config_path = "config.json"
3092
- self.config = load_config(config_path, type=BaseConfig, use_default_if_not_found=True)
3093
- if not self.config.user_configs:
3094
- # 如果没有用户配置,创建一个默认配置
3095
- default_config = UserConfig[BaseConfig](
3096
- name="默认配置",
3097
- category="default",
3098
- description="默认配置",
3099
- backend=BackendConfig(),
3100
- options=BaseConfig()
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) 寻求帮助。"
3101
75
  )
3102
- self.config.user_configs.append(default_config)
3103
- self.current_config = self.config.user_configs[0]
3104
-
3105
-
3106
- def create_ui(self) -> gr.Blocks:
3107
- custom_css = """
3108
- #container { max-width: 800px; margin: auto; padding: 20px; }
3109
- .quick-controls-row > div {
3110
- display: flex !important;
3111
- flex-wrap: nowrap !important;
3112
- gap: 0 !important;
3113
- overflow-x: auto !important;
3114
- }
3115
- .quick-controls-row > div > div {
3116
- min-width: auto !important;
3117
- padding: 0;
3118
- }
3119
- """
3120
- with gr.Blocks(title="琴音小助手", css=custom_css) as app:
3121
- with gr.Column(elem_id="container"):
3122
- gr.Markdown(f"# 琴音小助手 v{self._kaa.version}")
3123
-
3124
- with gr.Tabs():
3125
- self._create_status_tab()
3126
- self._create_task_tab()
3127
- self._create_settings_tab()
3128
- self._create_produce_tab()
3129
- self._create_log_tab()
3130
- self._create_whats_new_tab()
3131
- self._create_screen_tab()
3132
-
3133
- # 启动 IdleModeManager 后台线程
3134
- self.idle_mgr.start()
3135
-
3136
- return app
3137
-
3138
- def main(kaa: Kaa | None = None, start_immidiately: bool = False) -> None:
3139
- kaa = kaa or Kaa('./config.json')
3140
- ui = KotoneBotUI(kaa)
3141
- app = ui.create_ui()
3142
-
3143
- if start_immidiately:
3144
- ui.start_run()
3145
-
3146
- server_name = "0.0.0.0" if ui.current_config.options.misc.expose_to_lan else "127.0.0.1"
3147
- app.launch(inbrowser=True, show_error=True, server_name=server_name)
76
+ gr.Code(traceback.format_exc(), label="Traceback")
3148
77
 
3149
- if __name__ == "__main__":
3150
- main()
78
+ error_blocks.launch(inbrowser=True)