feat(task): 支持指定培育中行动优先级
This commit is contained in:
parent
d5c64dfdbc
commit
fc8c456902
|
@ -1 +1 @@
|
||||||
{"definitions":{"be6836bd-ee42-432b-9166-469c74f32f0b":{"name":"InPurodyuusu.BoxWeeksUntilExam","displayName":"考试剩余周","type":"hint-box","annotationId":"be6836bd-ee42-432b-9166-469c74f32f0b","useHintRect":false,"description":"培育中左上角的下次考试剩余周数"}},"annotations":[{"id":"be6836bd-ee42-432b-9166-469c74f32f0b","type":"rect","data":{"x1":11,"y1":8,"x2":237,"y2":196}}]}
|
{"definitions":{"be6836bd-ee42-432b-9166-469c74f32f0b":{"name":"InPurodyuusu.BoxWeeksUntilExam","displayName":"考试剩余周","type":"hint-box","annotationId":"be6836bd-ee42-432b-9166-469c74f32f0b","useHintRect":false,"description":"培育中左上角的下次考试剩余周数"},"d6b64759-26b7-45b1-bf8e-5c0d98611e0d":{"name":"InPurodyuusu.TextActionVocal","displayName":"Vo. レッスン","type":"template","annotationId":"d6b64759-26b7-45b1-bf8e-5c0d98611e0d","useHintRect":false,"description":"培育 行动页面 声乐课程按钮文字"},"303cccc1-c674-4d3a-8c89-19ea729fdbef":{"name":"InPurodyuusu.TextActionDance","displayName":"Da. レッスン","type":"template","annotationId":"303cccc1-c674-4d3a-8c89-19ea729fdbef","useHintRect":false,"description":"培育 行动页面 舞蹈课程按钮文字"},"cc8a495d-330d-447d-8a80-a8a6ecc409c5":{"name":"InPurodyuusu.TextActionVisual","displayName":"Vi. レッスン","type":"template","annotationId":"cc8a495d-330d-447d-8a80-a8a6ecc409c5","useHintRect":false,"description":"培育 行动页面 形象课程按钮文字"}},"annotations":[{"id":"be6836bd-ee42-432b-9166-469c74f32f0b","type":"rect","data":{"x1":11,"y1":8,"x2":237,"y2":196}},{"id":"d6b64759-26b7-45b1-bf8e-5c0d98611e0d","type":"rect","data":{"x1":116,"y1":1067,"x2":255,"y2":1097}},{"id":"303cccc1-c674-4d3a-8c89-19ea729fdbef","type":"rect","data":{"x1":288,"y1":1068,"x2":431,"y2":1096}},{"id":"cc8a495d-330d-447d-8a80-a8a6ecc409c5","type":"rect","data":{"x1":466,"y1":1067,"x2":606,"y2":1096}}]}
|
|
@ -1,7 +1,7 @@
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from typing_extensions import deprecated
|
from typing_extensions import deprecated, assert_never
|
||||||
from itertools import cycle
|
from itertools import cycle
|
||||||
from typing import Generic, Iterable, Literal, NamedTuple, Callable, Generator, TypeVar, ParamSpec, cast
|
from typing import Generic, Iterable, Literal, NamedTuple, Callable, Generator, TypeVar, ParamSpec, cast
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from cv2.typing import MatLike
|
||||||
|
|
||||||
from .. import R
|
from .. import R
|
||||||
from . import loading
|
from . import loading
|
||||||
from ..common import conf
|
from ..common import ProduceAction, conf
|
||||||
from .scenes import at_home
|
from .scenes import at_home
|
||||||
from .commu import handle_unread_commu
|
from .commu import handle_unread_commu
|
||||||
from kotonebot.errors import UnrecoverableError
|
from kotonebot.errors import UnrecoverableError
|
||||||
|
@ -96,7 +96,7 @@ def handle_sp_lesson():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@action('执行推荐行动')
|
@action('执行推荐行动')
|
||||||
def handle_recommended_action(final_week: bool = False) -> ActionType:
|
def handle_recommended_action(final_week: bool = False) -> ProduceAction | None:
|
||||||
"""
|
"""
|
||||||
在行动选择页面,执行推荐行动
|
在行动选择页面,执行推荐行动
|
||||||
|
|
||||||
|
@ -121,16 +121,20 @@ def handle_recommended_action(final_week: bool = False) -> ActionType:
|
||||||
if result is None:
|
if result is None:
|
||||||
logger.debug("No recommended lesson found")
|
logger.debug("No recommended lesson found")
|
||||||
return None
|
return None
|
||||||
|
recommended = None
|
||||||
if not final_week:
|
if not final_week:
|
||||||
if result.index == 0:
|
if result.index == 0:
|
||||||
lesson_text = regex("Da")
|
lesson_text = regex("Da")
|
||||||
|
recommended = ProduceAction.DANCE
|
||||||
elif result.index == 1:
|
elif result.index == 1:
|
||||||
lesson_text = regex("Vo|V0|VO")
|
lesson_text = regex("Vo|V0|VO")
|
||||||
|
recommended = ProduceAction.VOCAL
|
||||||
elif result.index == 2:
|
elif result.index == 2:
|
||||||
lesson_text = regex("Vi|V1|VI")
|
lesson_text = regex("Vi|V1|VI")
|
||||||
|
recommended = ProduceAction.VISUAL
|
||||||
elif result.index == 3:
|
elif result.index == 3:
|
||||||
rest()
|
rest()
|
||||||
return 'rest'
|
return ProduceAction.REST
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
logger.info("Rec. lesson: %s", lesson_text)
|
logger.info("Rec. lesson: %s", lesson_text)
|
||||||
|
@ -138,19 +142,22 @@ def handle_recommended_action(final_week: bool = False) -> ActionType:
|
||||||
logger.debug("Try clicking lesson...")
|
logger.debug("Try clicking lesson...")
|
||||||
lesson_ret = ocr.expect(lesson_text)
|
lesson_ret = ocr.expect(lesson_text)
|
||||||
device.double_click(lesson_ret.rect)
|
device.double_click(lesson_ret.rect)
|
||||||
return 'lesson'
|
return recommended
|
||||||
else:
|
else:
|
||||||
if result.index == 0:
|
if result.index == 0:
|
||||||
template = R.InPurodyuusu.ButtonFinalPracticeDance
|
template = R.InPurodyuusu.ButtonFinalPracticeDance
|
||||||
|
recommended = ProduceAction.DANCE
|
||||||
elif result.index == 1:
|
elif result.index == 1:
|
||||||
template = R.InPurodyuusu.ButtonFinalPracticeVocal
|
template = R.InPurodyuusu.ButtonFinalPracticeVocal
|
||||||
|
recommended = ProduceAction.VOCAL
|
||||||
elif result.index == 2:
|
elif result.index == 2:
|
||||||
template = R.InPurodyuusu.ButtonFinalPracticeVisual
|
template = R.InPurodyuusu.ButtonFinalPracticeVisual
|
||||||
|
recommended = ProduceAction.VISUAL
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
logger.debug("Try clicking lesson...")
|
logger.debug("Try clicking lesson...")
|
||||||
device.double_click(image.expect_wait(template))
|
device.double_click(image.expect_wait(template))
|
||||||
return 'lesson'
|
return recommended
|
||||||
|
|
||||||
class CardDetectResult(NamedTuple):
|
class CardDetectResult(NamedTuple):
|
||||||
type: int
|
type: int
|
||||||
|
@ -769,43 +776,128 @@ def produce_end():
|
||||||
sleep(1)
|
sleep(1)
|
||||||
logger.info("Produce completed.")
|
logger.info("Produce completed.")
|
||||||
|
|
||||||
|
@action('执行行动')
|
||||||
|
def handle_action(action: ProduceAction, final_week: bool = False) -> ProduceAction | None:
|
||||||
|
"""
|
||||||
|
执行行动
|
||||||
|
|
||||||
|
前置条件:位于行动选择页面\n
|
||||||
|
结束状态:若返回 True,取决于执行的行动。若返回 False,则仍然位于行动选择页面。
|
||||||
|
|
||||||
|
:param action: 行动类型
|
||||||
|
:param final_week: 是否为冲刺周
|
||||||
|
:return: 执行的行动
|
||||||
|
"""
|
||||||
|
match action:
|
||||||
|
case ProduceAction.RECOMMENDED:
|
||||||
|
return handle_recommended_action(final_week)
|
||||||
|
case ProduceAction.DANCE:
|
||||||
|
# TODO: 这两个模板的名称要统一一下
|
||||||
|
templ = R.InPurodyuusu.TextActionVisual if not final_week else R.InPurodyuusu.ButtonFinalPracticeVisual
|
||||||
|
if button := image.find(templ):
|
||||||
|
device.double_click(button)
|
||||||
|
return ProduceAction.DANCE
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
case ProduceAction.VOCAL:
|
||||||
|
templ = R.InPurodyuusu.TextActionVocal if not final_week else R.InPurodyuusu.ButtonFinalPracticeVocal
|
||||||
|
if button := image.find(templ):
|
||||||
|
device.double_click(button)
|
||||||
|
return ProduceAction.VOCAL
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
case ProduceAction.VISUAL:
|
||||||
|
templ = R.InPurodyuusu.TextActionDance if not final_week else R.InPurodyuusu.ButtonFinalPracticeDance
|
||||||
|
if button := image.find(templ):
|
||||||
|
device.double_click(button)
|
||||||
|
return ProduceAction.VISUAL
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
case ProduceAction.REST:
|
||||||
|
if is_rest_available():
|
||||||
|
rest()
|
||||||
|
return ProduceAction.REST
|
||||||
|
case ProduceAction.OUTING:
|
||||||
|
if outing_available():
|
||||||
|
enter_outing()
|
||||||
|
return ProduceAction.OUTING
|
||||||
|
case ProduceAction.STUDY:
|
||||||
|
if study_available():
|
||||||
|
enter_study()
|
||||||
|
return ProduceAction.STUDY
|
||||||
|
case ProduceAction.ALLOWANCE:
|
||||||
|
if allowance_available():
|
||||||
|
enter_allowance()
|
||||||
|
return ProduceAction.ALLOWANCE
|
||||||
|
case _:
|
||||||
|
logger.warning("Unknown action: %s", action)
|
||||||
|
return None
|
||||||
|
|
||||||
def week_normal(week_first: bool = False):
|
def week_normal(week_first: bool = False):
|
||||||
until_action_scene(week_first)
|
until_action_scene(week_first)
|
||||||
|
logger.info("Handling actions...")
|
||||||
|
action: ProduceAction | None = None
|
||||||
# SP 课程
|
# SP 课程
|
||||||
if (
|
if (
|
||||||
conf().produce.prefer_lesson_ap
|
conf().produce.prefer_lesson_ap
|
||||||
and handle_sp_lesson()
|
and handle_sp_lesson()
|
||||||
):
|
):
|
||||||
executed_action = 'lesson'
|
action = ProduceAction.DANCE
|
||||||
else:
|
else:
|
||||||
executed_action = handle_recommended_action()
|
actions = conf().produce.actions_order
|
||||||
logger.info("Executed recommended action: %s", executed_action)
|
for action in actions:
|
||||||
# 推荐练习
|
logger.debug("Checking action: %s", action)
|
||||||
if executed_action == 'lesson':
|
if action := handle_action(action):
|
||||||
until_practice_scene()
|
logger.info("Action %s hit.", action)
|
||||||
practice()
|
break
|
||||||
# 推荐休息
|
match action:
|
||||||
elif executed_action == 'rest':
|
case (
|
||||||
pass
|
ProduceAction.REST |
|
||||||
# 没有推荐行动
|
ProduceAction.OUTING | ProduceAction.STUDY | ProduceAction.ALLOWANCE
|
||||||
elif executed_action is None:
|
):
|
||||||
if outing_available():
|
# 什么都不需要做
|
||||||
enter_outing()
|
pass
|
||||||
elif study_available():
|
case ProduceAction.DANCE | ProduceAction.VOCAL | ProduceAction.VISUAL:
|
||||||
enter_study()
|
until_practice_scene()
|
||||||
elif allowance_available():
|
practice()
|
||||||
enter_allowance()
|
case ProduceAction.RECOMMENDED:
|
||||||
elif is_rest_available():
|
# RECOMMENDED 应当被 handle_recommended_action 转换为具体的行动
|
||||||
rest()
|
raise ValueError("Recommended action should not be handled here.")
|
||||||
else:
|
case None:
|
||||||
raise ValueError("No action available.")
|
raise ValueError("Action is None.")
|
||||||
|
case _:
|
||||||
|
assert_never(action)
|
||||||
until_action_scene()
|
until_action_scene()
|
||||||
|
|
||||||
def week_final_lesson():
|
def week_final_lesson():
|
||||||
until_action_scene()
|
until_action_scene()
|
||||||
if handle_recommended_action(final_week=True) != 'lesson':
|
# if handle_recommended_action(final_week=True) != 'lesson':
|
||||||
raise ValueError("Failed to enter recommended action on final week.")
|
# raise ValueError("Failed to enter recommended action on final week.")
|
||||||
sleep(5)
|
# sleep(5)
|
||||||
|
action: ProduceAction | None = None
|
||||||
|
actions = conf().produce.actions_order
|
||||||
|
for action in actions:
|
||||||
|
logger.debug("Checking action: %s", action)
|
||||||
|
if action := handle_action(action, True):
|
||||||
|
logger.info("Action %s hit.", action)
|
||||||
|
break
|
||||||
|
match action:
|
||||||
|
case (
|
||||||
|
ProduceAction.REST |
|
||||||
|
ProduceAction.OUTING | ProduceAction.STUDY | ProduceAction.ALLOWANCE
|
||||||
|
):
|
||||||
|
# 什么都不需要做
|
||||||
|
pass
|
||||||
|
case ProduceAction.DANCE | ProduceAction.VOCAL | ProduceAction.VISUAL:
|
||||||
|
until_practice_scene()
|
||||||
|
practice()
|
||||||
|
case ProduceAction.RECOMMENDED:
|
||||||
|
# RECOMMENDED 应当被 handle_recommended_action 转换为具体的行动
|
||||||
|
raise ValueError("Recommended action should not be handled here.")
|
||||||
|
case None:
|
||||||
|
raise ValueError("Action is None.")
|
||||||
|
case _:
|
||||||
|
assert_never(action)
|
||||||
until_practice_scene()
|
until_practice_scene()
|
||||||
practice()
|
practice()
|
||||||
|
|
||||||
|
@ -1050,6 +1142,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
# practice()
|
# practice()
|
||||||
|
week_mid_exam()
|
||||||
# week_final_exam()
|
# week_final_exam()
|
||||||
# exam('final')
|
# exam('final')
|
||||||
# produce_end()
|
# produce_end()
|
||||||
|
@ -1058,7 +1151,7 @@ if __name__ == '__main__':
|
||||||
# hajime_pro(start_from=16)
|
# hajime_pro(start_from=16)
|
||||||
# exam('mid')
|
# exam('mid')
|
||||||
stage = (detect_produce_scene())
|
stage = (detect_produce_scene())
|
||||||
hajime_regular_from_stage(stage, 'pro')
|
hajime_regular_from_stage(stage, 'regular')
|
||||||
|
|
||||||
# click_recommended_card(card_count=skill_card_count())
|
# click_recommended_card(card_count=skill_card_count())
|
||||||
# exam('mid')
|
# exam('mid')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from importlib import resources
|
|
||||||
import os
|
import os
|
||||||
from typing import Literal, Dict
|
from importlib import resources
|
||||||
|
from typing import Literal, Dict, NamedTuple, Tuple, TypeVar, Generic
|
||||||
from enum import IntEnum, Enum
|
from enum import IntEnum, Enum
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
@ -8,6 +8,11 @@ from pydantic import BaseModel, ConfigDict
|
||||||
# TODO: from kotonebot import config (context) 会和 kotonebot.config 冲突
|
# TODO: from kotonebot import config (context) 会和 kotonebot.config 冲突
|
||||||
from kotonebot.backend.context import config
|
from kotonebot.backend.context import config
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
class ConfigEnum(Enum):
|
||||||
|
def display(self) -> str:
|
||||||
|
return self.value[1]
|
||||||
|
|
||||||
class Priority(IntEnum):
|
class Priority(IntEnum):
|
||||||
START_GAME = 1
|
START_GAME = 1
|
||||||
DEFAULT = 0
|
DEFAULT = 0
|
||||||
|
@ -302,6 +307,33 @@ class ContestConfig(ConfigBaseModel):
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
"""是否启用竞赛"""
|
"""是否启用竞赛"""
|
||||||
|
|
||||||
|
class ProduceAction(Enum):
|
||||||
|
RECOMMENDED = 'recommended'
|
||||||
|
VISUAL = 'visual'
|
||||||
|
VOCAL = 'vocal'
|
||||||
|
DANCE = 'dance'
|
||||||
|
# VISUAL_SP = 'visual_sp'
|
||||||
|
# VOCAL_SP = 'vocal_sp'
|
||||||
|
# DANCE_SP = 'dance_sp'
|
||||||
|
OUTING = 'outing'
|
||||||
|
STUDY = 'study'
|
||||||
|
ALLOWANCE = 'allowance'
|
||||||
|
REST = 'rest'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_name(self):
|
||||||
|
MAP = {
|
||||||
|
ProduceAction.RECOMMENDED: '推荐行动',
|
||||||
|
ProduceAction.VISUAL: '形象课程',
|
||||||
|
ProduceAction.VOCAL: '声乐课程',
|
||||||
|
ProduceAction.DANCE: '舞蹈课程',
|
||||||
|
ProduceAction.OUTING: '外出(おでかけ)',
|
||||||
|
ProduceAction.STUDY: '自习(授業)',
|
||||||
|
ProduceAction.ALLOWANCE: '活动支给(活動支給)',
|
||||||
|
ProduceAction.REST: '休息',
|
||||||
|
}
|
||||||
|
return MAP[self]
|
||||||
|
|
||||||
class ProduceConfig(ConfigBaseModel):
|
class ProduceConfig(ConfigBaseModel):
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
"""是否启用培育"""
|
"""是否启用培育"""
|
||||||
|
@ -340,6 +372,21 @@ class ProduceConfig(ConfigBaseModel):
|
||||||
启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。
|
启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。
|
||||||
若出现多个 SP 课程,随机选择一个。
|
若出现多个 SP 课程,随机选择一个。
|
||||||
"""
|
"""
|
||||||
|
actions_order: list[ProduceAction] = [
|
||||||
|
ProduceAction.RECOMMENDED,
|
||||||
|
ProduceAction.VISUAL,
|
||||||
|
ProduceAction.VOCAL,
|
||||||
|
ProduceAction.DANCE,
|
||||||
|
ProduceAction.ALLOWANCE,
|
||||||
|
ProduceAction.OUTING,
|
||||||
|
ProduceAction.STUDY,
|
||||||
|
ProduceAction.REST,
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
行动优先级
|
||||||
|
|
||||||
|
每一周的行动将会按这里设置的优先级执行。
|
||||||
|
"""
|
||||||
|
|
||||||
class MissionRewardConfig(ConfigBaseModel):
|
class MissionRewardConfig(ConfigBaseModel):
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
|
|
|
@ -16,7 +16,7 @@ from kotonebot.config.manager import load_config, save_config
|
||||||
from kotonebot.tasks.common import (
|
from kotonebot.tasks.common import (
|
||||||
BaseConfig, APShopItems, PurchaseConfig, ActivityFundsConfig,
|
BaseConfig, APShopItems, PurchaseConfig, ActivityFundsConfig,
|
||||||
PresentsConfig, AssignmentConfig, ContestConfig, ProduceConfig,
|
PresentsConfig, AssignmentConfig, ContestConfig, ProduceConfig,
|
||||||
MissionRewardConfig, PIdol, DailyMoneyShopItems
|
MissionRewardConfig, PIdol, DailyMoneyShopItems, ProduceAction
|
||||||
)
|
)
|
||||||
from kotonebot.config.base_config import UserConfig, BackendConfig
|
from kotonebot.config.base_config import UserConfig, BackendConfig
|
||||||
from kotonebot.backend.bot import KotoneBot
|
from kotonebot.backend.bot import KotoneBot
|
||||||
|
@ -255,6 +255,7 @@ class KotoneBotUI:
|
||||||
follow_producer: bool,
|
follow_producer: bool,
|
||||||
self_study_lesson: Literal['dance', 'visual', 'vocal'],
|
self_study_lesson: Literal['dance', 'visual', 'vocal'],
|
||||||
prefer_lesson_ap: bool,
|
prefer_lesson_ap: bool,
|
||||||
|
actions_order: List[str],
|
||||||
mission_reward_enabled: bool,
|
mission_reward_enabled: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
ap_items_enum: List[Literal[0, 1, 2, 3]] = []
|
ap_items_enum: List[Literal[0, 1, 2, 3]] = []
|
||||||
|
@ -311,7 +312,8 @@ class KotoneBotUI:
|
||||||
use_note_boost=use_note_boost,
|
use_note_boost=use_note_boost,
|
||||||
follow_producer=follow_producer,
|
follow_producer=follow_producer,
|
||||||
self_study_lesson=self_study_lesson,
|
self_study_lesson=self_study_lesson,
|
||||||
prefer_lesson_ap=prefer_lesson_ap
|
prefer_lesson_ap=prefer_lesson_ap,
|
||||||
|
actions_order=[ProduceAction(action) for action in actions_order]
|
||||||
),
|
),
|
||||||
mission_reward=MissionRewardConfig(
|
mission_reward=MissionRewardConfig(
|
||||||
enabled=mission_reward_enabled
|
enabled=mission_reward_enabled
|
||||||
|
@ -585,7 +587,14 @@ class KotoneBotUI:
|
||||||
value=self.current_config.options.produce.prefer_lesson_ap,
|
value=self.current_config.options.produce.prefer_lesson_ap,
|
||||||
info=ProduceConfig.model_fields['prefer_lesson_ap'].description
|
info=ProduceConfig.model_fields['prefer_lesson_ap'].description
|
||||||
)
|
)
|
||||||
|
actions_order = gr.Dropdown(
|
||||||
|
choices=[(action.display_name, action.value) for action in ProduceAction],
|
||||||
|
value=[action.value for action in self.current_config.options.produce.actions_order],
|
||||||
|
label="行动优先级",
|
||||||
|
info="设置每周行动的优先级顺序",
|
||||||
|
multiselect=True
|
||||||
|
)
|
||||||
|
|
||||||
produce_enabled.change(
|
produce_enabled.change(
|
||||||
fn=lambda x: gr.Group(visible=x),
|
fn=lambda x: gr.Group(visible=x),
|
||||||
inputs=[produce_enabled],
|
inputs=[produce_enabled],
|
||||||
|
@ -597,7 +606,7 @@ class KotoneBotUI:
|
||||||
inputs=[auto_set_memory],
|
inputs=[auto_set_memory],
|
||||||
outputs=[memory_sets_group]
|
outputs=[memory_sets_group]
|
||||||
)
|
)
|
||||||
return produce_enabled, produce_mode, produce_count, produce_idols, memory_sets, auto_set_memory, auto_set_support, use_pt_boost, use_note_boost, follow_producer, self_study_lesson, prefer_lesson_ap
|
return produce_enabled, produce_mode, produce_count, produce_idols, memory_sets, auto_set_memory, auto_set_support, use_pt_boost, use_note_boost, follow_producer, self_study_lesson, prefer_lesson_ap, actions_order
|
||||||
|
|
||||||
def _create_settings_tab(self) -> None:
|
def _create_settings_tab(self) -> None:
|
||||||
with gr.Tab("设置"):
|
with gr.Tab("设置"):
|
||||||
|
|
Loading…
Reference in New Issue