Skip to content

feat: 添加仓库扫描功能及相关界面和配置#2134

Closed
chenshuo318-dotcom wants to merge 37 commits into
OneDragon-Anything:mainfrom
chenshuo318-dotcom:inventory_scan
Closed

feat: 添加仓库扫描功能及相关界面和配置#2134
chenshuo318-dotcom wants to merge 37 commits into
OneDragon-Anything:mainfrom
chenshuo318-dotcom:inventory_scan

Conversation

@chenshuo318-dotcom
Copy link
Copy Markdown

@chenshuo318-dotcom chenshuo318-dotcom commented Mar 24, 2026

实现仓库扫描功能,包括:

  1. 添加代理人信息扫描界面和逻辑
  2. 新增代理人相关屏幕配置
  3. 实现OCR工作线程用于并行处理扫描任务
  4. 添加仓库扫描应用工厂和主界面
  5. 集成扫描功能到主导航界面

新增代理人信息界面配置,包括代理人名称、等级等区域的识别配置。实现自动遍历和扫描代理人信息的功能,并将结果保存为JSON文件。

优化了布局:
image
image

Summary by CodeRabbit

发布说明

  • 新特性
    • 新增仓库扫描功能,支持代理人信息与装备自动扫描,包括预扫描和特定扫描模式
    • 新增代理人管理界面,包含装备、技能、信息等展示和操作
    • 新增代理人权重管理工具,支持自定义权重配置和数据导入导出

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

行走指南

该 PR 为游戏添加库存扫描应用,包括代理人相关的UI屏幕配置、OCR 驱动的数据扫描与解析、图形操作界面、翻译服务以及数据处理工具。

变更

分群 / 文件 摘要
代理人UI屏幕配置
assets/game_data/screen_info/agent_*.yml, assets/game_data/screen_info/agen_skill.yml
新增 6 个代理人相关屏幕定义(代理人-列表、代理人-信息、代理人-技能、代理人-技能详细、代理人-装备、代理人-装备详细),每个屏幕包含 UI 区域、导航路由和模板匹配参数。
菜单集成
assets/game_data/screen_info/_od_merged.yml, assets/game_data/screen_info/menu.yml
在菜单屏幕新增底部代理人导航区域,路由到代理人-列表。
库存扫描应用核心
src/zzz_od/application/inventory_scan/inventory_scan_app.py, src/zzz_od/application/inventory_scan/inventory_scan_app_factory.py, src/zzz_od/application/inventory_scan/inventory_scan_const.py, src/zzz_od/application/inventory_scan/inventory_scan_config.py
定义库存扫描应用的主入口、工厂类、配置常量和代理人扫描选项枚举。
OCR 处理引擎
src/zzz_od/application/inventory_scan/ocr_worker.py
后台 OCR 处理队列,支持异步提交任务、暂停/恢复、结果收集(驱动盘、武器、代理人)。
数据解析器
src/zzz_od/application/inventory_scan/parser/...
代理人、驱动盘、武器解析器,从 OCR 结果提取结构化数据;包初始化文件导出 DriveDiskParser。
预扫描应用
src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py, src/zzz_od/application/inventory_scan/pre_scan/pre_scan_const.py
预扫描应用,遍历并识别代理人列表,收集代理人名称至 JSON。
特定扫描应用
src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py, src/zzz_od/application/inventory_scan/special_scan/special_scan_const.py
针对特定代理人的扫描应用,提取代理人信息、驱动盘、武器数据,生成时间戳 JSON 报告。
GUI 界面
src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py, src/zzz_od/gui/view/one_dragon/zzz_one_dragon_interface.py
库存扫描 GUI(PySide6),包含预扫描/特定扫描模式切换、报告查看、代理人权重管理对话框;在主界面注册。
翻译与国际化服务
src/zzz_od/application/inventory_scan/translation/...
翻译服务加载 zzz_translation.json 并提供字符/武器/装备翻译,包含本地更新器和图标下载器(已废弃)。
库存数据处理
src/zzz_od/application/inventory_scan/InventoryDataProcessor.py
驱动盘扫描数据的加载、词条转换、最优配置计算、实际得分计算,生成处理结果 JSON。
角色权重配置
assets/character_weight/Ye_Shunguang.json, assets/character_weight/_tool/_character_weight.json
单个角色权重文件和权重表汇总。
角色权重工具
assets/character_weight/_tool/build_character_weights.py, assets/character_weight/_tool/process_characters.py, assets/character_weight/_tool/update_character_names.py, assets/character_weight/_tool/slot_Mapping.json
生成角色权重文件、处理角色英文名、更新中文名、槽位映射表的辅助脚本。
翻译数据资源
assets/wiki_data/zzz_translation.json
游戏角色、武器、装备的多语言翻译数据(CHS/EN)。
项目配置
.gitignore
忽略 Node.js 依赖(node_modules/、包文件)。

序列图

sequenceDiagram
    actor User as 用户
    participant GUI as 库存扫描GUI
    participant InventoryScanApp as InventoryScanApp
    participant PreScan as PreScanApp
    participant SpecialScan as SpecialScanApp
    participant OcrWorker as OCR Worker
    participant Parsers as 数据解析器
    participant FileSystem as 文件系统

    User->>GUI: 选择扫描模式<br/>(预扫描/特定扫描)
    GUI->>InventoryScanApp: 启动扫描流程
    InventoryScanApp->>InventoryScanApp: back_at_first()<br/>返回正常世界
    InventoryScanApp->>InventoryScanApp: prepare_screenshots_dir()<br/>初始化截图目录
    
    alt 预扫描模式
        InventoryScanApp->>PreScan: 创建 PreScanApp
        PreScan->>PreScan: navigate_to_agent_info()<br/>导航至代理人-信息屏幕
        PreScan->>PreScan: scan_agents()<br/>遍历代理人列表
        loop 遍历100次或直到解锁检查失败
            PreScan->>OcrWorker: submit('agent', screenshot, parser)
            OcrWorker->>Parsers: 提取代理人名称
            Parsers->>OcrWorker: 返回解析结果
            OcrWorker->>FileSystem: 收集结果
            PreScan->>PreScan: 点击下一个代理人<br/>休眠等待UI更新
        end
        PreScan->>OcrWorker: wait_complete()
        OcrWorker->>PreScan: 返回扫描结果
        PreScan->>FileSystem: 写入 agent_names.json
    else 特定扫描模式
        InventoryScanApp->>SpecialScan: 创建 SpecialScanApp(指定目标代理人)
        SpecialScan->>SpecialScan: navigate_to_agent_info()
        SpecialScan->>SpecialScan: scan_specific_agent()<br/>定位特定代理人
        SpecialScan->>SpecialScan: get_agent_info()<br/>扫描代理人信息
        SpecialScan->>OcrWorker: submit 代理人信息
        
        SpecialScan->>SpecialScan: navigate_to_agent_equipment()
        SpecialScan->>SpecialScan: get_agent_drive()<br/>遍历6个驱动盘位
        loop 每个驱动盘分区
            SpecialScan->>OcrWorker: submit('disc', 分区截图, parser)
            OcrWorker->>Parsers: 解析驱动盘数据
            Parsers->>OcrWorker: 返回结构化数据
        end
        
        SpecialScan->>SpecialScan: get_agent_weapon()
        SpecialScan->>OcrWorker: submit('wengine', 武器截图, parser)
        OcrWorker->>Parsers: 解析武器数据
        
        SpecialScan->>OcrWorker: wait_complete()
        SpecialScan->>SpecialScan: generate_json()<br/>合并所有扫描数据
        SpecialScan->>FileSystem: 写入 <timestamp>_data.json
    end
    
    InventoryScanApp-->>User: 扫描完成/失败反馈
    GUI->>FileSystem: 加载扫描报告JSON
    GUI-->>User: 显示扫描结果<br/>代理人信息、驱动盘、武器
Loading

预估代码审查工作量

🎯 4 (复杂) | ⏱️ ~60 分钟

建议审查人员

  • Usagi-wusaqi

诗歌

🐰 代理人扫描新功能上线,
OCR 处理忙不停,
驱动盘与武器数据齐,
权重计算精又准,
一扫而就显威能,
数据落盘归档妥,
仓库扫描更智能!✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题准确反映了本次变更的主要内容,即添加仓库扫描功能及其相关界面和配置。标题简洁、清晰,涵盖了核心功能模块。
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (11)
src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py (1)

6-6: 移除未使用的导入和冗余赋值

  1. ZApplication 被导入但从未使用(self.app 始终为 None
  2. self.ctx 在第 15 行设置后,又被父类 AppRunInterface.__init__ 在第 20 行重新设置
建议的清理
 from one_dragon_qt.view.app_run_interface import AppRunInterface
 from one_dragon_qt.widgets.setting_card.help_card import HelpCard
 from zzz_od.application.inventory_scan import inventory_scan_const
-from zzz_od.application.zzz_application import ZApplication
 from zzz_od.context.zzz_context import ZContext


 class InventoryScanInterface(AppRunInterface):

     def __init__(self,
                  ctx: ZContext,
                  parent=None):
-        self.ctx: ZContext = ctx
-        self.app: ZApplication | None = None
-
         AppRunInterface.__init__(
             self,
             ctx=ctx,

Also applies to: 15-16

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` at line 6,
当前文件导入了未使用的 ZApplication 和在构造器中对 self.app 与 self.ctx 做了冗余赋值:删除顶部的 from
zzz_od.application.zzz_application import ZApplication 导入,移除或停止设置未使用的属性
self.app(在类构造函数中找到对 self.app 的赋值并删除),并从构造函数中删除对 self.ctx 的重复赋值,保证初始化只由父类
AppRunInterface.__init__ 负责设置 self.ctx(保留父类调用并删除多余赋值语句)。
src/zzz_od/application/inventory_scan/ocr_worker.py (4)

33-34: 计数器的线程安全性

_processed_count_error_count 在工作线程中被修改,可能在主线程中被读取。虽然当前使用模式(在 wait_complete() 后读取)是安全的,但使用 threading.Lockitertools.count 可以让线程安全保证更明确。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py` around lines 33 - 34,
The counters _processed_count and _error_count are not explicitly thread-safe;
wrap their updates and reads with a threading.Lock (e.g., add self._count_lock =
threading.Lock() in the constructor) or replace them with thread-safe counters
(itertools.count or collections.Counter with a lock), then change all increments
(wherever self._processed_count += 1 / self._error_count += 1 occur) to perform
the update inside self._count_lock and ensure wait_complete() or any reader also
acquires the lock when reading the values; update references in the class
(constructor, increment sites, and wait_complete()) to use the chosen
lock-protected accessors so the counts are safely published across threads.

114-118: hasattr 检查是多余的

根据 MatchResult 类的实现,confidencexywh 属性始终在 __init__ 中初始化。这些 hasattr 检查可以安全移除以简化代码。

简化的代码
                     for match in match_list:
                         ocr_items.append({
                             'text': text,
-                            'confidence': match.confidence if hasattr(match, 'confidence') else 1.0,
-                            'position': (match.x, match.y, match.x + match.w, match.y + match.h) if hasattr(match, 'x') else None
+                            'confidence': match.confidence,
+                            'position': (match.x, match.y, match.x + match.w, match.y + match.h)
                         })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py` around lines 114 - 118,
Remove the redundant hasattr checks around MatchResult attributes in
ocr_worker.py: since MatchResult initializes confidence, x, y, w, h in its
__init__, simplify the ocr_items.append call to directly use match.confidence
and compute position from match.x, match.y, match.w, match.h (e.g.,
'confidence': match.confidence and 'position': (match.x, match.y, match.x +
match.w, match.y + match.h)); update the block that builds ocr_items in the
function handling OCR matches to stop using hasattr checks for confidence and x.

94-96: 暂停逻辑错误:等待了错误的事件

_pause_event 被设置时,代码调用 self._stop_event.wait(0.05) 等待停止事件,这是不正确的。应该使用 time.sleep() 或等待暂停事件被清除。

当前实现在暂停状态下,如果 _stop_event 未设置,wait(0.05) 会正常超时返回 False 并继续循环,功能上可以工作,但语义上令人困惑且不直观。

建议的修复
             if self._pause_event.is_set():
-                self._stop_event.wait(0.05)
+                import time
+                time.sleep(0.05)
                 continue

或者在文件顶部添加 import time

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py` around lines 94 - 96,
The pause logic incorrectly waits on self._stop_event inside the loop that
checks self._pause_event; change it to either sleep for a short interval or wait
on the pause event being cleared instead of the stop event: replace
self._stop_event.wait(0.05) with time.sleep(0.05) (and add import time at the
top) or use self._pause_event.wait(timeout=0.05) to block until the pause is
cleared; update the loop in ocr_worker.py where self._pause_event and
self._stop_event are referenced accordingly.

14-14: 建议使用更具体的类型提示

根据编码规范,所有函数和方法都必须包含类型提示。ctx 参数类型为 Any,可以使用 TYPE_CHECKING 模式来提供更精确的类型:

+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from zzz_od.context.zzz_context import ZContext

 class OcrWorker:
-    def __init__(self, ctx: Any):
+    def __init__(self, ctx: 'ZContext'):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py` at line 14, Replace the
vague Any annotation on the __init__ parameter with a concrete context type
using TYPE_CHECKING: add "from typing import TYPE_CHECKING" at top, inside "if
TYPE_CHECKING:" import or declare the real context class name you use in the app
(e.g. InventoryScanContext or AppContext), then change the signature to def
__init__(self, ctx: "InventoryScanContext") (or the actual name) so the runtime
avoids circular imports but static type checkers get the precise type; update
any references in the class to use that context type.
src/zzz_od/application/inventory_scan/inventory_scan_app.py (6)

93-102: 异常处理过于宽泛且路径操作应使用 pathlib

  1. 捕获 Exception 过于宽泛,建议捕获更具体的异常类型(如 OSError, IOError
  2. 路径操作应使用 pathlib 而非 os.path
♻️ 建议的修改
         try:
             # 确保目录存在
-            os.makedirs(self.data_file_path, exist_ok=True)
-            # 使用 os.path.join 拼接路径
-            json_file_path = os.path.join(self.data_file_path, 'agent_names.json')
+            self.data_file_path.mkdir(parents=True, exist_ok=True)
+            json_file_path = self.data_file_path / 'agent_names.json'
             with open(json_file_path, 'w', encoding='utf-8') as f:
                 json.dump(agent_name_list, f, ensure_ascii=False, indent=4)
-        except Exception as e:
+        except OSError as e:
             log.error(f"写代理人名称到JSON文件失败: {e}")
             return self.round_fail(f'写代理人名称到JSON文件失败: {e}')

As per coding guidelines: "Avoid unnecessary try-catch blocks; let exceptions be raised unless meaningful handling is possible" 和 "Use pathlib library for path handling"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 93
- 102, The current try/except around writing agent_names.json is too broad and
uses os.path; narrow the exception handling to specific I/O errors (e.g., catch
OSError/IOError only) and use pathlib.Path for path creation and file writes
(replace os.makedirs/self.data_file_path and os.path.join usage with
Path(self.data_file_path).mkdir(parents=True, exist_ok=True) and
Path(self.data_file_path) / 'agent_names.json'), then only log/return via
self.round_fail when those specific exceptions occur; keep other exceptions
propagated. Reference symbols to change: self.data_file_path, agent_names.json
write block, log.error and self.round_fail.

45-54: 路径操作应使用 pathlib

当前使用 os.path.exists 检查路径存在性,建议改用 pathlib.Path。另外,shutil 应在文件顶部导入。

♻️ 建议的修改
     `@node_from`(from_name='返回大世界-普通')
     `@operation_node`(name='准备截图文件夹')
     def prepare_screenshots_dir(self) -> OperationRoundResult:
         """准备截图文件夹并启动OCR工作线程"""
-        import shutil
-        if os.path.exists(self.screenshots_dir):
+        if self.screenshots_dir.exists():
             shutil.rmtree(self.screenshots_dir)
         self.ocr_worker.reset()
         self.ocr_worker.start()
         return self.round_success('截图文件夹已准备,且OCR工作线程已启动')

同时需要修改 __init__ 中的路径定义:

-        self.screenshots_dir = os_utils.get_path_under_work_dir('.debug', 'inventory_screenshots')
-        self.data_file_path = os_utils.get_path_under_work_dir('.debug', 'inventory_data')
+        self.screenshots_dir = Path(os_utils.get_path_under_work_dir('.debug', 'inventory_screenshots'))
+        self.data_file_path = Path(os_utils.get_path_under_work_dir('.debug', 'inventory_data'))

As per coding guidelines: "Use pathlib library for path handling instead of os.path"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 45
- 54, The method prepare_screenshots_dir should use pathlib for path handling
and move the shutil import to module top: change uses of
os.path.exists(self.screenshots_dir) to treat self.screenshots_dir as a
pathlib.Path and call self.screenshots_dir.exists(), and when removing the
directory use shutil.rmtree(str(self.screenshots_dir)) or
shutil.rmtree(self.screenshots_dir) depending on your Python version; update the
class __init__ to initialize screenshots_dir as a pathlib.Path (e.g., Path(...))
instead of a plain string, and ensure shutil is imported at the file top; keep
the calls to self.ocr_worker.reset() and self.ocr_worker.start() and the
round_success return unchanged.

24-37: __init__ 方法缺少返回类型提示。

根据编码规范,所有函数和方法必须包含类型提示。

♻️ 建议的修改
-    def __init__(self, ctx: ZContext):
+    def __init__(self, ctx: ZContext) -> None:

As per coding guidelines: "All functions and methods must include type hints"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 24
- 37, The __init__ method is missing a return type annotation; update the
constructor signature for __init__ (the method that calls ZApplication.__init__
and sets self.screenshots_dir, self.data_file_path, self.ocr_worker and area
fields like agent_unlocked_area, agent_name_area, switch_next_agent_area) to
include an explicit "-> None" return type (e.g., def __init__(self, ctx:
ZContext) -> None:) so it conforms to the project's type-hinting rules.

65-72: 代码格式和硬编码等待时间。

  1. 等号周围应有空格(第72行 screen= 和第69行 agent_name_list=
  2. 硬编码的等待时间(1秒、0.3秒)可考虑提取为类常量,便于后续调整
♻️ 建议的修改
+    # 等待时间常量
+    SCREEN_LOAD_WAIT = 1.0
+    AGENT_SWITCH_WAIT = 0.3
+
     `@node_from`(from_name='导航到代理人信息界面')
     `@operation_node`(name='扫描代理人基础信息')
     def scan_agents(self) -> OperationRoundResult:
         """扫描代理人基础信息"""
         log.debug("开始扫描代理人基础信息...")
-        time.sleep(1)# 等待代理人信息界面加载完成
-        `#遍历所有代理人`
+        time.sleep(self.SCREEN_LOAD_WAIT)  # 等待代理人信息界面加载完成
+        # 遍历所有代理人
         max_iterations = 100  # 最大循环次数,避免无限循环
         iteration = 0
-        agent_name_list=[]
+        agent_name_list = []
         while iteration < max_iterations:
             iteration += 1
-            screen=self.ctx.controller.get_screenshot()
+            screen = self.ctx.controller.get_screenshot()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 65
- 72, The code has formatting issues (missing spaces around assignment operators
for variables like agent_name_list and screen) and hard-coded sleep durations
(time.sleep(1) and other 0.3s sleeps) inside the inventory scan routine; fix by
adjusting spacing to PEP8 style (use "agent_name_list = []" and "screen =
self.ctx.controller.get_screenshot()") and replace literal sleeps with
configurable class-level constants (e.g. DEFAULT_SLEEP, SHORT_SLEEP) declared on
the InventoryScanApp class (or equivalent) and use those constants in place of
time.sleep(1) / time.sleep(0.3) in the method where agent_name_list, screen, and
the loop/iteration logic live.

1-21: 导入组织和路径处理建议。

  1. shutil 应在文件顶部导入,而非在函数内部(第49行)
  2. 根据编码规范,应使用 pathlib 而非 os.path 进行路径处理
♻️ 建议的导入修改
 from one_dragon.base.operation.application import application_const
 from one_dragon.base.operation.operation_base import OperationResult
 from one_dragon.base.operation.operation_edge import node_from
 from one_dragon.base.operation.operation_node import operation_node
 from one_dragon.base.operation.operation_round_result import OperationRoundResult
 from one_dragon.base.screen.screen_utils import FindAreaResultEnum, find_area_in_screen
 from one_dragon.utils.log_utils import log
 from one_dragon.utils import os_utils
 from zzz_od.application.inventory_scan import inventory_scan_const
 # from zzz_od.application.inventory_scan.inventory_scan_config import InventoryScanConfig
 # from zzz_od.application.inventory_scan.drive_disk.drive_disk_scan_app import DriveDiskScanApp
 # from zzz_od.application.inventory_scan.wengine.wengine_scan_app import WengineScanApp
 # from zzz_od.application.inventory_scan.agent.agent_scan_app import AgentScanApp
 # from zzz_od.application.inventory_scan.screenshot_cache import ScreenshotCache
 from zzz_od.application.inventory_scan.ocr_worker import OcrWorker
 from zzz_od.application.zzz_application import ZApplication
 from zzz_od.context.zzz_context import ZContext
 from zzz_od.operation.back_to_normal_world import BackToNormalWorld
-import os
+from pathlib import Path
 import json
+import shutil
 import time

As per coding guidelines: "Use pathlib library for path handling instead of os.path"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 1 -
21, Move the shutil import from inside the function (currently around line 49)
to the top-level imports and add "from pathlib import Path" next to other
imports; then replace all uses of os.path (e.g. os.path.join, os.path.exists,
os.path.dirname, os.path.abspath) with Path-based operations (Path(...) /
subpath, Path(...).exists(), Path(...).parent, Path(...).resolve(), etc.)
throughout this module (references to classes/functions to locate usage:
OcrWorker, ZApplication, ZContext, BackToNormalWorld and any helpers in
inventory_scan_app.py); keep the top-level import of os only if other non-path
os functionality is used, otherwise remove it to avoid redundancy.

84-91: 使用 log 而非 print 进行日志输出。

为保持日志输出的一致性,建议使用 log.infolog.debug 替代 print

♻️ 建议的修改
-                    print(f"代理人已解锁,名称为: {agent_name}")
+                    log.info(f"代理人已解锁,名称为: {agent_name}")
                     # 点击下一位代理人按钮
                     self.ctx.controller.click(self.switch_next_agent_area.rect.center)
                     time.sleep(0.3)# 等待下一位代理人加载完成
                 else:
                     return self.round_fail('代理人名称扫描失败')
             else:
-                print("代理人未解锁,结束扫描")
+                log.info("代理人未解锁,结束扫描")
                 break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 84
- 91, Replace the two print statements with logger calls to keep logging
consistent: change print(f"代理人已解锁,名称为: {agent_name}") to log.info (or log.debug)
including agent_name, and change print("代理人未解锁,结束扫描") to log.info (or
log.debug). Update the method containing agent_name, self.ctx.controller.click
and self.switch_next_agent_area usage (the same block that calls
self.round_fail('代理人名称扫描失败')) to use the module/class logger (log) instead of
print; ensure the log object is available/imported or use self.log if that is
the existing logger.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py`:
- Around line 93-102: The current try/except around writing agent_names.json is
too broad and uses os.path; narrow the exception handling to specific I/O errors
(e.g., catch OSError/IOError only) and use pathlib.Path for path creation and
file writes (replace os.makedirs/self.data_file_path and os.path.join usage with
Path(self.data_file_path).mkdir(parents=True, exist_ok=True) and
Path(self.data_file_path) / 'agent_names.json'), then only log/return via
self.round_fail when those specific exceptions occur; keep other exceptions
propagated. Reference symbols to change: self.data_file_path, agent_names.json
write block, log.error and self.round_fail.
- Around line 45-54: The method prepare_screenshots_dir should use pathlib for
path handling and move the shutil import to module top: change uses of
os.path.exists(self.screenshots_dir) to treat self.screenshots_dir as a
pathlib.Path and call self.screenshots_dir.exists(), and when removing the
directory use shutil.rmtree(str(self.screenshots_dir)) or
shutil.rmtree(self.screenshots_dir) depending on your Python version; update the
class __init__ to initialize screenshots_dir as a pathlib.Path (e.g., Path(...))
instead of a plain string, and ensure shutil is imported at the file top; keep
the calls to self.ocr_worker.reset() and self.ocr_worker.start() and the
round_success return unchanged.
- Around line 24-37: The __init__ method is missing a return type annotation;
update the constructor signature for __init__ (the method that calls
ZApplication.__init__ and sets self.screenshots_dir, self.data_file_path,
self.ocr_worker and area fields like agent_unlocked_area, agent_name_area,
switch_next_agent_area) to include an explicit "-> None" return type (e.g., def
__init__(self, ctx: ZContext) -> None:) so it conforms to the project's
type-hinting rules.
- Around line 65-72: The code has formatting issues (missing spaces around
assignment operators for variables like agent_name_list and screen) and
hard-coded sleep durations (time.sleep(1) and other 0.3s sleeps) inside the
inventory scan routine; fix by adjusting spacing to PEP8 style (use
"agent_name_list = []" and "screen = self.ctx.controller.get_screenshot()") and
replace literal sleeps with configurable class-level constants (e.g.
DEFAULT_SLEEP, SHORT_SLEEP) declared on the InventoryScanApp class (or
equivalent) and use those constants in place of time.sleep(1) / time.sleep(0.3)
in the method where agent_name_list, screen, and the loop/iteration logic live.
- Around line 1-21: Move the shutil import from inside the function (currently
around line 49) to the top-level imports and add "from pathlib import Path" next
to other imports; then replace all uses of os.path (e.g. os.path.join,
os.path.exists, os.path.dirname, os.path.abspath) with Path-based operations
(Path(...) / subpath, Path(...).exists(), Path(...).parent, Path(...).resolve(),
etc.) throughout this module (references to classes/functions to locate usage:
OcrWorker, ZApplication, ZContext, BackToNormalWorld and any helpers in
inventory_scan_app.py); keep the top-level import of os only if other non-path
os functionality is used, otherwise remove it to avoid redundancy.
- Around line 84-91: Replace the two print statements with logger calls to keep
logging consistent: change print(f"代理人已解锁,名称为: {agent_name}") to log.info (or
log.debug) including agent_name, and change print("代理人未解锁,结束扫描") to log.info (or
log.debug). Update the method containing agent_name, self.ctx.controller.click
and self.switch_next_agent_area usage (the same block that calls
self.round_fail('代理人名称扫描失败')) to use the module/class logger (log) instead of
print; ensure the log object is available/imported or use self.log if that is
the existing logger.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py`:
- Around line 33-34: The counters _processed_count and _error_count are not
explicitly thread-safe; wrap their updates and reads with a threading.Lock
(e.g., add self._count_lock = threading.Lock() in the constructor) or replace
them with thread-safe counters (itertools.count or collections.Counter with a
lock), then change all increments (wherever self._processed_count += 1 /
self._error_count += 1 occur) to perform the update inside self._count_lock and
ensure wait_complete() or any reader also acquires the lock when reading the
values; update references in the class (constructor, increment sites, and
wait_complete()) to use the chosen lock-protected accessors so the counts are
safely published across threads.
- Around line 114-118: Remove the redundant hasattr checks around MatchResult
attributes in ocr_worker.py: since MatchResult initializes confidence, x, y, w,
h in its __init__, simplify the ocr_items.append call to directly use
match.confidence and compute position from match.x, match.y, match.w, match.h
(e.g., 'confidence': match.confidence and 'position': (match.x, match.y, match.x
+ match.w, match.y + match.h)); update the block that builds ocr_items in the
function handling OCR matches to stop using hasattr checks for confidence and x.
- Around line 94-96: The pause logic incorrectly waits on self._stop_event
inside the loop that checks self._pause_event; change it to either sleep for a
short interval or wait on the pause event being cleared instead of the stop
event: replace self._stop_event.wait(0.05) with time.sleep(0.05) (and add import
time at the top) or use self._pause_event.wait(timeout=0.05) to block until the
pause is cleared; update the loop in ocr_worker.py where self._pause_event and
self._stop_event are referenced accordingly.
- Line 14: Replace the vague Any annotation on the __init__ parameter with a
concrete context type using TYPE_CHECKING: add "from typing import
TYPE_CHECKING" at top, inside "if TYPE_CHECKING:" import or declare the real
context class name you use in the app (e.g. InventoryScanContext or AppContext),
then change the signature to def __init__(self, ctx: "InventoryScanContext") (or
the actual name) so the runtime avoids circular imports but static type checkers
get the precise type; update any references in the class to use that context
type.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py`:
- Line 6: 当前文件导入了未使用的 ZApplication 和在构造器中对 self.app 与 self.ctx 做了冗余赋值:删除顶部的 from
zzz_od.application.zzz_application import ZApplication 导入,移除或停止设置未使用的属性
self.app(在类构造函数中找到对 self.app 的赋值并删除),并从构造函数中删除对 self.ctx 的重复赋值,保证初始化只由父类
AppRunInterface.__init__ 负责设置 self.ctx(保留父类调用并删除多余赋值语句)。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a101b99-f6f5-462b-97bc-ecb2c6804126

📥 Commits

Reviewing files that changed from the base of the PR and between 2627f88 and 42fbedb.

📒 Files selected for processing (10)
  • assets/game_data/screen_info/_od_merged.yml
  • assets/game_data/screen_info/agent_info.yml
  • assets/game_data/screen_info/agent_list.yml
  • assets/game_data/screen_info/menu.yml
  • src/zzz_od/application/inventory_scan/inventory_scan_app.py
  • src/zzz_od/application/inventory_scan/inventory_scan_app_factory.py
  • src/zzz_od/application/inventory_scan/inventory_scan_const.py
  • src/zzz_od/application/inventory_scan/ocr_worker.py
  • src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py
  • src/zzz_od/gui/view/one_dragon/zzz_one_dragon_interface.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (9)
src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py (3)

87-94: 使用 log 替代 print() 进行日志输出。

♻️ 建议修改
-                    print(f"代理人已解锁,名称为: {agent_name}")
+                    log.debug(f"代理人已解锁,名称为: {agent_name}")
...
-                print("代理人未解锁,结束扫描")
+                log.debug("代理人未解锁,结束扫描")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py` around lines
87 - 94, Replace direct print() calls with the module/class logger: instead of
print(f"代理人已解锁,名称为: {agent_name}") and print("代理人未解锁,结束扫描") use the appropriate
logger (e.g., self.log.info or self.log.debug) in the PreScanApp class so logs
integrate with the app's logging system; update the messages to the logger
methods and keep the same context (include agent_name), leaving surrounding
logic (self.ctx.controller.click, switch_next_agent_area.rect.center,
round_fail) unchanged.

99-100: 建议使用 pathlib 进行路径处理。

根据编码规范,应使用 pathlib 而非 os.path

♻️ 建议修改
+from pathlib import Path
...
-            os.makedirs(self.data_file_path, exist_ok=True)
-            json_file_path = os.path.join(self.data_file_path, 'agent_names.json')
+            Path(self.data_file_path).mkdir(parents=True, exist_ok=True)
+            json_file_path = Path(self.data_file_path) / 'agent_names.json'

As per coding guidelines: "Use pathlib library for path handling instead of os.path"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py` around lines
99 - 100, Replace os.path usage with pathlib: build the JSON path using
Path(self.data_file_path) / "agent_names.json" instead of os.path.join so change
the creation of json_file_path (and import pathlib.Path at the top) and, if
downstream code expects a string, call str(json_file_path) where needed; target
the json_file_path assignment in the PreScanApp (or the method that assigns
json_file_path using self.data_file_path) to make this change.

51-51: import shutil 移至文件顶部。

Python 风格指南建议将所有导入语句放在文件顶部。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py` at line 51,
Move the "import shutil" statement out of its current inline location and place
it among the other top-level imports at the beginning of the module
(pre_scan_app.py); ensure it follows the module-standard grouping (stdlib first)
and remove the duplicate/local import so that only a single top-level "import
shutil" exists.
src/zzz_od/application/inventory_scan/inventory_scan_app.py (2)

42-42: 使用 log 替代 print() 进行调试输出。

生产代码中应使用日志系统而非 print(),以便于日志级别控制和输出管理。

♻️ 建议修改
-        print(f"获取到的代理人扫描选项: {self.scan_agent_option}, 类型: {type(self.scan_agent_option)}")
+        log.debug(f"获取到的代理人扫描选项: {self.scan_agent_option}, 类型: {type(self.scan_agent_option)}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` at line 42,
Replace the debug print call with the application's logger: remove the
print(f"获取到的代理人扫描选项: {self.scan_agent_option}, 类型:
{type(self.scan_agent_option)}") and instead call the appropriate logger (e.g.,
self.logger or module-level logger) at a suitable level (debug/info), logging
both the value and its type; ensure the logger is imported/initialized if not
present and use a clear message referencing self.scan_agent_option and
type(self.scan_agent_option).

1-23: 清理未使用和注释掉的导入。

文件中存在多个未使用的导入(如 OperationResultnode_fromFindAreaResultEnumfind_area_in_screenapplication_consttimejsonos)以及注释掉的代码(Lines 10-14)。建议在合并前清理这些内容。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 1 -
23, Remove the unused imports and commented-out code in inventory_scan_app.py:
delete OperationResult, node_from, operation_node (if unused),
OperationRoundResult (if unused), FindAreaResultEnum, find_area_in_screen,
application_const, time, json, os (if os is unused vs os_utils), and any
commented imports for
DriveDiskScanApp/WengineScanApp/AgentScanApp/ScreenshotCache lines 10-14; keep
and verify needed imports such as AgentScanOptionEnum, OcrWorker, ZApplication,
ZContext, BackToNormalWorld, inventory_scan_const, os_utils, log, and
PreScanApp; also run the linter/unused-import checker to ensure no other dead
imports remain and remove or re-enable any commented code only if actually
required.
src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py (3)

50-54: 使用 log 替代 print() 进行日志输出。

调试和错误信息应通过日志系统输出,而非 print()

♻️ 建议修改
         except FileNotFoundError:
-            print(f"文件 {agent_names_file_path} 不存在,将使用默认选项{options}")
+            log.debug(f"文件 {agent_names_file_path} 不存在,将使用默认选项{options}")
         except json.JSONDecodeError:
-            print(f"文件 {agent_names_file_path} 格式错误,将使用默认选项{options}")
+            log.warning(f"文件 {agent_names_file_path} 格式错误,将使用默认选项{options}")
         except Exception as e:
-            print(f"读取文件 {agent_names_file_path} 时发生错误: {e},将使用默认选项{options}")
+            log.error(f"读取文件 {agent_names_file_path} 时发生错误: {e},将使用默认选项{options}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` around lines 50 -
54, 将这三处使用 print 的异常/警告输出改为使用日志模块:在处理 FileNotFoundError 和 json.JSONDecodeError
时用 log.warning 包含 agent_names_file_path 和 options;在通用 except Exception as e 分支改为
log.exception 或 log.error(..., exc_info=True) 以记录完整堆栈信息;更新相关 except 块(引用
agent_names_file_path、options、json.JSONDecodeError 和 Exception 分支)以使用 log 而非
print。

83-83: 使用直接赋值替代 setattr

当属性名为常量字符串时,直接赋值更清晰且类型检查器可以更好地分析。

♻️ 建议修改
-            setattr(self.ctx, '_inventory_scan_agent_option', selected_value)
+            self.ctx._inventory_scan_agent_option = selected_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` at line 83, 将对
setatt r 调用替换为对 self.ctx._inventory_scan_agent_option 的直接赋值:在位置使用的
setattr(self.ctx, '_inventory_scan_agent_option', selected_value) 的地方,改为直接把
selected_value 赋给 self.ctx._inventory_scan_agent_option,并在需要时在 ctx
的类定义或构造器中显式声明/初始化 _inventory_scan_agent_option
以满足类型检查(参考符号:setattr、self.ctx、_inventory_scan_agent_option、selected_value)。

40-40: 建议使用 pathlib 进行路径处理。

根据编码规范,应使用 pathlib 而非 os.path

♻️ 建议修改
+from pathlib import Path
...
-        agent_names_file_path = os.path.join(self.data_file_path, 'agent_names.json')
+        agent_names_file_path = Path(self.data_file_path) / 'agent_names.json'

As per coding guidelines: "Use pathlib library for path handling instead of os.path"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` at line 40,
Replace os.path usage when constructing agent_names_file_path in
inventory_scan_interface (the agent_names_file_path =
os.path.join(self.data_file_path, 'agent_names.json') line): import and use
pathlib.Path and build the path via Path(self.data_file_path) /
'agent_names.json', ensuring self.data_file_path is treated as a Path or
converted with Path(...) and update any surrounding code that assumes a str to
call str(...) only where necessary; reference the agent_names_file_path symbol
and self.data_file_path in inventory_scan_interface when making these changes.
src/zzz_od/application/inventory_scan/ocr_worker.py (1)

14-21: 考虑为 ctx 参数添加更具体的类型注解。

ctx 参数使用 Any 类型,但根据代码使用方式,应该是 ZContext 类型。更精确的类型注解有助于 IDE 提供更好的代码补全和类型检查。

♻️ 建议修改
+from zzz_od.context.zzz_context import ZContext
...
-    def __init__(self, ctx: Any):
+    def __init__(self, ctx: ZContext):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py` around lines 14 - 21,
The __init__ signature currently types ctx as Any; change it to the more precise
ZContext type (e.g., def __init__(self, ctx: ZContext):) and add the appropriate
import or forward declaration for ZContext at the top of
src/zzz_od/application/inventory_scan/ocr_worker.py; if ZContext would create a
circular import, use a string forward reference ("ZContext") or
typing.TYPE_CHECKING with a conditional import to preserve runtime behavior
while enabling static type checking and IDE completions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/zzz_od/application/inventory_scan/ocr_worker.py`:
- Around line 36-43: The start() method can spawn duplicate threads if called
while the existing _thread is still running; modify start() to check the current
thread state before creating a new one: verify self._thread is not None and
self._thread.is_alive() (or use _stop_event) and return or log a warning instead
of starting another thread; then only clear _stop_event, reset counters and
create a new threading.Thread targeting _worker when there is no live _thread
(references: start(), _thread, _worker, _stop_event, _processed_count,
_error_count).
- Around line 94-96: The pause loop is waiting on the wrong event: replace the
call to self._stop_event.wait(0.05) inside the if self._pause_event.is_set()
branch with a pause-appropriate wait (e.g., time.sleep(0.05) or waiting on the
pause event to be cleared) so the loop actually sleeps while paused instead of
returning immediately when the stop event is set; update imports to include time
if using time.sleep and ensure you still check self._stop_event elsewhere to
break out when stopped (refer to _pause_event and _stop_event in ocr_worker.py).

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py`:
- Around line 54-56: The OCR worker started in prepare_screenshots_dir
(self.ocr_worker.start()) is never stopped; modify the execute method to ensure
the worker is cleanly stopped in a finally block by calling
self.ocr_worker.stop() (and optionally check self.ocr_worker.is_alive() before
stopping), so that after round_success/round_failure the background thread is
terminated and resources are released; place the stop logic in execute's finally
to guarantee execution even on exceptions.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py`:
- Around line 36-39: The method on_interface_shown currently calls the parent
implementation twice (via super().on_interface_shown() and
AppRunInterface.on_interface_shown(self)); remove the duplicate call so the
parent is invoked only once—prefer using super().on_interface_shown() and delete
the explicit AppRunInterface.on_interface_shown(self) invocation to avoid double
execution of the parent behavior.

---

Nitpick comments:
In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py`:
- Line 42: Replace the debug print call with the application's logger: remove
the print(f"获取到的代理人扫描选项: {self.scan_agent_option}, 类型:
{type(self.scan_agent_option)}") and instead call the appropriate logger (e.g.,
self.logger or module-level logger) at a suitable level (debug/info), logging
both the value and its type; ensure the logger is imported/initialized if not
present and use a clear message referencing self.scan_agent_option and
type(self.scan_agent_option).
- Around line 1-23: Remove the unused imports and commented-out code in
inventory_scan_app.py: delete OperationResult, node_from, operation_node (if
unused), OperationRoundResult (if unused), FindAreaResultEnum,
find_area_in_screen, application_const, time, json, os (if os is unused vs
os_utils), and any commented imports for
DriveDiskScanApp/WengineScanApp/AgentScanApp/ScreenshotCache lines 10-14; keep
and verify needed imports such as AgentScanOptionEnum, OcrWorker, ZApplication,
ZContext, BackToNormalWorld, inventory_scan_const, os_utils, log, and
PreScanApp; also run the linter/unused-import checker to ensure no other dead
imports remain and remove or re-enable any commented code only if actually
required.

In `@src/zzz_od/application/inventory_scan/ocr_worker.py`:
- Around line 14-21: The __init__ signature currently types ctx as Any; change
it to the more precise ZContext type (e.g., def __init__(self, ctx: ZContext):)
and add the appropriate import or forward declaration for ZContext at the top of
src/zzz_od/application/inventory_scan/ocr_worker.py; if ZContext would create a
circular import, use a string forward reference ("ZContext") or
typing.TYPE_CHECKING with a conditional import to preserve runtime behavior
while enabling static type checking and IDE completions.

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py`:
- Around line 87-94: Replace direct print() calls with the module/class logger:
instead of print(f"代理人已解锁,名称为: {agent_name}") and print("代理人未解锁,结束扫描") use the
appropriate logger (e.g., self.log.info or self.log.debug) in the PreScanApp
class so logs integrate with the app's logging system; update the messages to
the logger methods and keep the same context (include agent_name), leaving
surrounding logic (self.ctx.controller.click,
switch_next_agent_area.rect.center, round_fail) unchanged.
- Around line 99-100: Replace os.path usage with pathlib: build the JSON path
using Path(self.data_file_path) / "agent_names.json" instead of os.path.join so
change the creation of json_file_path (and import pathlib.Path at the top) and,
if downstream code expects a string, call str(json_file_path) where needed;
target the json_file_path assignment in the PreScanApp (or the method that
assigns json_file_path using self.data_file_path) to make this change.
- Line 51: Move the "import shutil" statement out of its current inline location
and place it among the other top-level imports at the beginning of the module
(pre_scan_app.py); ensure it follows the module-standard grouping (stdlib first)
and remove the duplicate/local import so that only a single top-level "import
shutil" exists.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py`:
- Around line 50-54: 将这三处使用 print 的异常/警告输出改为使用日志模块:在处理 FileNotFoundError 和
json.JSONDecodeError 时用 log.warning 包含 agent_names_file_path 和 options;在通用
except Exception as e 分支改为 log.exception 或 log.error(..., exc_info=True)
以记录完整堆栈信息;更新相关 except 块(引用 agent_names_file_path、options、json.JSONDecodeError 和
Exception 分支)以使用 log 而非 print。
- Line 83: 将对 setatt r 调用替换为对 self.ctx._inventory_scan_agent_option 的直接赋值:在位置使用的
setattr(self.ctx, '_inventory_scan_agent_option', selected_value) 的地方,改为直接把
selected_value 赋给 self.ctx._inventory_scan_agent_option,并在需要时在 ctx
的类定义或构造器中显式声明/初始化 _inventory_scan_agent_option
以满足类型检查(参考符号:setattr、self.ctx、_inventory_scan_agent_option、selected_value)。
- Line 40: Replace os.path usage when constructing agent_names_file_path in
inventory_scan_interface (the agent_names_file_path =
os.path.join(self.data_file_path, 'agent_names.json') line): import and use
pathlib.Path and build the path via Path(self.data_file_path) /
'agent_names.json', ensuring self.data_file_path is treated as a Path or
converted with Path(...) and update any surrounding code that assumes a str to
call str(...) only where necessary; reference the agent_names_file_path symbol
and self.data_file_path in inventory_scan_interface when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff478741-2c9d-4cfa-bda9-47456589b8a7

📥 Commits

Reviewing files that changed from the base of the PR and between 42fbedb and 4e5f85e.

📒 Files selected for processing (13)
  • assets/game_data/screen_info/_od_merged.yml
  • assets/game_data/screen_info/agent_info.yml
  • assets/game_data/screen_info/agent_list.yml
  • assets/game_data/screen_info/menu.yml
  • src/zzz_od/application/inventory_scan/inventory_scan_app.py
  • src/zzz_od/application/inventory_scan/inventory_scan_app_factory.py
  • src/zzz_od/application/inventory_scan/inventory_scan_config.py
  • src/zzz_od/application/inventory_scan/inventory_scan_const.py
  • src/zzz_od/application/inventory_scan/ocr_worker.py
  • src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py
  • src/zzz_od/application/inventory_scan/pre_scan/pre_scan_const.py
  • src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py
  • src/zzz_od/gui/view/one_dragon/zzz_one_dragon_interface.py
✅ Files skipped from review due to trivial changes (6)
  • assets/game_data/screen_info/agent_list.yml
  • src/zzz_od/application/inventory_scan/inventory_scan_app_factory.py
  • src/zzz_od/application/inventory_scan/pre_scan/pre_scan_const.py
  • src/zzz_od/application/inventory_scan/inventory_scan_const.py
  • assets/game_data/screen_info/agent_info.yml
  • assets/game_data/screen_info/_od_merged.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/zzz_od/gui/view/one_dragon/zzz_one_dragon_interface.py
  • assets/game_data/screen_info/menu.yml

Comment thread src/zzz_od/application/inventory_scan/ocr_worker.py
Comment thread src/zzz_od/application/inventory_scan/ocr_worker.py
Comment thread src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py Outdated
Comment thread src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py Outdated
实现仓库扫描功能,包括:
1. 添加代理人信息扫描界面和逻辑
2. 新增代理人相关屏幕配置
3. 实现OCR工作线程用于并行处理扫描任务
4. 添加仓库扫描应用工厂和主界面
5. 集成扫描功能到主导航界面

新增代理人信息界面配置,包括代理人名称、等级等区域的识别配置。实现自动遍历和扫描代理人信息的功能,并将结果保存为JSON文件。
实现预扫描功能用于更新代理人列表,并在界面中添加代理人选择下拉框
将代理人扫描逻辑从主应用拆分到预扫描模块,提高代码可维护性
添加Node.js微服务用于处理驱动盘评分功能,包括:
1. 创建Express服务端和Python客户端
2. 实现驱动盘评分API接口
3. 添加相关文档说明
4. 更新.gitignore忽略node_modules
5. 优化OCR工作线程和界面显示逻辑
refactor(配置): 更新代理人装备页面配置
chore: 移除不再使用的node_service文件
docs: 添加特定扫描常量定义
增加对scan_agent_option为空的检查,避免空指针异常
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py (1)

12-18: __init__ 补上 -> None

当前构造函数少了返回类型标注。

As per coding guidelines, **/*.py: All functions and methods must include type hints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py`
around lines 12 - 18, Update the constructor signature to include the return
type annotation "-> None" for the __init__ method in the special_scan
application class; locate the __init__(self, ctx: ZContext) that calls
ZApplication.__init__(self, ctx=ctx, app_id=special_scan_const.APP_ID,
op_name=special_scan_const.APP_NAME) and change its definition to include "->
None" so the method signature becomes def __init__(self, ctx: ZContext) -> None:
while leaving the existing body and calls unchanged.
src/zzz_od/application/inventory_scan/inventory_scan_app.py (2)

62-70: 直接透传子应用结果,不要在这一层吞掉异常和失败原因。

这里把 PreScanApp / SpecialScanApp 的执行结果压成了固定文案,还用 except Exception 把 traceback 吞掉了;像“未检测到代理人姓名”“超过最大循环次数”这类具体失败点都会在这里丢失。更合适的写法是直接 round_by_op_result(...),让上层拿到原始结果。

♻️ 建议修改
-            try:
-                self.pre_scan_app = PreScanApp(self.ctx)
-                result = self.pre_scan_app.execute()
-                if result.success:
-                    return self.round_success('预扫描完成')
-                else:
-                    return self.round_fail('预扫描失败')
-            except Exception as e:
-                return self.round_fail(f'预扫描执行失败: {str(e)}')
+            self.pre_scan_app = PreScanApp(self.ctx)
+            return self.round_by_op_result(self.pre_scan_app.execute())
@@
-            try:
-                self.special_scan_app = SpecialScanApp(self.ctx)
-                result = self.special_scan_app.execute()
-                if result.success:
-                    return self.round_success('特定扫描完成')
-                else:
-                    return self.round_fail('特定扫描失败')
-            except Exception as e:
-                return self.round_fail(f'特定扫描执行失败: {str(e)}')
+            self.special_scan_app = SpecialScanApp(self.ctx)
+            return self.round_by_op_result(self.special_scan_app.execute())

As per coding guidelines, **/*.py: Avoid unnecessary try-catch blocks; let exceptions be raised unless meaningful handling is possible.

Also applies to: 80-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 62
- 70, The current code in InventoryScanApp is swallowing child app results and
exceptions for PreScanApp (and similarly for SpecialScanApp at 80-88): remove
the broad try/except and stop mapping every outcome to fixed messages; instead
return the child operation result through the inventory scan helper (use
round_by_op_result or equivalent) so the original success/failure and error
details propagate. Concretely, replace the try/except around
PreScanApp(self.ctx).execute() with a direct call that returns
round_by_op_result(result) (and do the same for SpecialScanApp), and delete the
generic except Exception blocks so tracebacks and specific failure reasons are
preserved.

28-52: 把这里的重复初始化收回到子应用里。

当前类后续只负责分发到 PreScanApp / SpecialScanApp,但 OcrWorker、多个 screen area 和 max_iterations 在本类里都没有被读取;同样的依赖已经分别在子应用里初始化了。保留这批重复状态只会增加耦合,后续改动也容易漂移;同时 __init__ 还少了 -> None

As per coding guidelines, **/*.py: All functions and methods must include type hints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py` around lines 28
- 52, The __init__ of InventoryScanApp currently initializes OcrWorker, many
screen areas (agent_unlocked_area, agent_name_area, switch_next_agent_area,
agent_drive_1_area...agent_drive_6_area) and max_iterations even though this
class only dispatches to PreScanApp/SpecialScanApp and these dependencies are
already initialized in those child apps; remove those duplicated initializations
from InventoryScanApp.__init__ and let PreScanApp/SpecialScanApp own them to
reduce coupling, while keeping only shared state actually used by
InventoryScanApp (e.g., scan_agent_option and screenshots/data paths if truly
shared); also add the missing return type annotation -> None to
InventoryScanApp.__init__ and ensure any moved initializations are created in
the corresponding child classes (look for OcrWorker usages and agent_* area
references in PreScanApp and SpecialScanApp to migrate).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/game_data/screen_info/_od_merged.yml`:
- Around line 220-233: The template for the region with area_name "属性加成" is
incorrectly set to template_sub_dir: menu and template_id: back (a back-button
template); update the region (area_name "属性加成", id_mark true) to use the correct
equipment/attribute template by replacing template_sub_dir and template_id with
the proper template used for the attribute label (e.g., the equipment/attribute
label template in your repo), ensuring id_mark remains true and
template_match_threshold and lcs_percent are preserved.

In `@src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py`:
- Around line 80-85: The method get_agent_drive currently clicks only
agent_drive_1_area and returns success, which prematurely ends scanning; update
get_agent_drive to iterate over all agent drive areas (e.g., agent_drive_1_area
through agent_drive_6_area or a consolidated list like self.agent_drive_areas),
click each area's rect.center, wait/verify each scan result (collect recognition
outcomes), aggregate these results, and only call round_success when all drives
have been processed; use the existing self.ctx.controller.click and any
available verification helper to detect/record scan completion for each drive
and include that aggregated status in the final return.

---

Nitpick comments:
In `@src/zzz_od/application/inventory_scan/inventory_scan_app.py`:
- Around line 62-70: The current code in InventoryScanApp is swallowing child
app results and exceptions for PreScanApp (and similarly for SpecialScanApp at
80-88): remove the broad try/except and stop mapping every outcome to fixed
messages; instead return the child operation result through the inventory scan
helper (use round_by_op_result or equivalent) so the original success/failure
and error details propagate. Concretely, replace the try/except around
PreScanApp(self.ctx).execute() with a direct call that returns
round_by_op_result(result) (and do the same for SpecialScanApp), and delete the
generic except Exception blocks so tracebacks and specific failure reasons are
preserved.
- Around line 28-52: The __init__ of InventoryScanApp currently initializes
OcrWorker, many screen areas (agent_unlocked_area, agent_name_area,
switch_next_agent_area, agent_drive_1_area...agent_drive_6_area) and
max_iterations even though this class only dispatches to
PreScanApp/SpecialScanApp and these dependencies are already initialized in
those child apps; remove those duplicated initializations from
InventoryScanApp.__init__ and let PreScanApp/SpecialScanApp own them to reduce
coupling, while keeping only shared state actually used by InventoryScanApp
(e.g., scan_agent_option and screenshots/data paths if truly shared); also add
the missing return type annotation -> None to InventoryScanApp.__init__ and
ensure any moved initializations are created in the corresponding child classes
(look for OcrWorker usages and agent_* area references in PreScanApp and
SpecialScanApp to migrate).

In `@src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py`:
- Around line 12-18: Update the constructor signature to include the return type
annotation "-> None" for the __init__ method in the special_scan application
class; locate the __init__(self, ctx: ZContext) that calls
ZApplication.__init__(self, ctx=ctx, app_id=special_scan_const.APP_ID,
op_name=special_scan_const.APP_NAME) and change its definition to include "->
None" so the method signature becomes def __init__(self, ctx: ZContext) -> None:
while leaving the existing body and calls unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31d24c7e-e06b-41ba-8cb2-06e54911cdee

📥 Commits

Reviewing files that changed from the base of the PR and between 668cd62 and 1935b83.

📒 Files selected for processing (7)
  • .gitignore
  • assets/game_data/screen_info/_od_merged.yml
  • assets/game_data/screen_info/agent_equipment.yml
  • assets/game_data/screen_info/agent_info.yml
  • src/zzz_od/application/inventory_scan/inventory_scan_app.py
  • src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py
  • src/zzz_od/application/inventory_scan/special_scan/special_scan_const.py
✅ Files skipped from review due to trivial changes (4)
  • .gitignore
  • src/zzz_od/application/inventory_scan/special_scan/special_scan_const.py
  • assets/game_data/screen_info/agent_equipment.yml
  • assets/game_data/screen_info/agent_info.yml

Comment thread assets/game_data/screen_info/_od_merged.yml
Comment thread src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@assets/character_weight/_tool/build_character_weights.py`:
- Around line 5-13: The script hardcodes absolute Windows paths
(character_weight_json, character_dir, and the zzz_translation.json open call)
which breaks on other machines; replace these with pathlib usage and build paths
relative to the repository or the script location (e.g., using
Path(__file__).resolve().parent / '..' or a repo root detection) to construct
character_dir, character_weight_json, and the translation file path, then open
those Path objects with .open(encoding='utf-8') instead of string literals so
the script runs portably across environments.

In `@src/zzz_od/application/inventory_scan/InventoryDataProcessor.py`:
- Around line 22-29: SLOT_MAIN_POOLS contains inconsistent stat names for slot 5
("以太属性伤害加成"/"物理属性伤害加成") which don't match the keys used by
calculate_optimal_disc_config and MAIN_STAT_GAIN; update the entries in
SLOT_MAIN_POOLS (and the similar entries around lines 45-49) to use the
canonical names "以太伤害加成" and "物理伤害加成" so they match
calculate_optimal_disc_config's character_weight keys and MAIN_STAT_GAIN.
- Around line 109-118: The loader currently returns the raw JSON so metadata
objects like {"name": "...", "weight": ...} get treated as a weight table and
later cause a TypeError in calculate_optimal_disc_config(); change
load_character_weight (in InventoryDataProcessor) to inspect the parsed JSON
and, if it contains a top-level "weight" key, return json_data["weight"]
(otherwise return the parsed data), and keep the existing error handling/logging
behavior so callers receive the proper weight mapping instead of the metadata
object.

In `@src/zzz_od/application/inventory_scan/parser/agent_parser.py`:
- Around line 223-234: _parse_cinema_level is falsely matching substrings like
"60/60" because it searches for any (\d)/(\d) inside the text; change the logic
to only accept the whole token as an X/Y grade: after cleaning OCR errors in
_parse_cinema_level, use a full-match regex (e.g.
re.fullmatch(r'\d{1,2}/\d{1,2}', cleaned)) or re.match(r'^\d{1,2}/\d{1,2}$',
cleaned) so you don't match substrings, then parse the two numbers and validate
them (numerator <= denominator and denominator in the expected range, e.g. <=6)
before returning f"{num}/{denom}"; otherwise continue and return "0/6".

In `@src/zzz_od/application/inventory_scan/parser/drive_disk_parser.py`:
- Around line 214-223: The _parse_slot function currently returns the literal
'1' on parse failure which silently mislabels unknown slots and can overwrite
real slot 1 in downstream code (e.g. SpecialScanApp.generate_json); change
_parse_slot to indicate "unknown" explicitly (e.g. return None or an empty
string and update its return type to Optional[str]) instead of defaulting to
'1', and then update callers like SpecialScanApp.generate_json to skip/handle
records with a missing slotKey (do not insert them into equippedDiscs or use a
distinct placeholder key) so failed parses cannot silently overwrite valid slot
1 data.

In `@src/zzz_od/application/inventory_scan/parser/wengine_parser.py`:
- Around line 150-167: In _parse_modification, the code indexes gray[y, x]
without bounds checking which can raise IndexError for different crop sizes;
before reading gray[y, x] validate screenshot/gray dimensions (use gray.shape to
get height/width) and ensure 0 <= x < width and 0 <= y < height for each sample
point (x computed as 260 + i*30, y=160); if a point is out of bounds either
clamp coordinates into range or skip that sample and proceed (or return a safe
default like 0 if too few samples), and update gray_values handling accordingly
to avoid exceptions when parsing.

In `@src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py`:
- Around line 61-72: The loop stops only when an "unlocked" area is detected,
causing repeated OCR submissions if the page doesn't change or all agents are
unlocked; modify the loop in pre_scan_app (around methods screenshot(),
find_area_in_screen(), ctx.controller.click(...), ocr_worker.submit(...),
agent_name_area, agent_unlocked_area) to detect and break on no-page-change or
duplicate agents: after clicking the next-agent button, capture a new screenshot
and compare it to the previous screenshot (or compute a simple hash/CRC) and if
identical, break the loop; additionally maintain a small set of seen agent
identifiers (e.g., hash of cropped agent_name_area) and skip/subsequently break
if the same agent is encountered again to avoid duplicate ocr_worker.submit
calls.

In `@src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py`:
- Around line 107-122: round_by_goto_screen('代理人-技能') and the post-click
navigation are not checked, so screenshot/crop may run on the wrong UI; update
the flow in special_scan_app.py to validate navigation and clicks before
taking/cropping images: call round_by_goto_screen(...) and assert its
success/return value (or verify current screen via ctx.screen_loader or a known
screen match) and return round_fail on failure; after
ctx.controller.click(self.agent_core_skill_area.rect.center) wait, re-screenshot
and verify the expected "核心技等级" area exists (e.g.,
ctx.screen_loader.get_area(...) returns a usable rect) before assigning
self._img_skill or proceeding, and keep existing screenshot None checks and fail
fast if any verification fails.

In `@src/zzz_od/application/inventory_scan/translation/translation_service.py`:
- Around line 128-132: The fuzzy-match threshold used in
translation_service._fuzzy_match is too low (0.2) and can cause OCR noise to be
accepted as authoritative keys later consumed by DriveDiskParser._parse_set_name
and WengineParser._parse_wengine_name; change the behavior so fuzzy matches are
not blindly used as exportable keys: either raise the confidence cut-off to a
conservative value (e.g., >=0.8) or mark fuzzy results as tentative and do NOT
call extract_translation in the code-path that generates persistent keys (so
setKey/key creation only uses exact matches or explicitly confirmed
translations). Update translation_service where best_match/confidence are
checked and, if keeping fuzzy fallback, add a boolean flag or different API call
to extract_translation to indicate “do not use for key export” and ensure
DriveDiskParser._parse_set_name and WengineParser._parse_wengine_name only
accept translations that are exact matches or have the stronger
confidence/confirmation.

In `@src/zzz_od/application/inventory_scan/translation/translation_updater.py`:
- Around line 87-90: The code currently resets translation_dict['weapon'] and
['equipment'] to empty dicts which breaks stable mappings used by update_all(),
TranslationService.translate_weapon()/translate_equipment(), WengineParser and
DriveDiskParser; instead remove the unconditional resets and preserve or merge
existing mappings by using a conditional initialization or merge (e.g., only
setdefault('weapon', {}) / setdefault('equipment', {}) or update existing dicts
with new entries) so existing translation keys remain available to the parsers
and translators.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py`:
- Around line 604-639: The UI is still reading the old equippedEngines schema;
update the rendering in inventory_scan_interface.py (the block that sets
self.agent_info_label and self.engine_info_label) to read
report.get('equippedWengine') instead of 'equippedEngines' and map the new
WengineParser fields: use the Wengine "key" as the engine name, "level" for 等级,
include "modification" and "promotion" as additional lines (e.g., 修改/改造 and 晋升),
and avoid expecting 'rarity'/'type'/'skills' (only show them if present). Ensure
both the agent_info list and engine_info list iterate the single equippedWengine
object (handle None) and produce sensible fallback text (未知 or 未装备音擎) when data
is missing.
- Around line 1061-1084: The save path used in on_save_weights writes edits to
data_file_path/agent_weights.json while _load_weights_data() only loads JSONs
from assets/character_weight, causing saved changes to be ignored; fix by making
the sources consistent: either change on_save_weights to write the edited agent
JSON back into the assets/character_weight location (maintaining filename/format
used by _load_weights_data()) or update _load_weights_data to also read and
merge data_file_path/agent_weights.json (and ensure merging logic preserves
expected dict structure). Update references in on_save_weights,
_load_weights_data, and any usages of agent_weights or update_weight_table to
load the same canonical source so saved edits persist across reloads.
- Around line 280-285: The current loop overwrites report_files[agent_name] when
multiple '*_data_*.json' exist because os.listdir() order is unstable; change
the loop that iterates file_name under self.data_file_path so it selects the
newest report per agent instead of last-seen: for each matching file compute
agent_name (file_name.split('_data_')[0]) and candidate_path =
os.path.join(self.data_file_path, file_name), then compare
os.path.getmtime(candidate_path) (or parse a timestamp from the filename if
available) against any existing report_files[agent_name] and keep only the path
with the latest modification time; update report_files accordingly so each agent
maps to its newest file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60ef971b-5fd9-4da9-9534-e834ae366b14

📥 Commits

Reviewing files that changed from the base of the PR and between 1935b83 and a91a624.

📒 Files selected for processing (28)
  • assets/character_weight/Ye_Shunguang.json
  • assets/character_weight/_tool/_character_weight.json
  • assets/character_weight/_tool/build_character_weights.py
  • assets/character_weight/_tool/process_characters.py
  • assets/character_weight/_tool/slot_Mapping.json
  • assets/character_weight/_tool/update_character_names.py
  • assets/game_data/screen_info/_od_merged.yml
  • assets/game_data/screen_info/agen_skill.yml
  • assets/game_data/screen_info/agent_equipment.yml
  • assets/game_data/screen_info/agent_equipment_detail.yml
  • assets/game_data/screen_info/agent_info.yml
  • assets/game_data/screen_info/agent_list.yml
  • assets/game_data/screen_info/agent_skill_detail.yml
  • assets/wiki_data/zzz_translation.json
  • src/zzz_od/application/inventory_scan/InventoryDataProcessor.py
  • src/zzz_od/application/inventory_scan/inventory_scan_app.py
  • src/zzz_od/application/inventory_scan/parser/__init__.py
  • src/zzz_od/application/inventory_scan/parser/agent_name_parser.py
  • src/zzz_od/application/inventory_scan/parser/agent_parser.py
  • src/zzz_od/application/inventory_scan/parser/drive_disk_parser.py
  • src/zzz_od/application/inventory_scan/parser/wengine_parser.py
  • src/zzz_od/application/inventory_scan/pre_scan/pre_scan_app.py
  • src/zzz_od/application/inventory_scan/special_scan/special_scan_app.py
  • src/zzz_od/application/inventory_scan/translation/__init__.py
  • src/zzz_od/application/inventory_scan/translation/icon_downloader.py
  • src/zzz_od/application/inventory_scan/translation/translation_service.py
  • src/zzz_od/application/inventory_scan/translation/translation_updater.py
  • src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py
✅ Files skipped from review due to trivial changes (12)
  • src/zzz_od/application/inventory_scan/parser/init.py
  • assets/character_weight/_tool/slot_Mapping.json
  • src/zzz_od/application/inventory_scan/translation/init.py
  • assets/character_weight/_tool/_character_weight.json
  • assets/wiki_data/zzz_translation.json
  • assets/character_weight/Ye_Shunguang.json
  • assets/game_data/screen_info/agent_equipment_detail.yml
  • assets/game_data/screen_info/agent_list.yml
  • assets/game_data/screen_info/agent_equipment.yml
  • assets/game_data/screen_info/agent_info.yml
  • assets/game_data/screen_info/agen_skill.yml
  • assets/game_data/screen_info/agent_skill_detail.yml

Comment on lines +5 to +13
character_weight_json = 'd:\\my\project\ZenlessZoneZero-OneDragon\\assets\\character_weight\\_character_weight.json'
character_dir = 'd:\\my\project\ZenlessZoneZero-OneDragon\\assets\\character_weight'

# 读取_character_weight.json文件
with open(character_weight_json, 'r', encoding='utf-8') as f:
weight_data = json.load(f)

# 读取zzz_translation.json文件以获取角色中文名
with open('d:\\my\\project\\ZenlessZoneZero-OneDragon\\assets\\wiki_data\\zzz_translation.json', 'r', encoding='utf-8') as f:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要把这组 _tool 脚本绑死在本机盘符上。

这里把仓库路径写死成 d:\...,这会让 build_character_weights.py,以及同目录里的 process_characters.py / update_character_names.py 只能在你的本地目录结构下运行;换一台机器、换一个 clone 路径,脚本就会直接失效。

🛠️ 建议修复
+from pathlib import Path
+
-# 文件路径
-character_weight_json = 'd:\\my\project\ZenlessZoneZero-OneDragon\\assets\\character_weight\\_character_weight.json'
-character_dir = 'd:\\my\project\ZenlessZoneZero-OneDragon\\assets\\character_weight'
+repo_root = Path(__file__).resolve().parents[3]
+character_weight_json = repo_root / 'assets' / 'character_weight' / '_character_weight.json'
+character_dir = repo_root / 'assets' / 'character_weight'
 
 # 读取_character_weight.json文件
-with open(character_weight_json, 'r', encoding='utf-8') as f:
+with character_weight_json.open('r', encoding='utf-8') as f:
     weight_data = json.load(f)
 
 # 读取zzz_translation.json文件以获取角色中文名
-with open('d:\\my\\project\\ZenlessZoneZero-OneDragon\\assets\\wiki_data\\zzz_translation.json', 'r', encoding='utf-8') as f:
+translation_path = repo_root / 'assets' / 'wiki_data' / 'zzz_translation.json'
+with translation_path.open('r', encoding='utf-8') as f:
     translation_data = json.load(f)

As per coding guidelines, Use pathlib library for path handling instead of os.path.

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 5-5: Invalid escape sequence: \p

Add backslash to escape sequence

(W605)


[warning] 5-5: Invalid escape sequence: \Z

Add backslash to escape sequence

(W605)


[warning] 6-6: Invalid escape sequence: \p

Add backslash to escape sequence

(W605)


[warning] 6-6: Invalid escape sequence: \Z

Add backslash to escape sequence

(W605)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@assets/character_weight/_tool/build_character_weights.py` around lines 5 -
13, The script hardcodes absolute Windows paths (character_weight_json,
character_dir, and the zzz_translation.json open call) which breaks on other
machines; replace these with pathlib usage and build paths relative to the
repository or the script location (e.g., using Path(__file__).resolve().parent /
'..' or a repo root detection) to construct character_dir,
character_weight_json, and the translation file path, then open those Path
objects with .open(encoding='utf-8') instead of string literals so the script
runs portably across environments.

Comment thread src/zzz_od/application/inventory_scan/InventoryDataProcessor.py
Comment on lines +109 to +118
def load_character_weight(self, character_weight_dir, key):
"""加载指定角色的权重配置"""
weight_file = Path(character_weight_dir) / f"{key}.json"
if weight_file.exists():
try:
with open(weight_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载权重文件 {weight_file} 失败: {e}")
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

这里会把权重元数据当成权重表,首个命中的角色就会崩。

按本 PR 里的 build_character_weights.py,角色权重文件写回后的结构是 {name, weight}。这里原样返回整个 JSON,后面的 calculate_optimal_disc_config() 会遍历到 ('name', '安比') 并执行 weight > 0,直接抛 TypeError

🛠️ 建议修复
     def load_character_weight(self, character_weight_dir, key):
         """加载指定角色的权重配置"""
         weight_file = Path(character_weight_dir) / f"{key}.json"
         if weight_file.exists():
             try:
                 with open(weight_file, 'r', encoding='utf-8') as f:
-                    return json.load(f)
+                    data = json.load(f)
+                    if isinstance(data, dict) and isinstance(data.get('weight'), dict):
+                        return data['weight']
+                    if isinstance(data, dict):
+                        return data
+                    return None
             except Exception as e:
                 print(f"加载权重文件 {weight_file} 失败: {e}")
         return None
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 116-116: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/InventoryDataProcessor.py` around lines
109 - 118, The loader currently returns the raw JSON so metadata objects like
{"name": "...", "weight": ...} get treated as a weight table and later cause a
TypeError in calculate_optimal_disc_config(); change load_character_weight (in
InventoryDataProcessor) to inspect the parsed JSON and, if it contains a
top-level "weight" key, return json_data["weight"] (otherwise return the parsed
data), and keep the existing error handling/logging behavior so callers receive
the proper weight mapping instead of the metadata object.

Comment on lines +223 to +234
def _parse_cinema_level(self, results: List[Dict]) -> str:
"""解析影画等级(命座)"""
for result in results:
text = result['text']
# 匹配"X/Y"格式
if '/' in text and len(text) <= 5:
# 清理可能的OCR错误(如O识别成0)
cleaned = text.replace('O', '0').replace('o', '0')
match = re.search(r'(\d)/(\d)', cleaned)
if match:
return f"{match.group(1)}/{match.group(2)}"
return "0/6"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

影画解析会把 60/60 之类的等级文本误判成命座。

_parse_cinema_level() 现在只要看到短斜杠文本就做 (\d)/(\d) 搜索。像 60/60 会命中成 0/610/12 会命中成 0/1,最后 mindscape 被写成错误值。

💡 建议修复
     def _parse_cinema_level(self, results: List[Dict]) -> str:
         """解析影画等级(命座)"""
         for result in results:
             text = result['text']
-            # 匹配"X/Y"格式
-            if '/' in text and len(text) <= 5:
-                # 清理可能的OCR错误(如O识别成0)
-                cleaned = text.replace('O', '0').replace('o', '0')
-                match = re.search(r'(\d)/(\d)', cleaned)
-                if match:
-                    return f"{match.group(1)}/{match.group(2)}"
+            cleaned = text.replace(' ', '').replace('O', '0').replace('o', '0')
+            match = re.fullmatch(r'([0-6])/6', cleaned)
+            if match:
+                return f"{match.group(1)}/6"
         return "0/6"
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 224-224: Docstring contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF002)


[warning] 224-224: Docstring contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF002)


[warning] 229-229: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


[warning] 229-229: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/parser/agent_parser.py` around lines
223 - 234, _parse_cinema_level is falsely matching substrings like "60/60"
because it searches for any (\d)/(\d) inside the text; change the logic to only
accept the whole token as an X/Y grade: after cleaning OCR errors in
_parse_cinema_level, use a full-match regex (e.g.
re.fullmatch(r'\d{1,2}/\d{1,2}', cleaned)) or re.match(r'^\d{1,2}/\d{1,2}$',
cleaned) so you don't match substrings, then parse the two numbers and validate
them (numerator <= denominator and denominator in the expected range, e.g. <=6)
before returning f"{num}/{denom}"; otherwise continue and return "0/6".

Comment on lines +214 to +223
def _parse_slot(self, texts: List[str]) -> str:
"""解析装备位置"""
for text in texts:
match = re.search(r'\[(\d)\]', text)
if match:
return match.group(1)
# 单独的数字1-6
if text.strip() in ['1', '2', '3', '4', '5', '6']:
return text.strip()
return '1'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要把未识别分区默认为 1 号位。

Line 223 返回 '1' 会把任何分区识别失败都伪造成 1 号位。后面 SpecialScanApp.generate_json()slotKey 作为 equippedDiscs 的 key,这种错误结果会直接覆盖真实的 1 号位数据,属于静默数据损坏。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/parser/drive_disk_parser.py` around
lines 214 - 223, The _parse_slot function currently returns the literal '1' on
parse failure which silently mislabels unknown slots and can overwrite real slot
1 in downstream code (e.g. SpecialScanApp.generate_json); change _parse_slot to
indicate "unknown" explicitly (e.g. return None or an empty string and update
its return type to Optional[str]) instead of defaulting to '1', and then update
callers like SpecialScanApp.generate_json to skip/handle records with a missing
slotKey (do not insert them into equippedDiscs or use a distinct placeholder
key) so failed parses cannot silently overwrite valid slot 1 data.

Comment on lines +128 to +132
# 3. 模糊匹配(相似度>0.2,取最接近的)
best_match, confidence = self._fuzzy_match(name, category_dict)
if best_match and confidence > 0.2:
log.info(f"模糊匹配: {name} -> {best_match} (置信度: {confidence:.4f})")
return extract_translation(category_dict[best_match], target_lang, name)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

别用 0.2 的模糊匹配阈值直接生成导出 key。

Line 130 这个阈值太宽松了。DriveDiskParser._parse_set_name()WengineParser._parse_wengine_name() 会把翻译结果直接当成标准 key 导出,这里一旦把 OCR 噪声误匹配成别的条目,后面不会再有机会纠正,最终 setKey / key 会被静默写错。

🧰 Tools
🪛 Ruff (0.15.7)

[warning] 128-128: Comment contains ambiguous (FULLWIDTH LEFT PARENTHESIS). Did you mean ( (LEFT PARENTHESIS)?

(RUF003)


[warning] 128-128: Comment contains ambiguous (FULLWIDTH COMMA). Did you mean , (COMMA)?

(RUF003)


[warning] 128-128: Comment contains ambiguous (FULLWIDTH RIGHT PARENTHESIS). Did you mean ) (RIGHT PARENTHESIS)?

(RUF003)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/translation/translation_service.py`
around lines 128 - 132, The fuzzy-match threshold used in
translation_service._fuzzy_match is too low (0.2) and can cause OCR noise to be
accepted as authoritative keys later consumed by DriveDiskParser._parse_set_name
and WengineParser._parse_wengine_name; change the behavior so fuzzy matches are
not blindly used as exportable keys: either raise the confidence cut-off to a
conservative value (e.g., >=0.8) or mark fuzzy results as tentative and do NOT
call extract_translation in the code-path that generates persistent keys (so
setKey/key creation only uses exact matches or explicitly confirmed
translations). Update translation_service where best_match/confidence are
checked and, if keeping fuzzy fallback, add a boolean flag or different API call
to extract_translation to indicate “do not use for key export” and ensure
DriveDiskParser._parse_set_name and WengineParser._parse_wengine_name only
accept translations that are exact matches or have the stronger
confidence/confirmation.

Comment on lines +87 to +90
# 音擎和驱动盘暂时保持空字典
log.info("音擎和驱动盘数据保持为空")
translation_dict['weapon'] = {}
translation_dict['equipment'] = {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

更新字典时不要清空武器和驱动盘翻译。

Line 87-90 把 weaponequipment 直接重置为空字典;一旦执行 update_all()TranslationService.translate_weapon()/translate_equipment() 就拿不到标准映射,WengineParserDriveDiskParser 只能退回原始 OCR 文本,导出的 key 会失去稳定性。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/application/inventory_scan/translation/translation_updater.py`
around lines 87 - 90, The code currently resets translation_dict['weapon'] and
['equipment'] to empty dicts which breaks stable mappings used by update_all(),
TranslationService.translate_weapon()/translate_equipment(), WengineParser and
DriveDiskParser; instead remove the unconditional resets and preserve or merge
existing mappings by using a conditional initialization or merge (e.g., only
setdefault('weapon', {}) / setdefault('equipment', {}) or update existing dicts
with new entries) so existing translation keys remain available to the parsers
and translators.

Comment on lines +280 to +285
for file_name in os.listdir(self.data_file_path):
if '_data_' in file_name and file_name.endswith('.json'):
# 提取代理人名称
agent_name = file_name.split('_data_')[0]
report_files[agent_name] = os.path.join(self.data_file_path, file_name)
except Exception as e:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

每个代理人应该固定选最新的报告文件。

同名代理人的多份 *_data_*.json 会在这里相互覆盖,而 os.listdir() 顺序不稳定,所以界面打开的可能是旧报告,不一定是最新一次扫描结果。

💡 建议修复
         try:
-            for file_name in os.listdir(self.data_file_path):
+            for file_name in sorted(os.listdir(self.data_file_path)):
                 if '_data_' in file_name and file_name.endswith('.json'):
                     # 提取代理人名称
                     agent_name = file_name.split('_data_')[0]
-                    report_files[agent_name] = os.path.join(self.data_file_path, file_name)
+                    current_path = os.path.join(self.data_file_path, file_name)
+                    previous_path = report_files.get(agent_name)
+                    if previous_path is None or file_name > os.path.basename(previous_path):
+                        report_files[agent_name] = current_path
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 285-285: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` around lines 280
- 285, The current loop overwrites report_files[agent_name] when multiple
'*_data_*.json' exist because os.listdir() order is unstable; change the loop
that iterates file_name under self.data_file_path so it selects the newest
report per agent instead of last-seen: for each matching file compute agent_name
(file_name.split('_data_')[0]) and candidate_path =
os.path.join(self.data_file_path, file_name), then compare
os.path.getmtime(candidate_path) (or parse a timestamp from the filename if
available) against any existing report_files[agent_name] and keep only the path
with the latest modification time; update report_files accordingly so each agent
maps to its newest file.

Comment thread src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py Outdated
Comment on lines +1061 to +1084
def on_load_weights():
self._load_weights_data()
self._update_agent_combo(agent_combo)
if agent_combo.count() > 0:
agent_combo.setCurrentIndex(0)
update_weight_table()
self.show_info_bar(gt('成功'), gt('权重数据加载完成'), icon=InfoBarIcon.SUCCESS)

def on_save_weights():
current_agent = agent_combo.currentText()
if current_agent and current_agent in self.agent_weights:
# 确保数据格式正确
for weight_type, data in self.agent_weights[current_agent].items():
if not isinstance(data, dict):
self.agent_weights[current_agent][weight_type] = {'weight': 0.5}
else:
if 'weight' not in data:
data['weight'] = 0.5

weight_file = os.path.join(self.data_file_path, 'agent_weights.json')
try:
os.makedirs(os.path.dirname(weight_file), exist_ok=True)
with open(weight_file, 'w', encoding='utf-8') as f:
json.dump(self.agent_weights, f, ensure_ascii=False, indent=2)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

权重“保存”和“加载”读写的是两套完全不同的数据源。

Line 1080 把编辑结果写到 .debug/inventory_data/agent_weights.json,但 _load_weights_data() 重新加载时只扫描 assets/character_weight/*.json。这会让用户保存后的改动在重新打开弹窗或点击“加载权重数据”后全部丢失。

Also applies to: 1106-1173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/zzz_od/gui/view/one_dragon/inventory_scan_interface.py` around lines 1061
- 1084, The save path used in on_save_weights writes edits to
data_file_path/agent_weights.json while _load_weights_data() only loads JSONs
from assets/character_weight, causing saved changes to be ignored; fix by making
the sources consistent: either change on_save_weights to write the edited agent
JSON back into the assets/character_weight location (maintaining filename/format
used by _load_weights_data()) or update _load_weights_data to also read and
merge data_file_path/agent_weights.json (and ensure merging logic preserves
expected dict structure). Update references in on_save_weights,
_load_weights_data, and any usages of agent_weights or update_weight_table to
load the same canonical source so saved edits persist across reloads.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged

实现仓库扫描功能,包括:
1. 添加代理人信息扫描界面和逻辑
2. 新增代理人相关屏幕配置
3. 实现OCR工作线程用于并行处理扫描任务
4. 添加仓库扫描应用工厂和主界面
5. 集成扫描功能到主导航界面

新增代理人信息界面配置,包括代理人名称、等级等区域的识别配置。实现自动遍历和扫描代理人信息的功能,并将结果保存为JSON文件。
实现预扫描功能用于更新代理人列表,并在界面中添加代理人选择下拉框
将代理人扫描逻辑从主应用拆分到预扫描模块,提高代码可维护性
添加Node.js微服务用于处理驱动盘评分功能,包括:
1. 创建Express服务端和Python客户端
2. 实现驱动盘评分API接口
3. 添加相关文档说明
4. 更新.gitignore忽略node_modules
5. 优化OCR工作线程和界面显示逻辑
refactor(配置): 更新代理人装备页面配置
chore: 移除不再使用的node_service文件
docs: 添加特定扫描常量定义
增加对scan_agent_option为空的检查,避免空指针异常
重构翻译服务模块,移除不再使用的图标下载器和翻译更新器,将核心翻译服务移至主目录。优化角色名称解析逻辑,使用角色code作为唯一标识。调整驱动盘主词条选项以匹配游戏术语。修复预扫描中重复代理人的检测问题。简化特殊扫描流程并增加界面验证。更新角色权重配置文件以保持数据一致性。
实现库存扫描应用的基本框架,包括预扫描和特定代理人扫描功能。添加必要的工具类和方法支持,如OCR工作线程和截图目录处理。同时优化特殊扫描应用的界面加载等待时间。
实现仓库扫描功能,包括:
1. 添加代理人信息扫描界面和逻辑
2. 新增代理人相关屏幕配置
3. 实现OCR工作线程用于并行处理扫描任务
4. 添加仓库扫描应用工厂和主界面
5. 集成扫描功能到主导航界面

新增代理人信息界面配置,包括代理人名称、等级等区域的识别配置。实现自动遍历和扫描代理人信息的功能,并将结果保存为JSON文件。
实现预扫描功能用于更新代理人列表,并在界面中添加代理人选择下拉框
将代理人扫描逻辑从主应用拆分到预扫描模块,提高代码可维护性
添加Node.js微服务用于处理驱动盘评分功能,包括:
1. 创建Express服务端和Python客户端
2. 实现驱动盘评分API接口
3. 添加相关文档说明
4. 更新.gitignore忽略node_modules
5. 优化OCR工作线程和界面显示逻辑
refactor(配置): 更新代理人装备页面配置
chore: 移除不再使用的node_service文件
docs: 添加特定扫描常量定义
增加对scan_agent_option为空的检查,避免空指针异常
重构翻译服务模块,移除不再使用的图标下载器和翻译更新器,将核心翻译服务移至主目录。优化角色名称解析逻辑,使用角色code作为唯一标识。调整驱动盘主词条选项以匹配游戏术语。修复预扫描中重复代理人的检测问题。简化特殊扫描流程并增加界面验证。更新角色权重配置文件以保持数据一致性。
实现仓库扫描功能,包括:
1. 添加代理人信息扫描界面和逻辑
2. 新增代理人相关屏幕配置
3. 实现OCR工作线程用于并行处理扫描任务
4. 添加仓库扫描应用工厂和主界面
5. 集成扫描功能到主导航界面

新增代理人信息界面配置,包括代理人名称、等级等区域的识别配置。实现自动遍历和扫描代理人信息的功能,并将结果保存为JSON文件。
添加Node.js微服务用于处理驱动盘评分功能,包括:
1. 创建Express服务端和Python客户端
2. 实现驱动盘评分API接口
3. 添加相关文档说明
4. 更新.gitignore忽略node_modules
5. 优化OCR工作线程和界面显示逻辑
refactor(配置): 更新代理人装备页面配置
chore: 移除不再使用的node_service文件
docs: 添加特定扫描常量定义
增加对scan_agent_option为空的检查,避免空指针异常
实现库存扫描应用的基本框架,包括预扫描和特定代理人扫描功能。添加必要的工具类和方法支持,如OCR工作线程和截图目录处理。同时优化特殊扫描应用的界面加载等待时间。
重构库存扫描界面布局,将音擎详情页分为左右两部分显示信息。改进驱动盘评分计算,增加相对得分功能并调整最大得分上限为100。优化代理人和音擎信息显示格式,添加音擎推荐功能。同时删除不再使用的工具脚本文件。
清理多个文件中的合并冲突标记(<<<<<<< HEAD, =======, >>>>>>>)
统一使用translation_service作为翻译服务导入路径
恢复agent_list.yml中正确的goto_list配置
移除ocr_worker.py中重复的线程检查逻辑
简化agent_name_parser.py中的翻译匹配逻辑
refactor(解析): 改进驱动盘等级和品级解析逻辑
fix(界面): 修复代理人检查报告界面显示问题
重构扫描结果界面更新逻辑,优化控件更新流程并添加全面的错误处理
移除旧扫描时间标签时增加清理逻辑,确保界面更新稳定性
为所有控件更新操作添加try-catch块,防止单点失败影响整体功能
refactor: 优化OCR工作线程和解析器结构

fix: 修复特定代理人扫描时的重复检测问题

docs: 更新翻译服务和常量文件注释

test: 添加预扫描和特定扫描的测试用例
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant