Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 12, 2026

Summary

Adds a user setting to disable TTS audio playback in Assist while preserving text responses. Users are currently forced to listen to TTS output even in quiet environments or when reading is faster (~60-70% efficiency gain).

Changes:

  • AssistSettingsView: Added "Mute voice responses" toggle (@AppStorage("assistMuteTTS"), default false)
  • AssistViewModel: Modified didReceiveTtsMediaUrl() to check setting and skip audio playback when enabled
    • Extracted UserDefaults key as static let ttsMuteKey for testability
    • Text responses continue displaying regardless of setting
    • Conversation continuation logic preserved
  • Localization: Added assist.settings.tts_mute.toggle and .footer strings with L10n generation
  • Tests: Added testDidReceiveTtsMediaUrlWhenMuted() using shared constant
func didReceiveTtsMediaUrl(_ mediaUrl: URL) {
    let muteTTS = UserDefaults.standard.bool(forKey: Self.ttsMuteKey)
    
    if muteTTS {
        Current.Log.info("TTS is muted by user setting, skipping audio playback")
        startRecordingAgainIfNeeded()
        return
    }
    
    audioPlayer.delegate = self
    audioPlayer.play(url: mediaUrl)
}

Use cases enabled:

  • Voice input → text output (quiet/public environments)
  • Text input → text output (completely silent)
  • User preference for reading over listening

Screenshots

N/A - Settings UI change only, requires iOS 26.0+ build to capture

Link to pull request in Documentation repository

Documentation: home-assistant/companion.home-assistant#

Any other notes

Setting is scoped to iOS 26.0+ matching other Assist settings. Backward compatible - default behavior unchanged.

Original prompt

This section details on the original issue you should resolve

<issue_title>Make TTS optional for Assist - allow text-only responses</issue_title>
<issue_description>## Problem

Currently, the iOS app requires TTS to be configured in the pipeline for Assist to work, even if the user only wants text responses. This forces users to:

  1. Configure a TTS provider they don't need
  2. Listen to audio responses they don't want
  3. Deal with TTS playback in quiet/public environments

The error when TTS is not configured:

PipelineRunValidationError: the pipeline does not support text-to-speech

Justification: Reading is Faster Than Listening

Research shows that reading text is significantly more efficient than listening to speech:

Metric Speed (wpm) Source
Average silent reading 238-260 ScienceDirect meta-analysis
Average TTS speech ~150 Standard TTS output
Efficiency gain ~60-70% Reading vs listening

Additionally:

  • Text can be skimmed/scanned; audio must be consumed linearly
  • TTS is disruptive in quiet environments (office, bedroom, public transit)
  • Some users simply prefer silent interactions

Precedent: ChatGPT App

The ChatGPT iOS app provides flexible voice interaction modes that Home Assistant should emulate:

Mode Input Output Use Case
Text only Keyboard Text Default, quiet environments
Transcription Voice (STT) Text Hands-free input, silent output
Full audio Voice (STT) Voice (TTS) Fully hands-free, driving

ChatGPT allows users to choose their interaction style without requiring all pipeline components. Home Assistant currently forces "Full audio" mode even when users only want "Text only" or "Transcription" modes.

Proposed Changes

  1. Remove TTS requirement for Assist - Allow pipelines with only STT + Conversation (no TTS) to work on mobile
  2. Add "Mute responses" toggle - For pipelines that have TTS configured, allow users to disable audio playback in app settings
  3. Text in/text out always works - Typing a query should never require TTS to be configured

Use Cases Enabled

  • ✅ Voice in → text out (STT only, no TTS needed)
  • ✅ Text in → text out (no STT, no TTS needed)
  • ✅ Voice in → voice out (current behavior, opt-in)
    </issue_description>

<agent_instructions>Update AssistView to allow override the TTS back from server and display only text if user wants, this should be unders assist settings view</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@bgoncal If you start the conversation by text it will reply text back and no STT nor TTS is needed right? Or this is not working for you? @bgoncal Could you please request the feature in here: https://github.com/orgs/home-assistant/discussions/categories/ios-macos

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits January 12, 2026 16:04
Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com>
Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com>
Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com>
Co-authored-by: bgoncal <5808343+bgoncal@users.noreply.github.com>
Copilot AI changed the title [WIP] Make TTS optional for Assist to allow text-only responses Add mute TTS setting for Assist - allow text-only responses Jan 12, 2026
Copilot AI requested a review from bgoncal January 12, 2026 16:12
@bgoncal bgoncal marked this pull request as ready for review January 12, 2026 17:14
Copilot AI review requested due to automatic review settings January 12, 2026 17:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a user setting to mute TTS audio playback in Assist while preserving text responses, addressing the issue where users are forced to listen to TTS output even in quiet environments. The implementation introduces a database-backed configuration system for Assist settings and modifies the pipeline to conditionally request TTS based on user preference.

Changes:

  • Adds AssistConfiguration model with database persistence for Assist settings including TTS mute toggle
  • Modifies AssistSource.audio enum case to include tts: Bool parameter that controls pipeline end_stage
  • Implements settings UI with toggle for muting voice responses (iOS 26.0+)

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Sources/App/Assist/AssistConfiguration.swift New model defining AssistConfiguration with database persistence and ModernAssistTheme enum
Sources/App/Assist/AssistSettingsView.swift Updated to use AssistSettingsViewModel and database-backed configuration instead of @AppStorage
Sources/App/Assist/AssistSettingsViewModel.swift New view model managing AssistConfiguration with auto-save on changes
Sources/App/Assist/AssistViewModel.swift Added configuration property, subscription for config changes, but incorrectly reads TTS mute from UserDefaults instead of configuration
Sources/App/Assist/AssistView.swift Updated to use configuration from view model and subscribe to config changes
Sources/App/Assist/ModernAssistView.swift Extracted ModernAssistTheme enum to AssistConfiguration.swift
Sources/Shared/Intents/AppIntent/AssistInApp/AssistService.swift Updated AssistSource.audio to include tts parameter and pass it to pipeline request
Sources/Shared/Intents/AppIntent/AssistInApp/AssistRequests.swift Modified pipeline request to set end_stage based on tts parameter (intent vs tts)
Sources/Shared/Database/Tables/AssistConfigurationTable.swift New database table for persisting Assist configuration
Sources/Shared/Database/DatabaseTables.swift Added AssistConfiguration enum for table columns
Sources/Shared/Database/GRDB+Initialization.swift Registered AssistConfigurationTable in database initialization
Sources/Watch/WatchCommunicatorService.swift Updated to always use tts: true for watch audio requests
Sources/App/Resources/en.lproj/Localizable.strings Added localization strings for TTS mute toggle and footer
Sources/Shared/Resources/Swiftgen/Strings.swift Generated localization accessors for TTS mute strings
Tests/App/Assist/AssistViewModel.test.swift Added test for muted TTS scenario using UserDefaults (incorrect storage mechanism)
HomeAssistant.xcodeproj/project.pbxproj Added new source files to Xcode project
Comments suppressed due to low confidence (1)

Tests/App/Assist/AssistViewModel.test.swift:124

  • This test is using a UserDefaults key that doesn't match the actual implementation. The TTS mute setting should be stored in the database via AssistConfiguration, not in UserDefaults. The test should either mock the database configuration or inject a test configuration into the AssistViewModel.
    func testAudioPlayerDidFinishPlayingStartRecordingAgain() {
        mockAssistService.shouldStartListeningAgainAfterPlaybackEnd = true
        sut.audioPlayerDidFinishPlaying(AudioPlayer())


func didReceiveTtsMediaUrl(_ mediaUrl: URL) {
// Check if TTS is muted in settings
let muteTTS = UserDefaults.standard.bool(forKey: Self.ttsMuteKey)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

The TTS mute setting is being read from UserDefaults directly, while it's stored in the database via AssistConfiguration. This creates an inconsistency where the settings view saves to database but this method reads from UserDefaults, which will never be set. Use the already available configuration.muteTTS property instead, which is kept in sync via subscribeForConfigChanges().

Suggested change
let muteTTS = UserDefaults.standard.bool(forKey: Self.ttsMuteKey)
let muteTTS = configuration.muteTTS

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +35
// Key for TTS mute setting (matches @AppStorage key in AssistSettingsView)
static let ttsMuteKey = "assistMuteTTS"
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

The ttsMuteKey constant is now unused since TTS mute is stored in the database via AssistConfiguration, not UserDefaults. This constant and the comment referencing @AppStorage should be removed.

Copilot uses AI. Check for mistakes.
}
}

// MARK: - Configuration Persistence
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

This comment "Configuration Persistence" appears misplaced as it's above the classicUI computed property rather than any configuration-related code. Consider removing this comment or moving it to a more appropriate location.

Suggested change
// MARK: - Configuration Persistence
// MARK: - Classic UI

Copilot uses AI. Check for mistakes.
Comment on lines 80 to 84
func testDidStartRecording() {
sut.preferredPipelineId = "2"
sut.didStartRecording(with: 16000)
XCTAssertEqual(mockAssistService.assistSource, .audio(pipelineId: "2", audioSampleRate: 16000.0))
XCTAssertEqual(mockAssistService.assistSource, .audio(pipelineId: "2", audioSampleRate: 16000.0, tts: true))
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

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

Missing test coverage for the scenario where audio recording starts with configuration.muteTTS set to true. The test should verify that when audio recording starts with TTS muted via configuration, the AssistSource is created with tts: false. This would test the integration between AssistViewModel.configuration.muteTTS and the startAssistAudioPipeline method.

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Jan 12, 2026

Codecov Report

❌ Patch coverage is 64.70588% with 6 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@3f27274). Learn more about missing BASE report.

Files with missing lines Patch % Lines
.../Intents/AppIntent/AssistInApp/AssistService.swift 28.57% 5 Missing ⚠️
...Intents/AppIntent/AssistInApp/AssistRequests.swift 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4212   +/-   ##
=======================================
  Coverage        ?   44.89%           
=======================================
  Files           ?      251           
  Lines           ?    14424           
  Branches        ?        0           
=======================================
  Hits            ?     6475           
  Misses          ?     7949           
  Partials        ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bgoncal bgoncal merged commit 4f0b4ec into main Jan 12, 2026
15 checks passed
@bgoncal bgoncal deleted the copilot/make-tts-optional-assist branch January 12, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make TTS optional for Assist - allow text-only responses

2 participants