fix: macOS Swift cleanup
This commit is contained in:
@@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
final class AppState {
|
final class AppState {
|
||||||
private let isPreview: Bool
|
private let isPreview: Bool
|
||||||
private var isInitializing = true
|
private var isInitializing = true
|
||||||
private nonisolated var configWatcher: ConfigFileWatcher?
|
private var configWatcher: ConfigFileWatcher?
|
||||||
private var suppressVoiceWakeGlobalSync = false
|
private var suppressVoiceWakeGlobalSync = false
|
||||||
private var voiceWakeGlobalSyncTask: Task<Void, Never>?
|
private var voiceWakeGlobalSyncTask: Task<Void, Never>?
|
||||||
|
|
||||||
@@ -321,6 +321,7 @@ final class AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
deinit {
|
deinit {
|
||||||
self.configWatcher?.stop()
|
self.configWatcher?.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
|
|||||||
|
|
||||||
private let manager = CLLocationManager()
|
private let manager = CLLocationManager()
|
||||||
private var locationContinuation: CheckedContinuation<CLLocation, Swift.Error>?
|
private var locationContinuation: CheckedContinuation<CLLocation, Swift.Error>?
|
||||||
private struct UncheckedSendable<T>: @unchecked Sendable {
|
|
||||||
let value: T
|
|
||||||
}
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
@@ -63,7 +60,7 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func withTimeout<T>(
|
private func withTimeout<T: Sendable>(
|
||||||
timeoutMs: Int,
|
timeoutMs: Int,
|
||||||
operation: @escaping () async throws -> T) async throws -> T
|
operation: @escaping () async throws -> T) async throws -> T
|
||||||
{
|
{
|
||||||
@@ -71,15 +68,38 @@ final class MacNodeLocationService: NSObject, CLLocationManagerDelegate {
|
|||||||
return try await operation()
|
return try await operation()
|
||||||
}
|
}
|
||||||
|
|
||||||
return try await withThrowingTaskGroup(of: UncheckedSendable<T>.self) { group in
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
group.addTask { try await UncheckedSendable(value: operation()) }
|
var didFinish = false
|
||||||
group.addTask {
|
|
||||||
try await Task.sleep(nanoseconds: UInt64(timeoutMs) * 1_000_000)
|
func finish(returning value: T) {
|
||||||
throw Error.timeout
|
guard !didFinish else { return }
|
||||||
|
didFinish = true
|
||||||
|
continuation.resume(returning: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func finish(throwing error: Swift.Error) {
|
||||||
|
guard !didFinish else { return }
|
||||||
|
didFinish = true
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeoutItem = DispatchWorkItem {
|
||||||
|
finish(throwing: Error.timeout)
|
||||||
|
}
|
||||||
|
DispatchQueue.main.asyncAfter(
|
||||||
|
deadline: .now() + .milliseconds(timeoutMs),
|
||||||
|
execute: timeoutItem)
|
||||||
|
|
||||||
|
Task { @MainActor in
|
||||||
|
do {
|
||||||
|
let value = try await operation()
|
||||||
|
timeoutItem.cancel()
|
||||||
|
finish(returning: value)
|
||||||
|
} catch {
|
||||||
|
timeoutItem.cancel()
|
||||||
|
finish(throwing: error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let result = try await group.next()!
|
|
||||||
group.cancelAll()
|
|
||||||
return result.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ extension OnboardingView {
|
|||||||
|
|
||||||
var navigationBar: some View {
|
var navigationBar: some View {
|
||||||
let wizardLockIndex = self.wizardPageOrderIndex
|
let wizardLockIndex = self.wizardPageOrderIndex
|
||||||
HStack(spacing: 20) {
|
return HStack(spacing: 20) {
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
Button(action: {}, label: {
|
Button(action: {}, label: {
|
||||||
Label("Back", systemImage: "chevron.left").labelStyle(.iconOnly)
|
Label("Back", systemImage: "chevron.left").labelStyle(.iconOnly)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ClawdbotProtocol
|
||||||
import Observation
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|||||||
@@ -232,13 +232,27 @@ struct OnboardingWizardStepView: View {
|
|||||||
|
|
||||||
private var selectOptions: some View {
|
private var selectOptions: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
ForEach(self.optionItems) { item in
|
ForEach(self.optionItems, id: \.index) { item in
|
||||||
|
self.selectOptionRow(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var multiselectOptions: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
ForEach(self.optionItems, id: \.index) { item in
|
||||||
|
self.multiselectOptionRow(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectOptionRow(_ item: WizardOptionItem) -> some View {
|
||||||
Button {
|
Button {
|
||||||
self.selectedIndex = item.index
|
self.selectedIndex = item.index
|
||||||
} label: {
|
} label: {
|
||||||
HStack(alignment: .top, spacing: 8) {
|
HStack(alignment: .top, spacing: 8) {
|
||||||
Image(systemName: self.selectedIndex == item.index ? "largecircle.fill.circle" : "circle")
|
Image(systemName: self.selectedIndex == item.index ? "largecircle.fill.circle" : "circle")
|
||||||
.foregroundStyle(.accent)
|
.foregroundStyle(Color.accentColor)
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(item.option.label)
|
Text(item.option.label)
|
||||||
.foregroundStyle(.primary)
|
.foregroundStyle(.primary)
|
||||||
@@ -252,12 +266,8 @@ struct OnboardingWizardStepView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var multiselectOptions: some View {
|
private func multiselectOptionRow(_ item: WizardOptionItem) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
ForEach(self.optionItems) { item in
|
|
||||||
Toggle(isOn: self.bindingForOption(item)) {
|
Toggle(isOn: self.bindingForOption(item)) {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(item.option.label)
|
Text(item.option.label)
|
||||||
@@ -269,8 +279,6 @@ struct OnboardingWizardStepView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func bindingForOption(_ item: WizardOptionItem) -> Binding<Bool> {
|
private func bindingForOption(_ item: WizardOptionItem) -> Binding<Bool> {
|
||||||
Binding(get: {
|
Binding(get: {
|
||||||
|
|||||||
@@ -588,7 +588,9 @@ actor TalkModeRuntime {
|
|||||||
let stream = client.streamSynthesize(voiceId: voiceId, request: request)
|
let stream = client.streamSynthesize(voiceId: voiceId, request: request)
|
||||||
guard self.isCurrent(input.generation) else { return }
|
guard self.isCurrent(input.generation) else { return }
|
||||||
|
|
||||||
if self.interruptOnSpeech, ! await self.prepareForPlayback(generation: input.generation) { return }
|
if self.interruptOnSpeech {
|
||||||
|
guard await self.prepareForPlayback(generation: input.generation) else { return }
|
||||||
|
}
|
||||||
|
|
||||||
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
|
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
|
||||||
self.phase = .speaking
|
self.phase = .speaking
|
||||||
@@ -643,7 +645,9 @@ actor TalkModeRuntime {
|
|||||||
|
|
||||||
private func playSystemVoice(input: TalkPlaybackInput) async throws {
|
private func playSystemVoice(input: TalkPlaybackInput) async throws {
|
||||||
self.ttsLogger.info("talk system voice start chars=\(input.cleanedText.count, privacy: .public)")
|
self.ttsLogger.info("talk system voice start chars=\(input.cleanedText.count, privacy: .public)")
|
||||||
if self.interruptOnSpeech, ! await self.prepareForPlayback(generation: input.generation) { return }
|
if self.interruptOnSpeech {
|
||||||
|
guard await self.prepareForPlayback(generation: input.generation) else { return }
|
||||||
|
}
|
||||||
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
|
await MainActor.run { TalkModeController.shared.updatePhase(.speaking) }
|
||||||
self.phase = .speaking
|
self.phase = .speaking
|
||||||
await TalkSystemSpeechSynthesizer.shared.stop()
|
await TalkSystemSpeechSynthesizer.shared.stop()
|
||||||
@@ -727,7 +731,6 @@ actor TalkModeRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension TalkModeRuntime {
|
extension TalkModeRuntime {
|
||||||
|
|
||||||
// MARK: - Audio playback (MainActor helpers)
|
// MARK: - Audio playback (MainActor helpers)
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
@@ -875,7 +875,7 @@ public struct WizardStep: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct WizardNextResult: Codable {
|
public struct WizardNextResult: Codable, Sendable {
|
||||||
public let done: Bool
|
public let done: Bool
|
||||||
public let step: [String: AnyCodable]?
|
public let step: [String: AnyCodable]?
|
||||||
public let status: AnyCodable?
|
public let status: AnyCodable?
|
||||||
@@ -900,7 +900,7 @@ public struct WizardNextResult: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct WizardStartResult: Codable {
|
public struct WizardStartResult: Codable, Sendable {
|
||||||
public let sessionid: String
|
public let sessionid: String
|
||||||
public let done: Bool
|
public let done: Bool
|
||||||
public let step: [String: AnyCodable]?
|
public let step: [String: AnyCodable]?
|
||||||
@@ -929,7 +929,7 @@ public struct WizardStartResult: Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct WizardStatusResult: Codable {
|
public struct WizardStatusResult: Codable, Sendable {
|
||||||
public let status: AnyCodable
|
public let status: AnyCodable
|
||||||
public let error: String?
|
public let error: String?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user