Skip to content

feat: 支持作业集,批量作战(支持自动编队和信赖干员) - #89

Open
Lixuhang987 wants to merge 1 commit into
MaaAssistantArknights:masterfrom
Lixuhang987:feat/copilot-list
Open

feat: 支持作业集,批量作战(支持自动编队和信赖干员)#89
Lixuhang987 wants to merge 1 commit into
MaaAssistantArknights:masterfrom
Lixuhang987:feat/copilot-list

Conversation

@Lixuhang987

@Lixuhang987 Lixuhang987 commented May 24, 2026

Copy link
Copy Markdown

支持作业集功能,一键按顺序执行作业集的所有作业(支持突袭)(支持自动编队和信赖干员)

本地构建测试正常
image
image

Summary by Sourcery

添加对下载和运行带有共享配置选项的 Copilot 任务集(job sets)的支持,包括自动编队和信赖干员选项。

新功能:

  • 引入 Copilot 任务集支持,允许将多个 Copilot 任务作为一个批次按顺序执行。
  • 增加用于管理 Copilot 任务集的 UI 和状态,包括列出条目、选择项目以及切换是否使用任务集。
  • 支持从远程 API 下载并导入 Copilot 任务集,将其展开为单独的阶段条目,包括突袭(raid)变体。
  • 暴露全局 Copilot 选项(自动编队和信赖干员),并将其同时应用于单个和列表 Copilot 配置。

改进:

  • 优化 Copilot 删除和选择行为,更好地处理项目移除后的后续选择逻辑。
  • 改进 Copilot 及 Copilot 任务集下载与解析过程中的错误日志记录和错误处理。
Original summary in English

Summary by Sourcery

Add support for downloading and running copilot job sets with shared configuration options, including automatic formation and trust operators.

New Features:

  • Introduce copilot job set support, allowing sequential execution of multiple copilot tasks as a batch.
  • Add UI and state to manage copilot job sets, including listing entries, selecting items, and toggling use of job sets.
  • Support downloading and importing copilot job sets from remote APIs, expanding them into individual stage entries, including raid variants.
  • Expose global copilot options (automatic formation and trust operators) and apply them to both single and list copilot configurations.

Enhancements:

  • Refine copilot deletion and selection behavior to better handle removal and subsequent selection of items.
  • Improve error logging and handling for copilot and copilot set downloads and parsing.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了两个问题,并给出了一些整体性的反馈:

  • 新增的 difficulty 处理逻辑目前不一致:copilotSetTaskEntries 把它当作位掩码来用(difficulty & 1/2),但 MAACopilot.isRaid 只检查 difficulty == 2;建议统一这两处的处理方式(例如使用 difficulty & 2 != 0),以避免对突袭关卡的细微误判。
  • StageNavigationNameResolver.stageCodeByIdentifier 在静态属性初始化器中同步加载并解码一个相对较大的 JSON 文件,这会在首次使用时阻塞主线程;建议将这部分 I/O 移出主线程,或者在后台队列上以懒加载的方式初始化并缓存结果。
给予 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- The new `difficulty` handling is inconsistent: `copilotSetTaskEntries` treats it as a bitmask (`difficulty & 1/2`), but `MAACopilot.isRaid` only checks `difficulty == 2`; consider aligning these (e.g., `difficulty & 2 != 0`) to avoid subtle misclassification of raid stages.
- `StageNavigationNameResolver.stageCodeByIdentifier` synchronously loads and decodes a relatively large JSON file in a static property initializer, which can block the main thread on first use; consider moving this I/O off the main thread or initializing it lazily on a background queue and caching the result.

## Individual Comments

### Comment 1
<location path="MeoAsstMac/Navigation/CopilotDetail.swift" line_range="45" />
<code_context>
+
     @ToolbarContentBuilder private func detailToolbar() -> some ToolbarContent {
         ToolbarItemGroup {
             Button {
</code_context>
<issue_to_address>
**issue (bug_risk):** Clipboard button no longer triggers a download, which may be an unintended UX regression.

This button used to parse the ID from the clipboard and immediately set `viewModel.downloadCopilot` / dismiss the sheet. Now it only updates `prtsCode` on successful parse and never triggers a download. If you want to preserve the previous "read & add" behavior, reintroduce the `downloadCopilot` assignment and `showAdd = false`, or explicitly split these into two buttons with separate actions.
</issue_to_address>

### Comment 2
<location path="MeoAsstMac/Navigation/CopilotContent.swift" line_range="194-203" />
<code_context>
+    private func downloadCopilotSet(id: String?) {
</code_context>
<issue_to_address>
**suggestion:** Copilot-set download loop swallows individual entry errors, making debugging partial failures harder.

Within the `for (copilotId, fileName) in zip(...)` loop, the failure path only logs the generic "下载作业集条目失败" message and drops the actual `error`. Please include `error.localizedDescription` (or similar) in the log for each failed item, or aggregate these per-item errors into a summary. This will make partial download failures much easier to diagnose.

Suggested implementation:

```
                    if let message = response.message {
                        viewModel.logError("\(message)")
                    } else {

```

```
                        viewModel.logError("下载作业集失败: \(id)")
                    }
                    self.downloading = false
                    return
                }

                guard let data = response.data else {
                    viewModel.logError("下载作业集失败: \(id), 响应数据为空")
                    self.downloading = false
                    return
                }

                let items = data.items
                for (copilotId, fileName) in zip(items.map(\.id), items.map(\.fileName)) {
                    do {
                        let file = try await self.downloadCopilotItem(copilotId: copilotId, fileName: fileName)
                        self.selection = file
                    } catch {
                        // Include the localized error description for each failed item to aid debugging
                        viewModel.logError("下载作业集条目失败: id=\(copilotId), fileName=\(fileName), error=\(error.localizedDescription)")
                    }
                }

                self.downloading = false

```

If there are other similar catch blocks inside loops that handle per-item downloads for Copilot sets, you may want to apply the same pattern there: log both identifying information about the item (IDs, filenames) and `error.localizedDescription` so partial failures are easier to diagnose.
</issue_to_address>

Sourcery 对开源项目永久免费 —— 如果你觉得这次 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈持续改进 Review 质量。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The new difficulty handling is inconsistent: copilotSetTaskEntries treats it as a bitmask (difficulty & 1/2), but MAACopilot.isRaid only checks difficulty == 2; consider aligning these (e.g., difficulty & 2 != 0) to avoid subtle misclassification of raid stages.
  • StageNavigationNameResolver.stageCodeByIdentifier synchronously loads and decodes a relatively large JSON file in a static property initializer, which can block the main thread on first use; consider moving this I/O off the main thread or initializing it lazily on a background queue and caching the result.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `difficulty` handling is inconsistent: `copilotSetTaskEntries` treats it as a bitmask (`difficulty & 1/2`), but `MAACopilot.isRaid` only checks `difficulty == 2`; consider aligning these (e.g., `difficulty & 2 != 0`) to avoid subtle misclassification of raid stages.
- `StageNavigationNameResolver.stageCodeByIdentifier` synchronously loads and decodes a relatively large JSON file in a static property initializer, which can block the main thread on first use; consider moving this I/O off the main thread or initializing it lazily on a background queue and caching the result.

## Individual Comments

### Comment 1
<location path="MeoAsstMac/Navigation/CopilotDetail.swift" line_range="45" />
<code_context>
+
     @ToolbarContentBuilder private func detailToolbar() -> some ToolbarContent {
         ToolbarItemGroup {
             Button {
</code_context>
<issue_to_address>
**issue (bug_risk):** Clipboard button no longer triggers a download, which may be an unintended UX regression.

This button used to parse the ID from the clipboard and immediately set `viewModel.downloadCopilot` / dismiss the sheet. Now it only updates `prtsCode` on successful parse and never triggers a download. If you want to preserve the previous "read & add" behavior, reintroduce the `downloadCopilot` assignment and `showAdd = false`, or explicitly split these into two buttons with separate actions.
</issue_to_address>

### Comment 2
<location path="MeoAsstMac/Navigation/CopilotContent.swift" line_range="194-203" />
<code_context>
+    private func downloadCopilotSet(id: String?) {
</code_context>
<issue_to_address>
**suggestion:** Copilot-set download loop swallows individual entry errors, making debugging partial failures harder.

Within the `for (copilotId, fileName) in zip(...)` loop, the failure path only logs the generic "下载作业集条目失败" message and drops the actual `error`. Please include `error.localizedDescription` (or similar) in the log for each failed item, or aggregate these per-item errors into a summary. This will make partial download failures much easier to diagnose.

Suggested implementation:

```
                    if let message = response.message {
                        viewModel.logError("\(message)")
                    } else {

```

```
                        viewModel.logError("下载作业集失败: \(id)")
                    }
                    self.downloading = false
                    return
                }

                guard let data = response.data else {
                    viewModel.logError("下载作业集失败: \(id), 响应数据为空")
                    self.downloading = false
                    return
                }

                let items = data.items
                for (copilotId, fileName) in zip(items.map(\.id), items.map(\.fileName)) {
                    do {
                        let file = try await self.downloadCopilotItem(copilotId: copilotId, fileName: fileName)
                        self.selection = file
                    } catch {
                        // Include the localized error description for each failed item to aid debugging
                        viewModel.logError("下载作业集条目失败: id=\(copilotId), fileName=\(fileName), error=\(error.localizedDescription)")
                    }
                }

                self.downloading = false

```

If there are other similar catch blocks inside loops that handle per-item downloads for Copilot sets, you may want to apply the same pattern there: log both identifying information about the item (IDs, filenames) and `error.localizedDescription` so partial failures are easier to diagnose.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.


@ToolbarContentBuilder private func detailToolbar() -> some ToolbarContent {
ToolbarItemGroup {
Button {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): 剪贴板按钮现在不再触发下载,这可能是一个非预期的交互回退。

这个按钮之前的行为是:从剪贴板解析 ID,并立即设置 viewModel.downloadCopilot / 关闭弹窗。现在它只会在解析成功时更新 prtsCode,但不会触发下载。如果你希望保留之前的「读取并添加」行为,请重新加入对 downloadCopilot 的赋值以及 showAdd = false,或者显式地拆分成两个按钮,分别承担「读取」和「下载」两个独立动作。

Original comment in English

issue (bug_risk): Clipboard button no longer triggers a download, which may be an unintended UX regression.

This button used to parse the ID from the clipboard and immediately set viewModel.downloadCopilot / dismiss the sheet. Now it only updates prtsCode on successful parse and never triggers a download. If you want to preserve the previous "read & add" behavior, reintroduce the downloadCopilot assignment and showAdd = false, or explicitly split these into two buttons with separate actions.

Comment on lines +194 to +203
private func downloadCopilotSet(id: String?) {
guard let id else { return }

Task {
self.downloading = true
do {
let response = try await requestCopilotSet(id: id)
guard response.statusCode == 200 else {
if let message = response.message {
viewModel.logError("\(message)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: 作业集下载循环会吞掉单个条目的错误信息,导致排查部分失败变得更困难。

for (copilotId, fileName) in zip(...) 循环中,失败分支只记录了通用的「下载作业集条目失败」日志,而忽略了实际的 error。建议在每个失败的条目日志中包含 error.localizedDescription(或类似信息),或者汇总为一个包含各条目错误的摘要。这会让排查部分下载失败问题变得容易得多。

建议的实现方式:

                    if let message = response.message {
                        viewModel.logError("\(message)")
                    } else {

                        viewModel.logError("下载作业集失败: \(id)")
                    }
                    self.downloading = false
                    return
                }

                guard let data = response.data else {
                    viewModel.logError("下载作业集失败: \(id), 响应数据为空")
                    self.downloading = false
                    return
                }

                let items = data.items
                for (copilotId, fileName) in zip(items.map(\.id), items.map(\.fileName)) {
                    do {
                        let file = try await self.downloadCopilotItem(copilotId: copilotId, fileName: fileName)
                        self.selection = file
                    } catch {
                        // Include the localized error description for each failed item to aid debugging
                        viewModel.logError("下载作业集条目失败: id=\(copilotId), fileName=\(fileName), error=\(error.localizedDescription)")
                    }
                }

                self.downloading = false

如果在其他针对作业集逐条下载的循环里也有类似的 catch 逻辑,建议同样应用这一模式:同时记录条目的标识信息(ID、文件名等)以及 error.localizedDescription,以便更容易诊断部分失败的问题。

Original comment in English

suggestion: Copilot-set download loop swallows individual entry errors, making debugging partial failures harder.

Within the for (copilotId, fileName) in zip(...) loop, the failure path only logs the generic "下载作业集条目失败" message and drops the actual error. Please include error.localizedDescription (or similar) in the log for each failed item, or aggregate these per-item errors into a summary. This will make partial download failures much easier to diagnose.

Suggested implementation:

                    if let message = response.message {
                        viewModel.logError("\(message)")
                    } else {

                        viewModel.logError("下载作业集失败: \(id)")
                    }
                    self.downloading = false
                    return
                }

                guard let data = response.data else {
                    viewModel.logError("下载作业集失败: \(id), 响应数据为空")
                    self.downloading = false
                    return
                }

                let items = data.items
                for (copilotId, fileName) in zip(items.map(\.id), items.map(\.fileName)) {
                    do {
                        let file = try await self.downloadCopilotItem(copilotId: copilotId, fileName: fileName)
                        self.selection = file
                    } catch {
                        // Include the localized error description for each failed item to aid debugging
                        viewModel.logError("下载作业集条目失败: id=\(copilotId), fileName=\(fileName), error=\(error.localizedDescription)")
                    }
                }

                self.downloading = false

If there are other similar catch blocks inside loops that handle per-item downloads for Copilot sets, you may want to apply the same pattern there: log both identifying information about the item (IDs, filenames) and error.localizedDescription so partial failures are easier to diagnose.

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