diff --git a/Sources/App/Assist/AssistView.swift b/Sources/App/Assist/AssistView.swift index 3e6190907..3231bc80f 100644 --- a/Sources/App/Assist/AssistView.swift +++ b/Sources/App/Assist/AssistView.swift @@ -44,7 +44,7 @@ struct AssistView: View { .navigationViewStyle(.stack) .onAppear { assistSession.inProgress = true - viewModel.onAppear() + viewModel.initialRoutine() } .onDisappear { assistSession.inProgress = false diff --git a/Sources/App/Assist/AssistViewModel.swift b/Sources/App/Assist/AssistViewModel.swift index 1618530d2..d3a036ac1 100644 --- a/Sources/App/Assist/AssistViewModel.swift +++ b/Sources/App/Assist/AssistViewModel.swift @@ -19,6 +19,7 @@ final class AssistViewModel: NSObject, ObservableObject { private var assistService: AssistServiceProtocol private(set) var autoStartRecording: Bool private(set) var audioTask: Task? + private(set) var routineTask: Task? private(set) var canSendAudioData = false @@ -43,31 +44,27 @@ final class AssistViewModel: NSObject, ObservableObject { } @MainActor - func onAppear() { + func initialRoutine() { AssistSession.shared.delegate = self - checkForAutoRecordingAndStart() - fetchPipelines() + routineTask?.cancel() + routineTask = Task.detached { [weak self] in + await self?.fetchPipelines() + self?.checkForAutoRecordingAndStart() + } } func onDisappear() { audioRecorder.stopRecording() audioPlayer.pause() audioTask?.cancel() + routineTask?.cancel() } @MainActor func assistWithText() { audioPlayer.pause() stopStreaming() - - guard !inputText.isEmpty else { return } - guard !pipelines.isEmpty, !preferredPipelineId.isEmpty else { - fetchPipelines() - return - } - assistService.assist(source: .text(input: inputText, pipelineId: preferredPipelineId)) - appendToChat(.init(content: inputText, itemType: .input)) inputText = "" } @@ -85,7 +82,7 @@ final class AssistViewModel: NSObject, ObservableObject { inputText = "" audioRecorder.startRecording() - // Wait untill green light from recorder delegate 'didStartRecording' + // Wait until green light from recorder delegate 'didStartRecording' } private func startAssistAudioPipeline(audioSampleRate: Double) { @@ -108,18 +105,22 @@ final class AssistViewModel: NSObject, ObservableObject { } @MainActor - private func fetchPipelines() { + private func fetchPipelines() async { showScreenLoader = true - assistService.fetchPipelines { [weak self] response in - self?.showScreenLoader = false - guard let self, let response else { - self?.showError(message: L10n.Assist.Error.pipelinesResponse) - return - } - if preferredPipelineId.isEmpty { - preferredPipelineId = response.preferredPipeline + await withCheckedContinuation { [weak self] continuation in + self?.assistService.fetchPipelines { [weak self] response in + self?.showScreenLoader = false + guard let self, let response else { + self?.showError(message: L10n.Assist.Error.pipelinesResponse) + continuation.resume() + return + } + if preferredPipelineId.isEmpty { + preferredPipelineId = response.preferredPipeline + } + pipelines = response.pipelines + continuation.resume() } - pipelines = response.pipelines } } @@ -216,7 +217,7 @@ extension AssistViewModel: AssistSessionDelegate { } preferredPipelineId = context.pipelineId autoStartRecording = context.autoStartRecording - onAppear() + initialRoutine() } } } diff --git a/Sources/App/Assist/Tests/AssistViewModel.test.swift b/Sources/App/Assist/Tests/AssistViewModel.test.swift index 39c27960e..24b66b759 100644 --- a/Sources/App/Assist/Tests/AssistViewModel.test.swift +++ b/Sources/App/Assist/Tests/AssistViewModel.test.swift @@ -28,8 +28,10 @@ final class AssistViewModelTests: XCTestCase { } @MainActor - func testOnAppearFetchPipelines() { - sut.onAppear() + func testOnAppearFetchPipelines() async throws { + sut.initialRoutine() + mockAssistService.pipelineResponse = .init(preferredPipeline: "", pipelines: []) + try await sut.routineTask?.value XCTAssert(mockAssistService.fetchPipelinesCalled) XCTAssertEqual(AssistSession.shared.delegate.debugDescription, sut.debugDescription) } @@ -37,7 +39,10 @@ final class AssistViewModelTests: XCTestCase { @MainActor func testOnAppearAutoStartRecording() async throws { sut = makeSut(autoStartRecording: true) - sut.onAppear() + mockAssistService.pipelineResponse = .init(preferredPipeline: "", pipelines: []) + + sut.initialRoutine() + try await sut.routineTask?.value try await sut.audioTask?.value XCTAssertNotNil(sut.audioTask) XCTAssertTrue(mockAudioPlayer.pauseCalled) @@ -47,14 +52,17 @@ final class AssistViewModelTests: XCTestCase { } @MainActor - func testOnDisappear() { + func testOnDisappear() async throws { sut = makeSut(autoStartRecording: true) - sut.onAppear() - sut.onDisappear() + sut.initialRoutine() + try await sut.routineTask?.value + try await sut.audioTask?.value + sut.onDisappear() XCTAssertTrue(mockAudioRecorder.stopRecordingCalled) XCTAssertTrue(mockAudioPlayer.pauseCalled) XCTAssertTrue(sut.audioTask!.isCancelled) + XCTAssertTrue(sut.routineTask!.isCancelled) } @MainActor diff --git a/Sources/App/Assist/Tests/Mocks/MockAssistService.swift b/Sources/App/Assist/Tests/Mocks/MockAssistService.swift index 5c32a9b44..a5295517b 100644 --- a/Sources/App/Assist/Tests/Mocks/MockAssistService.swift +++ b/Sources/App/Assist/Tests/Mocks/MockAssistService.swift @@ -4,7 +4,7 @@ import Shared final class MockAssistService: AssistServiceProtocol { weak var delegate: AssistServiceDelegate? - var fetchPipelinesCompletion: ((PipelineResponse?) -> Void)? + var pipelineResponse: PipelineResponse? var fetchPipelinesCalled: Bool = false var sendAudioDataCalled: Bool = false var assistSource: AssistSource? @@ -13,7 +13,7 @@ final class MockAssistService: AssistServiceProtocol { func fetchPipelines(completion: @escaping (PipelineResponse?) -> Void) { fetchPipelinesCalled = true - fetchPipelinesCompletion = completion + completion(pipelineResponse) } func assist(source: AssistSource) { diff --git a/Sources/App/WebView/IncomingURLHandler.swift b/Sources/App/WebView/IncomingURLHandler.swift index 64926c79a..4c13417d4 100644 --- a/Sources/App/WebView/IncomingURLHandler.swift +++ b/Sources/App/WebView/IncomingURLHandler.swift @@ -92,7 +92,7 @@ class IncomingURLHandler { Current.Log.info(userActivity) if let assistInAppIntent = userActivity.interaction?.intent as? AssistInAppIntent { - guard let server = Current.servers.server(for: assistInAppIntent) else { return false } + guard let server = Current.servers.server(for: assistInAppIntent) ?? Current.servers.all.first else { return false } let pipeline = assistInAppIntent.pipeline let autoStartRecording = Bool(exactly: assistInAppIntent.withVoice ?? 0) ?? false diff --git a/Sources/Shared/Intents/AssistInApp/AssistModel.swift b/Sources/Shared/Intents/AssistInApp/AssistModel.swift index 40ed47c26..99d502b8d 100644 --- a/Sources/Shared/Intents/AssistInApp/AssistModel.swift +++ b/Sources/Shared/Intents/AssistInApp/AssistModel.swift @@ -9,6 +9,11 @@ public struct PipelineResponse: HADataDecodable { self.preferredPipeline = try data.decode("preferred_pipeline") self.pipelines = try data.decode("pipelines") } + + public init(preferredPipeline: String, pipelines: [Pipeline]) { + self.preferredPipeline = preferredPipeline + self.pipelines = pipelines + } } public struct Pipeline: HADataDecodable {