feat(ui): 为新的培育方案增加 UI

This commit is contained in:
XcantloadX 2025-07-08 09:51:49 +08:00
parent 41e7c8b4a8
commit 68b0cbda73
4 changed files with 678 additions and 216 deletions

View File

@ -4,7 +4,7 @@ import uuid
import re
import logging
from typing import Literal
from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, field_serializer, field_validator
from .const import ProduceAction, RecommendCardDetectionMode
@ -15,23 +15,19 @@ class ConfigBaseModel(BaseModel):
class ProduceData(ConfigBaseModel):
enabled: bool = False
"""是否启用培育"""
mode: Literal['regular', 'pro', 'master'] = 'regular'
"""
培育模式
进行一次 REGULAR 培育需要 ~30min进行一次 PRO 培育需要 ~1h具体视设备性能而定
"""
produce_count: int = 1
"""培育的次数。"""
idols: list[str] = []
idol: str | None = None
"""
要培育偶像的 IdolCardSkin.id将会按顺序循环选择培育
要培育偶像的 IdolCardSkin.id
"""
memory_sets: list[int] = []
"""要使用的回忆编成编号,从 1 开始。将会按顺序循环选择使用。"""
support_card_sets: list[int] = []
"""要使用的支援卡编成编号,从 1 开始。将会按顺序循环选择使用。"""
memory_set: int | None = None
"""要使用的回忆编成编号,从 1 开始。"""
support_card_set: int | None = None
"""要使用的支援卡编成编号,从 1 开始。"""
auto_set_memory: bool = False
"""是否自动编成回忆。此选项优先级高于回忆编成编号。"""
auto_set_support_card: bool = False
@ -80,9 +76,10 @@ class ProduceData(ConfigBaseModel):
skip_commu: bool = True
"""检测并跳过交流"""
class ProduceSolution(ConfigBaseModel):
"""培育方案"""
type: Literal['produce_solution'] = 'produce_solution'
"""方案类型标识"""
id: str
"""方案唯一标识符"""
name: str
@ -122,6 +119,28 @@ class ProduceSolutionManager:
safe_name = self._sanitize_filename(name)
return os.path.join(self.SOLUTIONS_DIR, f"{safe_name}.json")
def _find_file_path_by_id(self, id: str) -> str | None:
"""
根据方案ID查找文件路径
:param id: 方案ID
:return: 文件路径如果未找到则返回 None
"""
if not os.path.exists(self.SOLUTIONS_DIR):
return None
for filename in os.listdir(self.SOLUTIONS_DIR):
if filename.endswith('.json'):
try:
file_path = os.path.join(self.SOLUTIONS_DIR, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if data.get('id') == id:
return file_path
except Exception:
continue
return None
def new(self, name: str) -> ProduceSolution:
"""
创建新的培育方案
@ -151,8 +170,7 @@ class ProduceSolutionManager:
try:
file_path = os.path.join(self.SOLUTIONS_DIR, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
solution = ProduceSolution.model_validate_json(data)
solution = ProduceSolution.model_validate_json(f.read())
solutions.append(solution)
logger.info(f"Loaded produce solution from {file_path}")
except Exception:
@ -167,20 +185,9 @@ class ProduceSolutionManager:
:param id: 方案ID
"""
if not os.path.exists(self.SOLUTIONS_DIR):
return
for filename in os.listdir(self.SOLUTIONS_DIR):
if filename.endswith('.json'):
try:
file_path = os.path.join(self.SOLUTIONS_DIR, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if data.get('id') == id:
os.remove(file_path)
return
except Exception:
continue
file_path = self._find_file_path_by_id(id)
if file_path:
os.remove(file_path)
def save(self, id: str, solution: ProduceSolution) -> None:
"""
@ -192,9 +199,17 @@ class ProduceSolutionManager:
# 确保ID一致
solution.id = id
# 先删除具有相同ID的旧文件如果存在避免名称变更时产生重复文件
old_file_path = self._find_file_path_by_id(id)
if old_file_path:
os.remove(old_file_path)
# 保存新文件
file_path = self._get_file_path(solution.name)
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(solution.model_dump_json(indent=4), f, ensure_ascii=False)
# 使用 model_dump 并指定 mode='json' 来正确序列化枚举
data = solution.model_dump(mode='json')
json.dump(data, f, ensure_ascii=False, indent=4)
def read(self, id: str) -> ProduceSolution:
"""
@ -204,18 +219,37 @@ class ProduceSolutionManager:
:return: 方案对象
:raises FileNotFoundError: 当方案不存在时
"""
if not os.path.exists(self.SOLUTIONS_DIR):
file_path = self._find_file_path_by_id(id)
if not file_path:
raise FileNotFoundError(f"Solution with id '{id}' not found")
for filename in os.listdir(self.SOLUTIONS_DIR):
if filename.endswith('.json'):
try:
file_path = os.path.join(self.SOLUTIONS_DIR, filename)
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if data.get('id') == id:
return ProduceSolution.model_validate_json(data)
except Exception:
continue
try:
with open(file_path, 'r', encoding='utf-8') as f:
return ProduceSolution.model_validate_json(f.read())
except Exception as e:
raise FileNotFoundError(f"Failed to read solution with id '{id}': {e}")
raise FileNotFoundError(f"Solution with id '{id}' not found")
def duplicate(self, id: str) -> ProduceSolution:
"""
复制指定ID的培育方案
:param id: 要复制的方案ID
:return: 新的方案对象具有新的ID和名称
:raises FileNotFoundError: 当原方案不存在时
"""
original = self.read(id)
# 生成新的ID和名称
new_id = uuid.uuid4().hex
new_name = f"{original.name} - 副本"
# 创建新的方案对象
new_solution = ProduceSolution(
type=original.type,
id=new_id,
name=new_name,
description=original.description,
data=original.data.model_copy() # 深拷贝数据
)
return new_solution

View File

@ -71,6 +71,8 @@ class ProduceConfig(ConfigBaseModel):
"""是否启用培育"""
selected_solution_id: str | None = None
"""选中的培育方案ID"""
produce_count: int = 1
"""培育的次数。"""
class MissionRewardConfig(ConfigBaseModel):
enabled: bool = False

View File

@ -27,6 +27,7 @@ from kotonebot.kaa.config import (
MissionRewardConfig, DailyMoneyShopItems, ProduceAction,
RecommendCardDetectionMode, TraceConfig, StartGameConfig, EndGameConfig, UpgradeSupportCardConfig, MiscConfig,
)
from kotonebot.kaa.config.produce import ProduceSolution, ProduceSolutionManager, ProduceData
logger = logging.getLogger(__name__)
GradioInput = gr.Textbox | gr.Number | gr.Checkbox | gr.Dropdown | gr.Radio | gr.Slider | gr.Tabs | gr.Tab
@ -52,14 +53,7 @@ ConfigKey = Literal[
'select_which_contestant',
# produce
'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', 'recommend_card_detection_mode',
'use_ap_drink', 'skip_commu',
'produce_enabled', 'selected_solution_id', 'produce_count',
'mission_reward_enabled',
# club reward
@ -453,9 +447,9 @@ class KotoneBotUI:
gr.Warning(f"截图方法 '{screenshot_method}' 不适用于当前选择的模拟器类型,配置未保存。")
return ""
# 验证规则2若启用培育那么培育偶像不能为空
if options.produce.enabled and not options.produce.idols:
gr.Warning("启用培育时,培育偶像不能为空,配置未保存。")
# 验证规则2若启用培育那么必须选择培育方案
if options.produce.enabled and not options.produce.selected_solution_id:
gr.Warning("启用培育时,必须选择培育方案,配置未保存。")
return ""
# 验证规则3若启用AP/金币购买,对应的商品不能为空
@ -1230,13 +1224,21 @@ class KotoneBotUI:
value=self.current_config.options.produce.enabled,
info=ProduceConfig.model_fields['enabled'].description
)
with gr.Group(visible=self.current_config.options.produce.enabled) as produce_group:
produce_mode = gr.Dropdown(
choices=["regular", "pro", "master"],
value=self.current_config.options.produce.mode,
label="培育模式",
info=ProduceConfig.model_fields['mode'].description
# 培育方案管理区域
solution_manager = ProduceSolutionManager()
solutions = solution_manager.list()
solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
selected_solution_id = self.current_config.options.produce.selected_solution_id
solution_dropdown = gr.Dropdown(
choices=solution_choices,
value=selected_solution_id,
label="当前使用的培育方案",
interactive=True
)
produce_count = gr.Number(
minimum=1,
value=self.current_config.options.produce.produce_count,
@ -1244,106 +1246,523 @@ class KotoneBotUI:
interactive=True,
info=ProduceConfig.model_fields['produce_count'].description
)
# 添加偶像选择
idol_choices = []
for idol in IdolCard.all():
if idol.is_another:
idol_choices.append((f'{idol.name} 「{idol.another_name}', idol.skin_id))
else:
idol_choices.append((f'{idol.name}', idol.skin_id))
selected_idols = self.current_config.options.produce.idols
produce_idols = gr.Dropdown(
choices=idol_choices,
value=selected_idols,
label="选择要培育的偶像",
multiselect=True,
interactive=True,
info=ProduceConfig.model_fields['idols'].description
)
has_kotone = any("藤田ことね" in idol for idol in selected_idols)
is_strict_mode = self.current_config.options.produce.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
kotone_warning = gr.Markdown(
visible=has_kotone and not is_strict_mode,
value="使用「藤田ことね」进行培育时,确保将「推荐卡检测模式」设置为「严格模式」"
)
auto_set_memory = gr.Checkbox(
label="自动编成回忆",
value=self.current_config.options.produce.auto_set_memory,
info=ProduceConfig.model_fields['auto_set_memory'].description
)
with gr.Group(visible=not self.current_config.options.produce.auto_set_memory) as memory_sets_group:
memory_sets = gr.Dropdown(
choices=[str(i) for i in range(1, 11)], # 假设最多10个编成位
value=[str(i) for i in self.current_config.options.produce.memory_sets],
label="回忆编成编号",
multiselect=True,
interactive=True,
info=ProduceConfig.model_fields['memory_sets'].description
# 绑定启用状态变化事件
def on_produce_enabled_change(enabled):
return gr.Group(visible=enabled)
produce_enabled.change(
fn=on_produce_enabled_change,
inputs=[produce_enabled],
outputs=[produce_group]
)
# 存储设置Tab中的下拉框引用供培育Tab使用
self._settings_solution_dropdown = solution_dropdown
def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
config.produce.enabled = data['produce_enabled']
config.produce.selected_solution_id = data.get('selected_solution_id')
config.produce.produce_count = data.get('produce_count', 1)
return set_config, {
'produce_enabled': produce_enabled,
'selected_solution_id': solution_dropdown,
'produce_count': produce_count
}
def _create_produce_tab(self) -> None:
"""创建培育Tab"""
with gr.Tab("培育"):
gr.Markdown("## 培育管理")
# 培育方案管理区域
solution_manager = ProduceSolutionManager()
solutions = solution_manager.list()
solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
selected_solution_id = self.current_config.options.produce.selected_solution_id
solution_dropdown = gr.Dropdown(
choices=solution_choices,
value=selected_solution_id,
label="选择培育方案",
interactive=True
)
# 获取设置Tab中的下拉框引用
settings_dropdown = getattr(self, '_settings_solution_dropdown', None)
with gr.Row():
new_solution_btn = gr.Button("新建培育", scale=1)
delete_solution_btn = gr.Button("删除当前培育", scale=1)
# 培育方案详细设置区域
with gr.Group() as solution_settings_group:
# 获取当前选中的方案数据
current_solution = None
if selected_solution_id:
try:
current_solution = solution_manager.read(selected_solution_id)
except FileNotFoundError:
pass
if current_solution is None:
# 如果没有选中方案,隐藏所有设置组件
solution_name = gr.Textbox(label="方案名称", value="", visible=False)
solution_description = gr.Textbox(label="方案描述", value="", visible=False)
# 其他设置组件都设为不可见
produce_mode = gr.Dropdown(
choices=["regular", "pro", "master"],
label="培育模式",
info="进行一次 REGULAR 培育需要 ~30min进行一次 PRO 培育需要 ~1h具体视设备性能而定",
visible=False
)
produce_count = gr.Number(
minimum=1,
label="培育次数",
info="培育的次数。",
visible=False
)
# 添加偶像选择变化时的回调
def update_kotone_warning(selected_idols, recommend_card_detection_mode):
has_kotone = any("藤田ことね" in idol for idol in selected_idols)
is_strict_mode = recommend_card_detection_mode == RecommendCardDetectionMode.STRICT.value
return gr.Markdown(visible=has_kotone and not is_strict_mode)
# 添加偶像选择
idol_choices = []
for idol in IdolCard.all():
if idol.is_another:
idol_choices.append((f'{idol.name} 「{idol.another_name}', idol.skin_id))
else:
idol_choices.append((f'{idol.name}', idol.skin_id))
auto_set_support = gr.Checkbox(
label="自动编成支援卡",
value=self.current_config.options.produce.auto_set_support_card,
info=ProduceConfig.model_fields['auto_set_support_card'].description
)
use_pt_boost = gr.Checkbox(
label="使用支援强化 Pt 提升",
value=self.current_config.options.produce.use_pt_boost,
info=ProduceConfig.model_fields['use_pt_boost'].description
)
use_note_boost = gr.Checkbox(
label="使用笔记数提升",
value=self.current_config.options.produce.use_note_boost,
info=ProduceConfig.model_fields['use_note_boost'].description
)
follow_producer = gr.Checkbox(
label="关注租借了支援卡的制作人",
value=self.current_config.options.produce.follow_producer,
info=ProduceConfig.model_fields['follow_producer'].description
)
self_study_lesson = gr.Dropdown(
choices=['dance', 'visual', 'vocal'],
value=self.current_config.options.produce.self_study_lesson,
label='文化课自习时选项',
info='选择自习课类型'
)
prefer_lesson_ap = gr.Checkbox(
label="SP 课程优先",
value=self.current_config.options.produce.prefer_lesson_ap,
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
)
recommend_card_detection_mode = gr.Dropdown(
choices=[
(RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
(RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
],
value=self.current_config.options.produce.recommend_card_detection_mode.value,
label="推荐卡检测模式",
info=ProduceConfig.model_fields['recommend_card_detection_mode'].description
)
use_ap_drink = gr.Checkbox(
label="AP 不足时自动使用 AP 饮料",
value=self.current_config.options.produce.use_ap_drink,
info=ProduceConfig.model_fields['use_ap_drink'].description
)
skip_commu = gr.Checkbox(
label="检测并跳过交流",
value=self.current_config.options.produce.skip_commu,
info=ProduceConfig.model_fields['skip_commu'].description
)
produce_idols = gr.Dropdown(
choices=idol_choices,
label="选择要培育的偶像",
multiselect=False,
info="要培育偶像的 IdolCardSkin.id。",
visible=False
)
kotone_warning = gr.Markdown(visible=False)
auto_set_memory = gr.Checkbox(
label="自动编成回忆",
info="是否自动编成回忆。此选项优先级高于回忆编成编号。",
visible=False
)
with gr.Group(visible=False) as memory_sets_group:
memory_sets = gr.Dropdown(
choices=[str(i) for i in range(1, 11)],
label="回忆编成编号",
multiselect=False,
info="要使用的回忆编成编号,从 1 开始。",
visible=False
)
auto_set_support = gr.Checkbox(
label="自动编成支援卡",
info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。",
visible=False
)
with gr.Group(visible=False) as support_card_sets_group:
support_card_sets = gr.Dropdown(
choices=[str(i) for i in range(1, 11)],
label="支援卡编成编号",
multiselect=False,
info="要使用的支援卡编成编号,从 1 开始。",
visible=False
)
use_pt_boost = gr.Checkbox(
label="使用支援强化 Pt 提升",
info="是否使用支援强化 Pt 提升。",
visible=False
)
use_note_boost = gr.Checkbox(
label="使用笔记数提升",
info="是否使用笔记数提升。",
visible=False
)
follow_producer = gr.Checkbox(
label="关注租借了支援卡的制作人",
info="是否关注租借了支援卡的制作人。",
visible=False
)
self_study_lesson = gr.Dropdown(
choices=['dance', 'visual', 'vocal'],
label='文化课自习时选项',
info='自习课类型。',
visible=False
)
prefer_lesson_ap = gr.Checkbox(
label="SP 课程优先",
info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。",
visible=False
)
actions_order = gr.Dropdown(
choices=[(action.display_name, action.value) for action in ProduceAction],
label="行动优先级",
info="每一周的行动将会按这里设置的优先级执行。",
multiselect=True,
visible=False
)
recommend_card_detection_mode = gr.Dropdown(
choices=[
(RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
(RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
],
label="推荐卡检测模式",
info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。",
visible=False
)
use_ap_drink = gr.Checkbox(
label="AP 不足时自动使用 AP 饮料",
info="AP 不足时自动使用 AP 饮料",
visible=False
)
skip_commu = gr.Checkbox(
label="检测并跳过交流",
info="检测并跳过交流",
visible=False
)
save_solution_btn = gr.Button("保存培育方案", variant="primary", visible=False)
else:
# 显示选中方案的设置
solution_name = gr.Textbox(
label="方案名称",
value=current_solution.name,
interactive=True
)
solution_description = gr.Textbox(
label="方案描述",
value=current_solution.description or "",
interactive=True
)
produce_mode = gr.Dropdown(
choices=["regular", "pro", "master"],
value=current_solution.data.mode,
label="培育模式",
info="进行一次 REGULAR 培育需要 ~30min进行一次 PRO 培育需要 ~1h具体视设备性能而定"
)
produce_count = gr.Number(
minimum=1,
value=self.current_config.options.produce.produce_count,
label="培育次数",
interactive=True,
info="培育的次数。"
)
# 添加偶像选择
idol_choices = []
for idol in IdolCard.all():
if idol.is_another:
idol_choices.append((f'{idol.name} 「{idol.another_name}', idol.skin_id))
else:
idol_choices.append((f'{idol.name}', idol.skin_id))
produce_idols = gr.Dropdown(
choices=idol_choices,
value=current_solution.data.idol,
label="选择要培育的偶像",
multiselect=False,
interactive=True,
info="要培育偶像的 IdolCardSkin.id。"
)
has_kotone = bool(current_solution.data.idol and "藤田ことね" in current_solution.data.idol)
is_strict_mode = current_solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
kotone_warning = gr.Markdown(
visible=has_kotone and not is_strict_mode,
value="使用「藤田ことね」进行培育时,确保将「推荐卡检测模式」设置为「严格模式」"
)
auto_set_memory = gr.Checkbox(
label="自动编成回忆",
value=current_solution.data.auto_set_memory,
info="是否自动编成回忆。此选项优先级高于回忆编成编号。"
)
with gr.Group(visible=not current_solution.data.auto_set_memory) as memory_sets_group:
memory_sets = gr.Dropdown(
choices=[str(i) for i in range(1, 11)], # 假设最多10个编成位
value=str(current_solution.data.memory_set) if current_solution.data.memory_set else None,
label="回忆编成编号",
multiselect=False,
interactive=True,
info="要使用的回忆编成编号,从 1 开始。"
)
auto_set_support = gr.Checkbox(
label="自动编成支援卡",
value=current_solution.data.auto_set_support_card,
info="是否自动编成支援卡。此选项优先级高于支援卡编成编号。"
)
with gr.Group(visible=not current_solution.data.auto_set_support_card) as support_card_sets_group:
support_card_sets = gr.Dropdown(
choices=[str(i) for i in range(1, 11)], # 假设最多10个编成位
value=str(current_solution.data.support_card_set) if current_solution.data.support_card_set else None,
label="支援卡编成编号",
multiselect=False,
interactive=True,
info="要使用的支援卡编成编号,从 1 开始。"
)
use_pt_boost = gr.Checkbox(
label="使用支援强化 Pt 提升",
value=current_solution.data.use_pt_boost,
info="是否使用支援强化 Pt 提升。"
)
use_note_boost = gr.Checkbox(
label="使用笔记数提升",
value=current_solution.data.use_note_boost,
info="是否使用笔记数提升。"
)
follow_producer = gr.Checkbox(
label="关注租借了支援卡的制作人",
value=current_solution.data.follow_producer,
info="是否关注租借了支援卡的制作人。"
)
self_study_lesson = gr.Dropdown(
choices=['dance', 'visual', 'vocal'],
value=current_solution.data.self_study_lesson,
label='文化课自习时选项',
info='自习课类型。'
)
prefer_lesson_ap = gr.Checkbox(
label="SP 课程优先",
value=current_solution.data.prefer_lesson_ap,
info="优先 SP 课程。启用后,若出现 SP 课程,则会优先执行 SP 课程,而不是推荐课程。若出现多个 SP 课程,随机选择一个。"
)
actions_order = gr.Dropdown(
choices=[(action.display_name, action.value) for action in ProduceAction],
value=[action.value for action in current_solution.data.actions_order],
label="行动优先级",
info="每一周的行动将会按这里设置的优先级执行。",
multiselect=True
)
recommend_card_detection_mode = gr.Dropdown(
choices=[
(RecommendCardDetectionMode.NORMAL.display_name, RecommendCardDetectionMode.NORMAL.value),
(RecommendCardDetectionMode.STRICT.display_name, RecommendCardDetectionMode.STRICT.value)
],
value=current_solution.data.recommend_card_detection_mode.value,
label="推荐卡检测模式",
info="推荐卡检测模式。严格模式下,识别速度会降低,但识别准确率会提高。"
)
use_ap_drink = gr.Checkbox(
label="AP 不足时自动使用 AP 饮料",
value=current_solution.data.use_ap_drink,
info="AP 不足时自动使用 AP 饮料"
)
skip_commu = gr.Checkbox(
label="检测并跳过交流",
value=current_solution.data.skip_commu,
info="检测并跳过交流"
)
# 添加保存按钮
save_solution_btn = gr.Button("保存培育方案", variant="primary")
# 添加事件处理
def update_kotone_warning(selected_idol, recommend_card_detection_mode):
# Handle case where selected_idol is None (no selection)
# selected_idol is a single string (skin_id), not a list
if selected_idol is None:
has_kotone = False
else:
has_kotone = "藤田ことね" in selected_idol
is_strict_mode = recommend_card_detection_mode == RecommendCardDetectionMode.STRICT.value
return gr.Markdown(visible=has_kotone and not is_strict_mode)
def on_solution_change(solution_id):
"""当选择的培育方案改变时,更新所有设置组件"""
if not solution_id:
return [
gr.Textbox(value="", visible=False), # solution_name
gr.Textbox(value="", visible=False), # solution_description
gr.Dropdown(visible=False), # produce_mode
gr.Number(visible=False), # produce_count
gr.Dropdown(visible=False), # produce_idols
gr.Markdown(visible=False), # kotone_warning
gr.Checkbox(visible=False), # auto_set_memory
gr.Group(visible=False), # memory_sets_group
gr.Dropdown(visible=False), # memory_sets
gr.Checkbox(visible=False), # auto_set_support
gr.Checkbox(visible=False), # use_pt_boost
gr.Checkbox(visible=False), # use_note_boost
gr.Checkbox(visible=False), # follow_producer
gr.Dropdown(visible=False), # self_study_lesson
gr.Checkbox(visible=False), # prefer_lesson_ap
gr.Dropdown(visible=False), # actions_order
gr.Dropdown(visible=False), # recommend_card_detection_mode
gr.Checkbox(visible=False), # use_ap_drink
gr.Checkbox(visible=False), # skip_commu
gr.Button(visible=False), # save_solution_btn
]
try:
solution = solution_manager.read(solution_id)
has_kotone = bool(solution.data.idol and "藤田ことね" in solution.data.idol)
is_strict_mode = solution.data.recommend_card_detection_mode == RecommendCardDetectionMode.STRICT
return [
gr.Textbox(value=solution.name, visible=True),
gr.Textbox(value=solution.description or "", visible=True),
gr.Dropdown(value=solution.data.mode, visible=True),
gr.Number(value=self.current_config.options.produce.produce_count, visible=True),
gr.Dropdown(value=solution.data.idol, visible=True),
gr.Markdown(visible=has_kotone and not is_strict_mode),
gr.Checkbox(value=solution.data.auto_set_memory, visible=True),
gr.Group(visible=not solution.data.auto_set_memory),
gr.Dropdown(value=str(solution.data.memory_set) if solution.data.memory_set else None, visible=True),
gr.Checkbox(value=solution.data.auto_set_support_card, visible=True),
gr.Group(visible=not solution.data.auto_set_support_card),
gr.Dropdown(value=str(solution.data.support_card_set) if solution.data.support_card_set else None, visible=True),
gr.Checkbox(value=solution.data.use_pt_boost, visible=True),
gr.Checkbox(value=solution.data.use_note_boost, visible=True),
gr.Checkbox(value=solution.data.follow_producer, visible=True),
gr.Dropdown(value=solution.data.self_study_lesson, visible=True),
gr.Checkbox(value=solution.data.prefer_lesson_ap, visible=True),
gr.Dropdown(value=[action.value for action in solution.data.actions_order], visible=True),
gr.Dropdown(value=solution.data.recommend_card_detection_mode.value, visible=True),
gr.Checkbox(value=solution.data.use_ap_drink, visible=True),
gr.Checkbox(value=solution.data.skip_commu, visible=True),
gr.Button(visible=True), # save_solution_btn
]
except FileNotFoundError:
gr.Warning(f"培育方案 {solution_id} 不存在")
return on_solution_change(None)
except Exception as e:
gr.Error(f"加载培育方案失败:{str(e)}")
return on_solution_change(None)
def on_new_solution():
"""创建新的培育方案"""
try:
new_solution = solution_manager.new("新培育方案")
solution_manager.save(new_solution.id, new_solution)
# 重新列出所有方案
solutions = solution_manager.list()
solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
gr.Success("新培育方案创建成功")
# 根据是否有设置Tab的下拉框来决定返回值
updated_dropdown = gr.Dropdown(choices=solution_choices, value=new_solution.id)
if settings_dropdown is not None:
return [updated_dropdown, updated_dropdown]
else:
return updated_dropdown
except Exception as e:
gr.Error(f"创建培育方案失败:{str(e)}")
if settings_dropdown is not None:
return [gr.Dropdown(), gr.Dropdown()]
else:
return gr.Dropdown()
def on_delete_solution(solution_id):
"""删除当前培育方案"""
if not solution_id:
gr.Warning("请先选择要删除的培育方案")
if settings_dropdown is not None:
return [gr.Dropdown(), gr.Dropdown()]
else:
return gr.Dropdown()
try:
solution_manager.delete(solution_id)
# 重新列出所有方案
solutions = solution_manager.list()
solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
gr.Success("培育方案删除成功")
# 根据是否有设置Tab的下拉框来决定返回值
updated_dropdown = gr.Dropdown(choices=solution_choices, value=None)
if settings_dropdown is not None:
return [updated_dropdown, updated_dropdown]
else:
return updated_dropdown
except Exception as e:
gr.Error(f"删除培育方案失败:{str(e)}")
if settings_dropdown is not None:
return [gr.Dropdown(), gr.Dropdown()]
else:
return gr.Dropdown()
def on_save_solution(solution_id, name, description, mode, count, idols, auto_memory, memory_sets,
auto_support, support_card_sets, pt_boost, note_boost, follow_producer, study_lesson, prefer_ap,
actions, detection_mode, ap_drink, skip_commu_val):
"""保存培育方案"""
if not solution_id:
gr.Warning("请先选择要保存的培育方案")
if settings_dropdown is not None:
return [gr.Dropdown(), gr.Dropdown()]
else:
return gr.Dropdown()
try:
# 构建培育数据
produce_data = ProduceData(
mode=mode,
idol=idols if idols else None,
memory_set=int(memory_sets) if memory_sets else None,
support_card_set=int(support_card_sets) if support_card_sets else None,
auto_set_memory=auto_memory,
auto_set_support_card=auto_support,
use_pt_boost=pt_boost,
use_note_boost=note_boost,
follow_producer=follow_producer,
self_study_lesson=study_lesson,
prefer_lesson_ap=prefer_ap,
actions_order=[ProduceAction(action) for action in actions] if actions else [],
recommend_card_detection_mode=RecommendCardDetectionMode(detection_mode),
use_ap_drink=ap_drink,
skip_commu=skip_commu_val
)
# 构建方案对象
solution = ProduceSolution(
id=solution_id,
name=name or "未命名方案",
description=description,
data=produce_data
)
# 保存方案
solution_manager.save(solution_id, solution)
# 同时更新配置中的 produce_count
self.current_config.options.produce.produce_count = int(count)
# 重新列出所有方案(确保没有重复项)
solutions = solution_manager.list()
solution_choices = [(f"{sol.name} - {sol.description or '无描述'}", sol.id) for sol in solutions]
gr.Success("培育方案保存成功")
# 根据是否有设置Tab的下拉框来决定返回值
updated_dropdown = gr.Dropdown(choices=solution_choices, value=solution_id)
if settings_dropdown is not None:
return [updated_dropdown, updated_dropdown]
else:
return updated_dropdown
except Exception as e:
gr.Error(f"保存培育方案失败:{str(e)}")
if settings_dropdown is not None:
return [gr.Dropdown(), gr.Dropdown()]
else:
return gr.Dropdown()
# 绑定事件
# 为所有控件绑定change事件无论是否有选中方案
auto_set_memory.change(
fn=lambda x: gr.Group(visible=not x),
inputs=[auto_set_memory],
outputs=[memory_sets_group]
)
auto_set_support.change(
fn=lambda x: gr.Group(visible=not x),
inputs=[auto_set_support],
outputs=[support_card_sets_group]
)
if current_solution is not None:
recommend_card_detection_mode.change(
fn=update_kotone_warning,
inputs=[produce_idols, recommend_card_detection_mode],
@ -1355,54 +1774,56 @@ class KotoneBotUI:
outputs=kotone_warning
)
produce_enabled.change(
fn=lambda x: gr.Group(visible=x),
inputs=[produce_enabled],
outputs=[produce_group]
# 绑定方案管理事件
solution_dropdown.change(
fn=on_solution_change,
inputs=[solution_dropdown],
outputs=[
solution_name, solution_description,
produce_mode, produce_count, produce_idols, kotone_warning,
auto_set_memory, memory_sets_group, memory_sets,
auto_set_support, support_card_sets_group, support_card_sets,
use_pt_boost, use_note_boost, follow_producer,
self_study_lesson, prefer_lesson_ap, actions_order,
recommend_card_detection_mode, use_ap_drink, skip_commu,
save_solution_btn
]
)
auto_set_memory.change(
fn=lambda x: gr.Group(visible=not x),
inputs=[auto_set_memory],
outputs=[memory_sets_group]
# 准备输出列表如果设置Tab的下拉框存在则同时更新
outputs_for_new = [solution_dropdown]
outputs_for_delete = [solution_dropdown]
outputs_for_save = [solution_dropdown]
if settings_dropdown is not None:
outputs_for_new.append(settings_dropdown)
outputs_for_delete.append(settings_dropdown)
outputs_for_save.append(settings_dropdown)
new_solution_btn.click(
fn=on_new_solution,
outputs=outputs_for_new
)
delete_solution_btn.click(
fn=on_delete_solution,
inputs=[solution_dropdown],
outputs=outputs_for_delete
)
# 绑定保存按钮事件
save_solution_btn.click(
fn=on_save_solution,
inputs=[
solution_dropdown, solution_name, solution_description,
produce_mode, produce_count, produce_idols,
auto_set_memory, memory_sets, auto_set_support, support_card_sets,
use_pt_boost, use_note_boost, follow_producer,
self_study_lesson, prefer_lesson_ap, actions_order,
recommend_card_detection_mode, use_ap_drink, skip_commu
],
outputs=outputs_for_save
)
def set_config(config: BaseConfig, data: dict[ConfigKey, Any]) -> None:
config.produce.enabled = data['produce_enabled']
config.produce.mode = data['produce_mode']
config.produce.produce_count = data['produce_count']
config.produce.idols = data['produce_idols']
config.produce.memory_sets = [int(i) for i in data['memory_sets']]
config.produce.auto_set_memory = data['auto_set_memory']
config.produce.auto_set_support_card = data['auto_set_support']
config.produce.use_pt_boost = data['use_pt_boost']
config.produce.use_note_boost = data['use_note_boost']
config.produce.follow_producer = data['follow_producer']
config.produce.self_study_lesson = data['self_study_lesson']
config.produce.prefer_lesson_ap = data['prefer_lesson_ap']
config.produce.actions_order = [ProduceAction(action) for action in data['actions_order']]
config.produce.recommend_card_detection_mode = RecommendCardDetectionMode(data['recommend_card_detection_mode'])
config.produce.use_ap_drink = data['use_ap_drink']
config.produce.skip_commu = data['skip_commu']
return set_config, {
'produce_enabled': produce_enabled,
'produce_mode': produce_mode,
'produce_count': produce_count,
'produce_idols': produce_idols,
'memory_sets': memory_sets,
'auto_set_memory': auto_set_memory,
'auto_set_support': auto_set_support,
'use_pt_boost': use_pt_boost,
'use_note_boost': use_note_boost,
'follow_producer': follow_producer,
'self_study_lesson': self_study_lesson,
'prefer_lesson_ap': prefer_lesson_ap,
'actions_order': actions_order,
'recommend_card_detection_mode': recommend_card_detection_mode,
'use_ap_drink': use_ap_drink,
'skip_commu': skip_commu
}
def _create_club_reward_settings(self) -> ConfigBuilderReturnValue:
with gr.Column():
@ -2119,6 +2540,7 @@ class KotoneBotUI:
self._create_status_tab()
self._create_task_tab()
self._create_settings_tab()
self._create_produce_tab()
self._create_log_tab()
self._create_whats_new_tab()
self._create_screen_tab()

View File

@ -1,5 +1,4 @@
import logging
from itertools import cycle
from typing import Optional, Literal
from typing_extensions import assert_never
@ -192,7 +191,7 @@ def do_produce(
前置条件可导航至首页的任意页面\n
结束状态游戏首页\n
:param memory_set_index: 回忆编成编号
:param idol_skin_id: 要培育的偶像如果为 None则使用配置文件中的偶像
:param mode: 培育模式
@ -385,33 +384,38 @@ def produce():
"""
培育任务
"""
if not produce_solution().data.enabled:
if not conf().produce.enabled:
logger.info('Produce is disabled.')
return
import time
count = produce_solution().data.produce_count
idols = produce_solution().data.idols
memory_sets = produce_solution().data.memory_sets
count = conf().produce.produce_count
idol = produce_solution().data.idol
memory_set = produce_solution().data.memory_set
support_card_set = produce_solution().data.support_card_set
mode = produce_solution().data.mode
# 数据验证
if count < 0:
user.warning('配置有误', '培育次数不能小于 0。将跳过本次培育。')
return
if idol is None:
user.warning('配置有误', '未设置要培育的偶像。将跳过本次培育。')
return
idol_iterator = cycle(idols)
memory_set_iterator = cycle(memory_sets)
for i in range(count):
start_time = time.time()
idol = next(idol_iterator)
if produce_solution().data.auto_set_memory:
memory_set = None
memory_set_to_use = None
else:
memory_set = next(memory_set_iterator, None)
memory_set_to_use = memory_set
if produce_solution().data.auto_set_support_card:
support_card_set_to_use = None
else:
support_card_set_to_use = support_card_set
logger.info(
f'Produce start with: '
f'idol: {idol}, mode: {mode}, memory_set: #{memory_set}'
f'idol: {idol}, mode: {mode}, memory_set: #{memory_set_to_use}, support_card_set: #{support_card_set_to_use}'
)
if not do_produce(idol, mode, memory_set):
if not do_produce(idol, mode, memory_set_to_use):
user.info('AP 不足', f'由于 AP 不足,跳过了 {count - i} 次培育。')
logger.info('%d produce(s) skipped because of insufficient AP.', count - i)
break
@ -427,11 +431,11 @@ if __name__ == '__main__':
from kotonebot.kaa.common import BaseConfig
from kotonebot.kaa.main import Kaa
produce_solution().data.enabled = True
conf().produce.enabled = True
conf().produce.produce_count = 1
produce_solution().data.mode = 'pro'
produce_solution().data.produce_count = 1
# produce_solution().data.idols = ['i_card-skin-hski-3-002']
produce_solution().data.memory_sets = [1]
# produce_solution().data.idol = 'i_card-skin-hski-3-002'
produce_solution().data.memory_set = 1
produce_solution().data.auto_set_memory = False
# do_produce(PIdol.月村手毬_初声, 'pro', 5)
produce()