Skip to content

Commit

Permalink
Improvements for Apple Watch Assist (#2839)
Browse files Browse the repository at this point in the history
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->
  • Loading branch information
bgoncal committed Jul 10, 2024
1 parent df68401 commit 2c838a1
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 248 deletions.
6 changes: 6 additions & 0 deletions Sources/App/Assist/AssistViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ extension AssistViewModel: AssistServiceDelegate {
func didReceiveTtsMediaUrl(_ mediaUrl: URL) {
audioPlayer.play(url: mediaUrl)
}

@MainActor
func didReceiveError(code: String, message: String) {
Current.Log.error("Assist error: \(code)")
appendToChat(.init(content: message, itemType: .error))
}
}

extension AssistViewModel: AssistSessionDelegate {
Expand Down
1 change: 1 addition & 0 deletions Sources/App/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"assist.error.pipelines_response" = "Failed to obtain Assist pipelines, please check your pipelines configuration.";
"assist.pipelines_picker.title" = "Assist Pipelines";
"assist.watch.mic_button.title" = "Tap to ";
"assist.watch.not_reachable.title" = "Assist requires iPhone connectivity. Your iPhone is currently unreachable.";
"cancel_label" = "Cancel";
"carPlay.action.intro.item.body" = "Tap to continue on your iPhone";
"carPlay.action.intro.item.title" = "Create your first action";
Expand Down
83 changes: 38 additions & 45 deletions Sources/App/WatchCommunicatorService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,35 +136,27 @@ extension WatchCommunicatorService {
return
}

firstly { () -> Promise<Void> in
Promise { seal in
initAssistServiceIfNeeded(server: server).fetchPipelines { pipelinesResponse in
if let pipelines = pipelinesResponse?.pipelines,
let preferredPipeline = pipelinesResponse?.preferredPipeline {
message.reply(.init(identifier: responseIdentifier, content: [
"pipelines": pipelines.map({ pipeline in
[
"name": pipeline.name,
"id": pipeline.id,
]
}),
"preferredPipeline": preferredPipeline,
]))
seal.fulfill(())
} else {
seal.reject(WatchAssistCommunicatorError.pipelinesFetchFailed)
}
}
initAssistServiceIfNeeded(server: server).fetchPipelines { pipelinesResponse in
if let pipelines = pipelinesResponse?.pipelines,
let preferredPipeline = pipelinesResponse?.preferredPipeline {
message.reply(.init(identifier: responseIdentifier, content: [
"pipelines": pipelines.map({ pipeline in
[
"name": pipeline.name,
"id": pipeline.id,
]
}),
"preferredPipeline": preferredPipeline,
]))
} else {
Current.Log
.error("Error during fetch Assist pipelines: \(WatchAssistCommunicatorError.pipelinesFetchFailed)")
message.reply(.init(identifier: responseIdentifier, content: ["error": true]))
}
}.catch { err in
Current.Log.error("Error during fetch Assist pipelines: \(err)")
message.reply(.init(identifier: responseIdentifier, content: ["error": true]))
}
}

private func assistAudioData(blob: Blob) {
let responseIdentifier = InteractiveImmediateResponses.assistAudioDataResponse.rawValue

let serverId = blob.metadata?["serverId"] as? String
guard let server = Current.servers.all.first(where: { $0.identifier.rawValue == serverId }) ?? Current
.servers.all.first else {
Expand All @@ -173,28 +165,18 @@ extension WatchCommunicatorService {
return
}

firstly { [weak self] () -> Promise<Void> in
Promise { seal in
let pipelineId = blob.metadata?["pipelineId"] as? String ?? ""
guard let self, let sampleRate = blob.metadata?["sampleRate"] as? Double else {
let errorMessage = "No sample rate received in message \(blob.identifier)"
Current.Log.error(errorMessage)
return
}
let audioData = blob.content
self.pendingAudioData = audioData
self.initAssistServiceIfNeeded(server: server).assist(source: .audio(
pipelineId: pipelineId,
audioSampleRate: sampleRate
))
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
seal.fulfill(())
}
}
}.catch { err in
let errorMessage = "Error during fetch Assist pipelines: \(err)"
Current.Log.warning(errorMessage)
let pipelineId = blob.metadata?["pipelineId"] as? String
guard let sampleRate = blob.metadata?["sampleRate"] as? Double else {
let errorMessage = "No sample rate received in message \(blob.identifier)"
Current.Log.error(errorMessage)
return
}
let audioData = blob.content
pendingAudioData = audioData
initAssistServiceIfNeeded(server: server).assist(source: .audio(
pipelineId: pipelineId,
audioSampleRate: sampleRate
))
}

private func initAssistServiceIfNeeded(server: Server) -> AssistServiceProtocol {
Expand Down Expand Up @@ -262,6 +244,17 @@ extension WatchCommunicatorService: AssistServiceDelegate {
)
sendMessage(message: message)
}

func didReceiveError(code: String, message: String) {
let message = ImmediateMessage(
identifier: InteractiveImmediateResponses.assistError.rawValue,
content: [
"code": code,
"message": message,
]
)
sendMessage(message: message)
}
}

// MARK: - ServerObserver
Expand Down
71 changes: 39 additions & 32 deletions Sources/Extensions/Watch/Assist/Settings/WatchAssistSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,54 @@ import Shared
import SwiftUI

struct WatchAssistSettings: View {
@EnvironmentObject var assistService: WatchAssistService
@StateObject private var assistService: WatchAssistService

init(assistService: WatchAssistService) {
self._assistService = .init(wrappedValue: assistService)
}

var body: some View {
ScrollView {
VStack(spacing: Spaces.two) {
VStack {
Text(L10n.Settings.ConnectionSection.servers)
Picker(selection: $assistService.selectedServer) {
ForEach($assistService.servers.wrappedValue, id: \.identifier.rawValue) { server in
Text(server.info.name)
.tag(server.identifier.rawValue)
if !assistService.servers.isEmpty, assistService.selectedServer != nil {
VStack {
Text(L10n.Settings.ConnectionSection.servers)
Picker(selection: $assistService.selectedServer) {
ForEach(assistService.servers, id: \.identifier.rawValue) { server in
Text(server.info.name)
.tag(server.identifier.rawValue)
}
} label: {
EmptyView()
}
} label: {
EmptyView()
}
.modify {
if #available(watchOS 9, *) {
$0.pickerStyle(.navigationLink)
} else {
$0.pickerStyle(.wheel)
.frame(height: 100)
.modify {
if #available(watchOS 9, *) {
$0.pickerStyle(.navigationLink)
} else {
$0.pickerStyle(.wheel)
.frame(height: 100)
}
}
}
}

VStack {
Text(L10n.Assist.PipelinesPicker.title)
Picker(selection: $assistService.preferredPipeline) {
ForEach($assistService.pipelines.wrappedValue, id: \.id) { pipeline in
Text(pipeline.name)
.tag(pipeline.id)
if !assistService.isFetchingPipeline, !assistService.pipelines.isEmpty {
VStack {
Text(L10n.Assist.PipelinesPicker.title)
Picker(selection: $assistService.preferredPipeline) {
ForEach(assistService.pipelines, id: \.id) { pipeline in
Text(pipeline.name)
.tag(pipeline.id)
}
} label: {
EmptyView()
}
} label: {
EmptyView()
}
.modify {
if #available(watchOS 9, *) {
$0.pickerStyle(.navigationLink)
} else {
$0.pickerStyle(.wheel)
.frame(height: 100)
.modify {
if #available(watchOS 9, *) {
$0.pickerStyle(.navigationLink)
} else {
$0.pickerStyle(.wheel)
.frame(height: 100)
}
}
}
}
Expand Down
Loading

0 comments on commit 2c838a1

Please sign in to comment.