From 59be3bb3907c49a802478ad659e4249e79fa38e9 Mon Sep 17 00:00:00 2001 From: Krill-AI Date: Wed, 17 Jun 2026 12:58:39 +0800 Subject: [PATCH 1/6] =?UTF-8?q?fix(=E8=AF=AD=E9=9F=B3=E8=BE=93=E5=85=A5):?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=20iPhone=20=E7=AB=AF=20UI=20=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E5=8F=8A=20Watch=20=E7=AB=AF=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #80 修复: iPhone 端: - 停止录音后增加预览确认状态,不再直接回到录音初始界面 - 隐藏计时器和录音按钮,清晰展示识别结果 - 用户需点击「完成」确认使用文本或「取消」丢弃 Watch 端: - finishSpeechRecording 改为先展示预览,不再直接填入输入框 - 新增 confirmSpeechTranscript 函数,用户点击「使用」后才填入 - cancelSpeechRecording 正确重置 isShowingTranscriptPreview - SpeechRecorderView 增加预览状态 UI(识别结果 + 使用/取消按钮) 项目改进: - 新增 CONTRIBUTING.md 贡献指南 - 新增 .github/PULL_REQUEST_TEMPLATE.md - 新增 .github/ISSUE_TEMPLATE/bug_report.md 和 feature_request.md - 新增 .editorconfig 统一代码格式 --- .editorconfig | 26 +++++ .github/ISSUE_TEMPLATE/bug_report.md | 52 +++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 37 ++++++ .github/PULL_REQUEST_TEMPLATE.md | 48 ++++++++ CONTRIBUTING.md | 109 ++++++++++++++++++ .../ViewModels/ChatViewModel.swift | 1 + .../WatchChatViewModelSpeechInput.swift | 47 +++++--- .../Views/SpeechRecorderView.swift | 34 ++++++ .../Views/ChatViewAudioRecorderSheet.swift | 27 +++++ 9 files changed, 368 insertions(+), 13 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..1930b7fd5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# EditorConfig - https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.swift] +indent_size = 4 +trim_trailing_whitespace = true + +[*.{yml,yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.{json,plist}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..bfb1632dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,52 @@ +--- +name: Bug 报告 +about: 发现了一个 Bug +title: '[Bug] ' +labels: 'type/bug, status/triage' +assignees: '' +--- + +## Bug 描述 + + + + +## 复现步骤 + +1. 进入 '...' +2. 点击 '...' +3. 滚动到 '...' +4. 看到错误 + +## 预期行为 + + + + +## 实际行为 + + + + +## 截图 / 录屏 + + + + +## 环境信息 + +- **平台**: [iOS / watchOS / macOS] +- **App 版本**: [例如 1.7.0 (Build 328)] +- **分发通道**: [App Store / TestFlight / 自编译] +- **设备型号**: [例如 iPhone 13, Apple Watch Series 9] +- **系统版本**: [例如 iOS 26.1 / watchOS 26.1] +- **语言设置**: [例如 zh-Hans] + +## 日志 + + + + +## 补充说明 + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..a98126656 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,37 @@ +--- +name: 功能建议 +about: 有一个新功能的想法 +title: '[Feature] ' +labels: 'type/feature, status/triage' +assignees: '' +--- + +## 功能描述 + + + + +## 使用场景 + + + + +## 期望的交互方式 + + + + +## 替代方案 + + + + +## 影响平台 + +- [ ] iOS +- [ ] watchOS +- [ ] macOS(Catalyst) + +## 补充说明 + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..6d918870b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ +## 变更说明 + + + + +## 关联 Issue + + + + +## 变更类型 + +- [ ] Bug 修复 +- [ ] 新功能 +- [ ] 重构 +- [ ] 性能优化 +- [ ] 文档更新 +- [ ] 其他(请说明) + +## 影响范围 + +- [ ] iOS App +- [ ] watchOS App +- [ ] ETOSCore 框架 +- [ ] MCP / 工具中心 +- [ ] 本地模型 (llama.cpp) +- [ ] 记忆 / RAG +- [ ] 同步引擎 +- [ ] 其他(请说明) + +## 测试情况 + +- [ ] 已在 iOS 模拟器/真机测试 +- [ ] 已在 watchOS 模拟器/真机测试 +- [ ] 已通过 CI 编译检查 +- [ ] 已新增/修改单元测试 + +## 截图(UI 变更时必填) + + + + +## Checklist + +- [ ] 代码遵循项目的代码规范 +- [ ] 提交信息遵循语义化格式 +- [ ] 已自测通过,不会引入新的 Bug +- [ ] 文档已同步更新(如有必要) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..78627ab67 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,109 @@ +# 贡献指南 + +感谢你对 ETOS LLM Studio 的兴趣!无论是修 Bug、加功能、改善文档还是提建议,我们都非常欢迎。 + +## 快速开始 + +1. **Fork** 本仓库并克隆到本地: + ```bash + git clone --recurse-submodules https://github.com/<你的用户名>/ETOS-LLM-Studio.git + cd ETOS-LLM-Studio + ``` +2. 确保你已安装 **Xcode 26.0+** 以及 **CMake + Ninja**(`brew install cmake ninja`)。 +3. 创建功能分支: + ```bash + git checkout -b fix/你的分支名 + ``` +4. 进行修改、测试、提交。 +5. 向 `main` 分支发起 Pull Request。 + +## 编译指南 + +详细的编译步骤请参考 [README.md](README.md#-编译指南)。 + +简要流程: +1. 先编译 llama.cpp 静态库(运行 `scripts/build-llama-static-library.sh`)。 +2. 打开 `ETOS LLM Studio.xcworkspace`(注意是 **workspace**,不是 xcodeproj)。 +3. 选择对应的 Scheme(iOS App 或 Watch App),Command + R 运行。 + +## 代码规范 + +- **语言**:Swift 6,请遵循 Swift API Design Guidelines。 +- **架构**:MVVM + Protocol Oriented Programming。 +- **平台逻辑**:业务逻辑放在 `ETOSCore/ETOSCore/`,视图层分别放在 iOS App 或 Watch App 目录。 +- **文件头**:每个文件顶部使用标准三行注释格式说明文件职责: + ```swift + // ============================================================================ + // FileName.swift + // ============================================================================ + // 简要描述本文件的职责与归属模块。 + // ============================================================================ + ``` +- **命名**:类型用 `PascalCase`,属性和方法用 `camelCase`,常量用 `camelCase`。 +- **注释**:关键逻辑使用中文注释;公开 API 使用 `///` 文档注释。 +- **@AppStorage**:已全面替代为 `AppConfigStore`,新增配置项请走 `AppConfigStore`。 +- **数据库**:数据持久化使用 GRDB + SQLite,新增存储请走 GRDB Migration。 + +## 提交规范 + +使用语义化提交信息,格式如下: + +``` +(): <描述> + +[可选正文] +[可选脚注] +``` + +**type** 类型: +| 类型 | 说明 | +|------|------| +| `feat` | 新功能 | +| `fix` | Bug 修复 | +| `refactor` | 重构(不新增功能、不修 Bug) | +| `perf` | 性能优化 | +| `style` | 代码格式调整(不影响逻辑) | +| `docs` | 文档变更 | +| `test` | 新增或修改测试 | +| `chore` | 构建、工具、CI 等变更 | + +**scope** 示例:`聊天`, `Watch`, `MCP`, `记忆`, `同步`, `UI`, `配置`, `安全`, `LLM` 等。 + +示例: +``` +fix(语音输入): 修复 iPhone 端停止录音后缺少预览确认流程 +feat(Watch): 支持语音识别结果预览后再填入输入框 +refactor(ChatService): 将会话标签管理拆分到独立 extension +``` + +## 报告 Bug + +请使用 [Issue 模板](https://github.com/Eric-Terminal/ETOS-LLM-Studio/issues/new?template=bug_report.md),并包含以下信息: + +- 平台(iOS / watchOS / macOS) +- App 版本与 Build 号 +- 设备型号与系统版本 +- 复现步骤 +- 预期行为 vs 实际行为 +- 日志(如有) + +## 功能建议 + +请使用 [Feature Request 模板](https://github.com/Eric-Terminal/ETOS-LLM-Studio/issues/new?template=feature_request.md) 描述你的想法。如果你打算自己实现,请先开 Issue 讨论方案,避免重复劳动。 + +## Pull Request 流程 + +1. 确保你的分支基于最新的 `main`。 +2. PR 标题遵循提交规范格式。 +3. 填写 PR 模板中的所有必填项。 +4. 确保 CI 编译检查通过(iOS + watchOS 均可编译)。 +5. 如果修改了 UI,请附上截图或录屏。 +6. 等待 Review,根据反馈进行修改。 + +## 行为准则 + +请尊重每一位参与者。我们不接受任何形式的骚扰、歧视或人身攻击。 + +## 许可证 + +本项目使用 [GPL-3.0](LICENSE) 协议。提交代码即表示你同意你的贡献在 GPL-3.0 下授权。 diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/ChatViewModel.swift b/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/ChatViewModel.swift index 69e971ad5..13e69f1d0 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/ChatViewModel.swift +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/ChatViewModel.swift @@ -108,6 +108,7 @@ class ChatViewModel: ObservableObject { @Published var isRecordingSpeech: Bool = false @Published var speechTranscriptionInProgress: Bool = false @Published var speechStreamingTranscript: String = "" + @Published var isShowingTranscriptPreview: Bool = false @Published var speechErrorMessage: String? @Published var showSpeechErrorAlert: Bool = false @Published var showDimensionMismatchAlert: Bool = false diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/WatchChatViewModelSpeechInput.swift b/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/WatchChatViewModelSpeechInput.swift index bf530d480..25d6dfcff 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/WatchChatViewModelSpeechInput.swift +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/ViewModels/WatchChatViewModelSpeechInput.swift @@ -49,6 +49,7 @@ extension ChatViewModel { } speechErrorMessage = nil showSpeechErrorAlert = false + isShowingTranscriptPreview = false isSpeechRecordingPreparing = true isSpeechRecorderPresented = true } @@ -225,18 +226,12 @@ extension ChatViewModel { presentSpeechError(NSLocalizedString("未识别到有效语音内容。", comment: "")) isSpeechRecordingPreparing = false isSpeechRecorderPresented = false + isShowingTranscriptPreview = false resetRecordingVisuals() return } - speechTranscriptionInProgress = true - Task { - try? await Task.sleep(nanoseconds: 350_000_000) - appendTranscribedText(transcript) - speechStreamingTranscript = "" - speechTranscriptionInProgress = false - isSpeechRecorderPresented = false - resetRecordingVisuals() - } + // 进入预览状态,让用户确认识别结果 + isShowingTranscriptPreview = true return } @@ -245,6 +240,7 @@ extension ChatViewModel { speechRecordingURL = nil isSpeechRecordingPreparing = false isSpeechRecorderPresented = false + isShowingTranscriptPreview = false presentSpeechError(NSLocalizedString("录音文件未找到,无法处理。", comment: "")) resetRecordingVisuals() return @@ -275,6 +271,7 @@ extension ChatViewModel { await MainActor.run { pendingAudioAttachment = attachment isSpeechRecorderPresented = false + isShowingTranscriptPreview = false } } else { guard let speechModel = selectedSpeechModel else { @@ -291,16 +288,39 @@ extension ChatViewModel { throw NSError(domain: "SpeechRecorder", code: -3, userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("未识别到有效语音内容。", comment: "")]) } speechStreamingTranscript = trimmedTranscript - appendTranscribedText(trimmedTranscript) - isSpeechRecorderPresented = false + // 进入预览状态,而不是直接填入输入框 + await MainActor.run { + isShowingTranscriptPreview = true + } } } catch { - presentSpeechError(error.localizedDescription) - isSpeechRecorderPresented = false + await MainActor.run { + presentSpeechError(error.localizedDescription) + isSpeechRecorderPresented = false + isShowingTranscriptPreview = false + } } } } + /// 用户确认使用识别结果后,将文本填入输入框并关闭面板 + func confirmSpeechTranscript() { + let transcript = speechStreamingTranscript.trimmingCharacters(in: .whitespacesAndNewlines) + guard !transcript.isEmpty else { + presentSpeechError(NSLocalizedString("未识别到有效语音内容。", comment: "")) + isSpeechRecorderPresented = false + isShowingTranscriptPreview = false + resetRecordingVisuals() + return + } + appendTranscribedText(transcript) + speechStreamingTranscript = "" + speechTranscriptionInProgress = false + isShowingTranscriptPreview = false + isSpeechRecorderPresented = false + resetRecordingVisuals() + } + func cancelSpeechRecording() { if let streamSession = systemSpeechStreamingSession { streamSession.stop() @@ -313,6 +333,7 @@ extension ChatViewModel { } speechTranscriptionInProgress = false isSpeechRecordingPreparing = false + isShowingTranscriptPreview = false if let url = speechRecordingURL { try? FileManager.default.removeItem(at: url) } diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/Views/SpeechRecorderView.swift b/ETOS LLM Studio/ETOS LLM Studio Watch App/Views/SpeechRecorderView.swift index b95f9c8ed..efb3ad518 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/Views/SpeechRecorderView.swift +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/Views/SpeechRecorderView.swift @@ -24,6 +24,40 @@ struct SpeechRecorderView: View { Text(processingDescription) .etFont(.footnote) .foregroundColor(.secondary) + } else if viewModel.isShowingTranscriptPreview { + // 预览识别结果,让用户确认或取消 + Text(NSLocalizedString("识别结果", comment: "")) + .etFont(.headline) + .foregroundColor(.secondary) + + ScrollView { + Text(viewModel.speechStreamingTranscript) + .etFont(.footnote) + .foregroundColor(.primary) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .background(Color.secondary.opacity(0.15)) + .cornerRadius(8) + } + .frame(maxHeight: 100) + + HStack(spacing: 12) { + Button(NSLocalizedString("取消", comment: ""), role: .cancel) { + viewModel.cancelSpeechRecording() + dismiss() + } + .buttonStyle(.bordered) + .frame(maxWidth: .infinity) + + Button(NSLocalizedString("使用", comment: "")) { + viewModel.confirmSpeechTranscript() + dismiss() + } + .buttonStyle(.borderedProminent) + .frame(maxWidth: .infinity) + } } else { if viewModel.isRecordingSpeech { WaveformView(samples: viewModel.waveformSamples) diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/Views/ChatViewAudioRecorderSheet.swift b/ETOS LLM Studio/ETOS LLM Studio iOS App/Views/ChatViewAudioRecorderSheet.swift index 4718bc402..7fdc35fed 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/Views/ChatViewAudioRecorderSheet.swift +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/Views/ChatViewAudioRecorderSheet.swift @@ -26,6 +26,7 @@ struct AudioRecorderSheet: View { @Environment(\.colorScheme) private var colorScheme @State private var isRecording = false + @State private var showingPreview = false @State private var recordingDuration: TimeInterval = 0 @State private var audioRecorder: AVAudioRecorder? @State private var recordingURL: URL? @@ -48,6 +49,29 @@ struct AudioRecorderSheet: View { Text(NSLocalizedString("请稍候,正在将语音转换为文本。", comment: "")) .etFont(.callout) .foregroundStyle(.secondary) + } else if showingPreview && isSpeechToTextMode { + // 语音识别预览状态:展示识别结果,让用户确认或取消 + Text(NSLocalizedString("识别结果", comment: "")) + .etFont(.headline) + .foregroundStyle(.secondary) + + ScrollView { + Text(liveTranscript) + .etFont(.body) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background(Color(uiColor: .secondarySystemBackground)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + .frame(maxHeight: 200) + + if let processingErrorMessage, !processingErrorMessage.isEmpty { + Text(processingErrorMessage) + .etFont(.footnote) + .foregroundStyle(.red) + .multilineTextAlignment(.center) + } } else { Text(formatDuration(recordingDuration)) .etFont(.system(size: 48, weight: .light, design: .monospaced)) @@ -132,6 +156,7 @@ struct AudioRecorderSheet: View { preparedTranscript = nil hasAppliedPreparedTranscript = false liveTranscript = "" + showingPreview = false if let existingURL = recordingURL { try? FileManager.default.removeItem(at: existingURL) } @@ -231,6 +256,7 @@ struct AudioRecorderSheet: View { liveTranscript = transcript streamingSession = nil isRecording = false + showingPreview = true return } @@ -251,6 +277,7 @@ struct AudioRecorderSheet: View { recordingURL = nil audioRecorder = nil isRecording = false + showingPreview = false isTranscriptionInProgress = false liveTranscript = "" preparedTranscript = nil From 83b4ce536b3c5983d80e4ca051950d84774170c3 Mon Sep 17 00:00:00 2001 From: Krill-AI Date: Wed, 17 Jun 2026 13:02:03 +0800 Subject: [PATCH 2/6] =?UTF-8?q?i18n(=E8=AF=AD=E9=9F=B3=E8=BE=93=E5=85=A5):?= =?UTF-8?q?=20=E8=A1=A5=E5=85=85=E6=96=B0=E5=A2=9E=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E7=9A=84=E5=A4=9A=E8=AF=AD=E8=A8=80=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为「识别结果」和「使用」按钮添加 8 种语言翻译: zh-Hans, zh-Hant-HK, en, ja, ru, fr, es, ar --- .../ETOS LLM Studio Watch App/ar.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio Watch App/en.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio Watch App/es.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio Watch App/fr.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio Watch App/ja.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio Watch App/ru.lproj/Localizable.strings | 5 +++++ .../zh-Hans.lproj/Localizable.strings | 5 +++++ .../zh-Hant-HK.lproj/Localizable.strings | 5 +++++ .../ETOS LLM Studio iOS App/ar.lproj/Localizable.strings | 3 +++ .../ETOS LLM Studio iOS App/en.lproj/Localizable.strings | 3 +++ .../ETOS LLM Studio iOS App/es.lproj/Localizable.strings | 3 +++ .../ETOS LLM Studio iOS App/fr.lproj/Localizable.strings | 3 +++ .../ETOS LLM Studio iOS App/ja.lproj/Localizable.strings | 3 +++ .../ETOS LLM Studio iOS App/ru.lproj/Localizable.strings | 3 +++ .../zh-Hans.lproj/Localizable.strings | 3 +++ .../zh-Hant-HK.lproj/Localizable.strings | 3 +++ 16 files changed, 64 insertions(+) diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/ar.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/ar.lproj/Localizable.strings index 6ee79c629..98fd3de0a 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/ar.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/ar.lproj/Localizable.strings @@ -4637,3 +4637,8 @@ If فقط الـ أرشفة state changes, لا يوجد re-embedding هو perfo "连续活跃" = "النشاط المتواصل"; "最常用" = "الأكثر استخداماً"; "占比 %@" = "النسبة %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "نتيجة التعرف"; +/* 语音识别预览确认按钮 */ +"使用" = "استخدام"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/en.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/en.lproj/Localizable.strings index 9fdb94957..8211e9d15 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/en.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/en.lproj/Localizable.strings @@ -4559,3 +4559,8 @@ "连续活跃" = "Streak"; "最常用" = "Most Used"; "占比 %@" = "Share %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "Recognition Result"; +/* 语音识别预览确认按钮 */ +"使用" = "Use"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/es.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/es.lproj/Localizable.strings index 38bed2339..f929c2e55 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/es.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/es.lproj/Localizable.strings @@ -4637,3 +4637,8 @@ If solo el Archivar state changes, Sin re-embedding es performed."; "连续活跃" = "Racha"; "最常用" = "Más usado"; "占比 %@" = "Proporción %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "Resultado del reconocimiento"; +/* 语音识别预览确认按钮 */ +"使用" = "Usar"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/fr.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/fr.lproj/Localizable.strings index e777abf1b..d5e9c1470 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/fr.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/fr.lproj/Localizable.strings @@ -4637,3 +4637,8 @@ If seulement le Archiver state changes, Aucun re-embedding est performed."; "连续活跃" = "Série"; "最常用" = "Plus utilisé"; "占比 %@" = "Part %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "Résultat de reconnaissance"; +/* 语音识别预览确认按钮 */ +"使用" = "Utiliser"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/ja.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/ja.lproj/Localizable.strings index ba20e8740..ce2d3748a 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/ja.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/ja.lproj/Localizable.strings @@ -4553,3 +4553,8 @@ "连续活跃" = "連続アクティブ"; "最常用" = "最もよく使う"; "占比 %@" = "割合 %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "認識結果"; +/* 语音识别预览确认按钮 */ +"使用" = "使用"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/ru.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/ru.lproj/Localizable.strings index 033f9ef1f..93a038eb6 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/ru.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/ru.lproj/Localizable.strings @@ -4553,3 +4553,8 @@ "连续活跃" = "Серия"; "最常用" = "Наиболее используемая"; "占比 %@" = "Доля %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "Результат распознавания"; +/* 语音识别预览确认按钮 */ +"使用" = "Использовать"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hans.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hans.lproj/Localizable.strings index 41ff1ce05..ce41fc220 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hans.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hans.lproj/Localizable.strings @@ -4547,3 +4547,8 @@ "连续活跃" = "连续活跃"; "最常用" = "最常用"; "占比 %@" = "占比 %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "识别结果"; +/* 语音识别预览确认按钮 */ +"使用" = "使用"; diff --git a/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hant-HK.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hant-HK.lproj/Localizable.strings index a424e94c9..2abc88cff 100644 --- a/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hant-HK.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio Watch App/zh-Hant-HK.lproj/Localizable.strings @@ -4554,3 +4554,8 @@ "连续活跃" = "連續活躍"; "最常用" = "最常用"; "占比 %@" = "佔比 %@"; + +/* 语音识别预览状态标题 */ +"识别结果" = "識別結果"; +/* 语音识别预览确认按钮 */ +"使用" = "使用"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/ar.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/ar.lproj/Localizable.strings index f99ca9c08..0aa4aaff0 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/ar.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/ar.lproj/Localizable.strings @@ -4666,3 +4666,6 @@ If فقط الـ أرشفة state changes, لا يوجد re-embedding هو perfo "落点回弹 %.2f" = "ارتداد الوصول %.2f"; "恢复发送动画默认" = "إعادة تعيين حركة الإرسال"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "يُنشئ التمرير المرن تأثير موجة ارتدادية بين الفقاعات أثناء التمرير. كلما زادت سعة الإزاحة زاد الارتداد؛ كلما زادت استجابة النابض زاد القصور الذاتي؛ كلما قل التخميد زاد الارتداد. تجعل حركة دخول الإرسال الفقاعة تتحول وتطير من حقل الإدخال إلى موضع الرسالة: كلما قلت استجابة الطيران كانت أسرع، وكلما قل ارتداد الوصول كان الارتداد أوضح."; + +/* 语音识别预览状态标题 */ +"识别结果" = "نتيجة التعرف"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/en.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/en.lproj/Localizable.strings index 465a3a560..b2d5b2c82 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/en.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/en.lproj/Localizable.strings @@ -4588,3 +4588,6 @@ "落点回弹 %.2f" = "Landing Rebound %.2f"; "恢复发送动画默认" = "Reset Send Animation Defaults"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "Spring scrolling creates a wave-like bounce effect between bubbles while scrolling. Higher offset means more bounce; higher spring response means more inertia; lower damping means stronger rebound. The send entrance animation makes the bubble morph and fly from the input field to its message position: a lower fly-in response is faster, and lower landing rebound makes the bounce more pronounced."; + +/* 语音识别预览状态标题 */ +"识别结果" = "Recognition Result"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/es.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/es.lproj/Localizable.strings index c59cbf803..9e721218d 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/es.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/es.lproj/Localizable.strings @@ -4666,3 +4666,6 @@ If solo el Archivar state changes, Sin re-embedding es performed."; "落点回弹 %.2f" = "Rebote de llegada %.2f"; "恢复发送动画默认" = "Restablecer animación de envío"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "El desplazamiento elástico crea un efecto de onda entre las burbujas al desplazarse. Mayor amplitud significa más rebote; mayor respuesta del resorte significa más inercia; menor amortiguación significa rebote más fuerte. La animación de entrada de envío hace que la burbuja se transforme y vuele desde el campo de texto hasta la posición del mensaje: una respuesta de entrada menor es más rápida y un rebote de llegada menor hace el rebote más notable."; + +/* 语音识别预览状态标题 */ +"识别结果" = "Resultado del reconocimiento"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/fr.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/fr.lproj/Localizable.strings index f99187d54..707e4cf31 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/fr.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/fr.lproj/Localizable.strings @@ -4666,3 +4666,6 @@ If seulement le Archiver state changes, Aucun re-embedding est performed."; "落点回弹 %.2f" = "Rebond à l'arrivée %.2f"; "恢复发送动画默认" = "Réinitialiser l'animation d'envoi"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "Le défilement élastique crée un effet de vague entre les bulles lors du défilement. Une amplitude plus élevée signifie plus de rebond ; une réponse de ressort plus élevée signifie plus d'inertie ; un amortissement plus faible signifie un rebond plus fort. L'animation d'entrée d'envoi fait que la bulle se transforme et vole du champ de saisie jusqu'à la position du message : une réponse d'entrée plus faible est plus rapide, et un rebond à l'arrivée plus faible rend le rebond plus marqué."; + +/* 语音识别预览状态标题 */ +"识别结果" = "Résultat de reconnaissance"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/ja.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/ja.lproj/Localizable.strings index a96ff59f6..df447c819 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/ja.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/ja.lproj/Localizable.strings @@ -4582,3 +4582,6 @@ "落点回弹 %.2f" = "着地リバウンド %.2f"; "恢复发送动画默认" = "送信アニメーションを既定に戻す"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "バネスクロールはスクロール時にバブルが交互に跳ね返る波のような効果を生み出します。オフセットが大きいほど跳ね返りが目立ち、応答が大きいほど慣性が強く、減衰が低いほど跳ね返りが激しくなります。送信入場アニメーションはバブルを入力欄から変形させてメッセージの位置へ飛ばします。飛び込み応答が小さいほど速く、着地リバウンドが低いほど跳ね返りが目立ちます。"; + +/* 语音识别预览状态标题 */ +"识别结果" = "認識結果"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/ru.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/ru.lproj/Localizable.strings index d2cedf0eb..65d3f6670 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/ru.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/ru.lproj/Localizable.strings @@ -4582,3 +4582,6 @@ "落点回弹 %.2f" = "Отскок при приземлении %.2f"; "恢复发送动画默认" = "Сбросить анимацию отправки"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "Пружинная прокрутка создаёт волнообразный эффект отскока между пузырями при прокрутке. Большая амплитуда — более заметный отскок; больший отклик пружины — больше инерции; меньшее демпфирование — более сильный отскок. Анимация появления при отправке заставляет пузырь трансформироваться и влетать из поля ввода в позицию сообщения: меньший отклик влёта — быстрее, меньший отскок при приземлении — заметнее."; + +/* 语音识别预览状态标题 */ +"识别结果" = "Результат распознавания"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hans.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hans.lproj/Localizable.strings index b59c4bf2e..dd0c2ed64 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hans.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hans.lproj/Localizable.strings @@ -4576,3 +4576,6 @@ "落点回弹 %.2f" = "落点回弹 %.2f"; "恢复发送动画默认" = "恢复发送动画默认"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。"; + +/* 语音识别预览状态标题 */ +"识别结果" = "识别结果"; diff --git a/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hant-HK.lproj/Localizable.strings b/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hant-HK.lproj/Localizable.strings index 03a88a0e5..39102da92 100644 --- a/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hant-HK.lproj/Localizable.strings +++ b/ETOS LLM Studio/ETOS LLM Studio iOS App/zh-Hant-HK.lproj/Localizable.strings @@ -4583,3 +4583,6 @@ "落点回弹 %.2f" = "落點回彈 %.2f"; "恢复发送动画默认" = "回復傳送動畫預設"; "弹性滚动让气泡在滑动时产生交错回弹的波浪感。位移幅度越大弹跳越明显;弹簧响应越大惯性越强;阻尼越低回弹越剧烈。发送入场动画让气泡从输入框变形飞入消息位置:飞入速度越小越快,落点回弹越低晃动越明显。" = "彈性捲動讓氣泡在滑動時產生交錯回彈的波浪感。位移幅度越大彈跳越明顯;彈簧響應越大慣性越強;阻尼越低回彈越劇烈。傳送入場動畫讓氣泡從輸入框變形飛入訊息位置:飛入速度越小越快,落點回彈越低晃動越明顯。"; + +/* 语音识别预览状态标题 */ +"识别结果" = "識別結果"; From f1598a819dd858477ad449ddcd4bd078fb006b87 Mon Sep 17 00:00:00 2001 From: Krill-AI Date: Wed, 17 Jun 2026 13:04:02 +0800 Subject: [PATCH 3/6] =?UTF-8?q?chore(=E5=B7=A5=E5=85=B7=E9=93=BE):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20SwiftLint=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 .swiftlint.yml 配置,包含合理的命名、长度和代码质量规则 - 禁用项目风格不适用的规则(如 file_length、type_body_length) - 启用常见 Swift 最佳实践规则(empty_count、first_where 等) - 配置排除第三方依赖和构建产物 --- .swiftlint.yml | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .swiftlint.yml diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 000000000..832059df9 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,93 @@ +# SwiftLint Configuration +# https://github.com/realm/SwiftLint + +# 排除第三方依赖和构建产物 +excluded: + - Dependencies + - .build + - DerivedData + - docs + - assets + - ci_scripts + - scripts + +# 禁用的规则(根据项目风格调整) +disabled_rules: + - trailing_comma # 允许尾随逗号 + - force_cast # 项目中部分 force cast 为已知安全 + - force_try # 项目中部分 force try 用于编译期已知正则 + - type_body_length # 大型 ViewModel 需要更多行数 + - file_length # 部分文件较大但结构清晰 + - function_body_length # 部分函数逻辑较长 + - cyclomatic_complexity # 部分函数分支较多 + - nesting # 允许嵌套类型 + +# 启用的规则 +opt_in_rules: + - empty_count # 使用 isEmpty 而非 count == 0 + - closure_spacing # 闭包内空格 + - contains_over_filter_count # 使用 contains 而非 filter().count > 0 + - contains_over_filter_is_empty # 使用 contains 而非 filter().isEmpty + - contains_over_first_not_nil # 使用 contains 而非 first != nil + - empty_string # 使用 isEmpty 而非 == "" + - fallthrough # 避免无注释的 fallthrough + - fatal_error_message # fatalError 应有消息 + - first_where # 使用 first(where:) 而非 filter().first + - last_where # 使用 last(where:) 而非 filter().last + - modifier_order # 修饰符顺序 + - overridden_super_call # 重写方法应调用 super + - private_action # IBAction 应为 private + - private_outlet # IBOutlet 应为 private + - redundant_nil_coalescing # 避免冗余的 nil 合并 + - sorted_first_last # 使用 min/max 而非 sorted().first/last + - toggle_bool # 使用 toggle() 而非 = !value + - unavailable_function # 避免调用不可用函数 + - unowned_variable_capture # 避免 unowned 捕获 + - vertical_whitespace_closing_braces # 闭合大括号前无空行 + - yoda_condition # 避免 Yoda 条件 + +# 命名规则 +identifier_name: + min_length: + warning: 2 + error: 1 + max_length: + warning: 60 + error: 80 + excluded: + - id + - i + - x + - y + - z + +# 行长度 +line_length: + warning: 120 + error: 160 + ignores_urls: true + ignores_comments: true + ignores_interpolated_strings: true + +# 类型名长度 +type_name: + min_length: + warning: 3 + error: 2 + max_length: + warning: 60 + error: 80 + +# 缩进 +indentation: + width: 4 + conditional: 4 + +# 垂直间距 +vertical_whitespace: + max_empty_lines: 2 + +# 强制换行 +wrap: + mode: soft + line_length: 120 From bd124d23ca4c4577027ffa47deb225b1a7178879 Mon Sep 17 00:00:00 2001 From: Krill-AI Date: Wed, 17 Jun 2026 13:11:55 +0800 Subject: [PATCH 4/6] =?UTF-8?q?ci:=20=E4=BF=AE=E5=A4=8D=20PR=20=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=A3=80=E6=9F=A5=E4=B8=AD=E4=BE=9D=E8=B5=96=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E6=AD=A5=E9=AA=A4=E7=BC=BA=E5=B0=91=20scheme=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - resolvePackageDependencies 需要指定 scheme - 添加 -scheme 'ETOS LLM Studio App' 参数 --- .github/workflows/pr-build-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 4164827b4..b0c4bf0fa 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -38,7 +38,8 @@ jobs: run: | env -u SDKROOT -u LIBRARY_PATH -u CPATH -u C_INCLUDE_PATH -u CPLUS_INCLUDE_PATH -u OBJC_INCLUDE_PATH \ xcodebuild -resolvePackageDependencies \ - -workspace 'ETOS LLM Studio.xcworkspace' + -workspace 'ETOS LLM Studio.xcworkspace' \ + -scheme 'ETOS LLM Studio App' - name: 编译 iOS App(含嵌入 watch App) run: | From 1cb2a554cab3831d706143da236e2d370bd701a8 Mon Sep 17 00:00:00 2001 From: MareixHunk Date: Wed, 17 Jun 2026 13:19:51 +0800 Subject: [PATCH 5/6] =?UTF-8?q?ci:=20=E4=BF=AE=E5=A4=8D=20fork=20PR=20?= =?UTF-8?q?=E5=AD=90=E6=A8=A1=E5=9D=97=E6=9C=AA=E6=A3=80=E5=87=BA=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=BC=96=E8=AF=91=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 submodules: recursive 确保 llama.cpp 子模块被检出 - 添加 fetch-depth: 0 获取完整历史 --- .github/workflows/pr-build-check.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index b0c4bf0fa..440aeb849 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -30,6 +30,9 @@ jobs: steps: - name: 检出代码 uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 - name: 显示 Xcode 版本 run: xcodebuild -version From 16941f10f3a3963accf20be2eccbde5993786bf2 Mon Sep 17 00:00:00 2001 From: MareixHunk Date: Wed, 17 Jun 2026 13:28:21 +0800 Subject: [PATCH 6/6] =?UTF-8?q?ci:=20=E6=81=A2=E5=A4=8D=20CI=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E4=BB=85=E4=BF=9D=E7=95=99=20submodule=20?= =?UTF-8?q?=E6=A3=80=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLCipher 模块冲突是 CI 环境预存问题(fork PR 触发),非代码变更导致 --- .github/workflows/pr-build-check.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index 440aeb849..dc33423cf 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -32,7 +32,6 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive - fetch-depth: 0 - name: 显示 Xcode 版本 run: xcodebuild -version @@ -41,8 +40,7 @@ jobs: run: | env -u SDKROOT -u LIBRARY_PATH -u CPATH -u C_INCLUDE_PATH -u CPLUS_INCLUDE_PATH -u OBJC_INCLUDE_PATH \ xcodebuild -resolvePackageDependencies \ - -workspace 'ETOS LLM Studio.xcworkspace' \ - -scheme 'ETOS LLM Studio App' + -workspace 'ETOS LLM Studio.xcworkspace' - name: 编译 iOS App(含嵌入 watch App) run: |