diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f667dbf9..c41f6a9dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Cron/Gateway: separate per-job webhook delivery (`delivery.mode = "webhook"`) from announce delivery, enforce valid HTTP(S) webhook URLs, and keep a temporary legacy `notify + cron.webhook` fallback for stored jobs. (#17901) Thanks @advaitpaliwal. +- iOS/Talk: add a `Voice Directive Hint` toggle for Talk Mode prompts so users can disable ElevenLabs voice-switching instructions to save tokens when not needed. (#18250) Thanks @zeulewan. - Telegram/Agents: add inline button `style` support (`primary|success|danger`) across message tool schema, Telegram action parsing, send pipeline, and runtime prompt guidance. (#18241) Thanks @obviyus. ### Fixes diff --git a/apps/ios/Sources/Settings/SettingsTab.swift b/apps/ios/Sources/Settings/SettingsTab.swift index 0f8e461d7d..31653f1966 100644 --- a/apps/ios/Sources/Settings/SettingsTab.swift +++ b/apps/ios/Sources/Settings/SettingsTab.swift @@ -15,6 +15,7 @@ struct SettingsTab: View { @AppStorage("voiceWake.enabled") private var voiceWakeEnabled: Bool = false @AppStorage("talk.enabled") private var talkEnabled: Bool = false @AppStorage("talk.button.enabled") private var talkButtonEnabled: Bool = true + @AppStorage("talk.voiceDirectiveHint.enabled") private var talkVoiceDirectiveHintEnabled: Bool = true @AppStorage("camera.enabled") private var cameraEnabled: Bool = true @AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = OpenClawLocationMode.off.rawValue @AppStorage("location.preciseEnabled") private var locationPreciseEnabled: Bool = true @@ -255,6 +256,10 @@ struct SettingsTab: View { Text("Use this local override when gateway config redacts talk.apiKey for mobile clients.") .font(.footnote) .foregroundStyle(.secondary) + Toggle("Voice Directive Hint", isOn: self.$talkVoiceDirectiveHintEnabled) + Text("Include ElevenLabs voice switching instructions in the Talk Mode prompt. Disable to save tokens.") + .font(.footnote) + .foregroundStyle(.secondary) // Keep this separate so users can hide the side bubble without disabling Talk Mode. Toggle("Show Talk Button", isOn: self.$talkButtonEnabled) diff --git a/apps/ios/Sources/Voice/TalkModeManager.swift b/apps/ios/Sources/Voice/TalkModeManager.swift index fae5c28be4..10aaad8acb 100644 --- a/apps/ios/Sources/Voice/TalkModeManager.swift +++ b/apps/ios/Sources/Voice/TalkModeManager.swift @@ -828,7 +828,11 @@ final class TalkModeManager: NSObject { private func buildPrompt(transcript: String) -> String { let interrupted = self.lastInterruptedAtSeconds self.lastInterruptedAtSeconds = nil - return TalkPromptBuilder.build(transcript: transcript, interruptedAtSeconds: interrupted) + let includeVoiceDirectiveHint = (UserDefaults.standard.object(forKey: "talk.voiceDirectiveHint.enabled") as? Bool) ?? true + return TalkPromptBuilder.build( + transcript: transcript, + interruptedAtSeconds: interrupted, + includeVoiceDirectiveHint: includeVoiceDirectiveHint) } private enum ChatCompletionState: CustomStringConvertible { diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/TalkPromptBuilder.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/TalkPromptBuilder.swift index c63f40e9d3..2a2e39d68c 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/TalkPromptBuilder.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/TalkPromptBuilder.swift @@ -1,10 +1,19 @@ public enum TalkPromptBuilder: Sendable { - public static func build(transcript: String, interruptedAtSeconds: Double?) -> String { + public static func build( + transcript: String, + interruptedAtSeconds: Double?, + includeVoiceDirectiveHint: Bool = true + ) -> String { var lines: [String] = [ "Talk Mode active. Reply in a concise, spoken tone.", - "You may optionally prefix the response with JSON (first line) to set ElevenLabs voice (id or alias), e.g. {\"voice\":\"\",\"once\":true}.", ] + if includeVoiceDirectiveHint { + lines.append( + "You may optionally prefix the response with JSON (first line) to set ElevenLabs voice (id or alias), e.g. {\"voice\":\"\",\"once\":true}." + ) + } + if let interruptedAtSeconds { let formatted = String(format: "%.1f", interruptedAtSeconds) lines.append("Assistant speech interrupted at \(formatted)s.") diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/TalkPromptBuilderTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/TalkPromptBuilderTests.swift index 1ca18fdf32..513b60d047 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/TalkPromptBuilderTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/TalkPromptBuilderTests.swift @@ -12,4 +12,18 @@ final class TalkPromptBuilderTests: XCTestCase { let prompt = TalkPromptBuilder.build(transcript: "Hi", interruptedAtSeconds: 1.234) XCTAssertTrue(prompt.contains("Assistant speech interrupted at 1.2s.")) } + + func testBuildIncludesVoiceDirectiveHintByDefault() { + let prompt = TalkPromptBuilder.build(transcript: "Hello", interruptedAtSeconds: nil) + XCTAssertTrue(prompt.contains("ElevenLabs voice")) + } + + func testBuildExcludesVoiceDirectiveHintWhenDisabled() { + let prompt = TalkPromptBuilder.build( + transcript: "Hello", + interruptedAtSeconds: nil, + includeVoiceDirectiveHint: false) + XCTAssertFalse(prompt.contains("ElevenLabs voice")) + XCTAssertTrue(prompt.contains("Talk Mode active.")) + } }