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