fix(ui): refine talk overlays

This commit is contained in:
Peter Steinberger
2025-12-30 06:47:35 +01:00
parent 7612a83fa2
commit a7617e4d79
5 changed files with 16 additions and 11 deletions

View File

@@ -16,6 +16,10 @@
- macOS Talk Mode: fix audio stop ordering so disabling Talk Mode always stops in-flight playback.
- macOS Talk Mode: throttle audio-level updates (avoid per-buffer task creation) to reduce CPU/task churn.
- macOS Talk Mode: increase overlay window size so wave rings dont clip; close button is hover-only and closer to the orb.
- Talk Mode: align to the gateways main session key and fall back to history polling when chat events drop (prevents stuck “thinking” / missing messages).
- Talk Mode: treat history timestamps as seconds or milliseconds to avoid stale assistant picks (macOS/iOS/Android).
- Chat UI: dedupe identical history messages to avoid duplicate bubbles.
- Chat UI: user bubbles use `ui.seamColor` (fallback to a calmer default blue).
- Talk Mode: wait for chat history to surface the assistant reply before starting TTS (macOS/iOS/Android).
- iOS Talk Mode: fix chat completion wait to time out even if no events arrive (prevents “Thinking…” hangs).
- iOS Talk Mode: keep recognition running during playback to support interrupt-on-speech.

View File

@@ -62,7 +62,7 @@ fun TalkOrbOverlay(
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(contentAlignment = Alignment.Center) {
Canvas(modifier = Modifier.size(240.dp)) {
Canvas(modifier = Modifier.size(300.dp)) {
val center = this.center
val baseRadius = size.minDimension * 0.27f

View File

@@ -12,14 +12,14 @@ struct TalkOrbOverlay: View {
ZStack {
Circle()
.stroke(seam.opacity(0.26), lineWidth: 2)
.frame(width: 220, height: 220)
.frame(width: 280, height: 280)
.scaleEffect(self.pulse ? 1.15 : 0.96)
.opacity(self.pulse ? 0.0 : 1.0)
.animation(.easeOut(duration: 1.3).repeatForever(autoreverses: false), value: self.pulse)
Circle()
.stroke(seam.opacity(0.18), lineWidth: 2)
.frame(width: 220, height: 220)
.frame(width: 280, height: 280)
.scaleEffect(self.pulse ? 1.45 : 1.02)
.opacity(self.pulse ? 0.0 : 0.9)
.animation(.easeOut(duration: 1.9).repeatForever(autoreverses: false).delay(0.2), value: self.pulse)
@@ -34,8 +34,8 @@ struct TalkOrbOverlay: View {
],
center: .center,
startRadius: 1,
endRadius: 92))
.frame(width: 136, height: 136)
endRadius: 112))
.frame(width: 168, height: 168)
.overlay(
Circle()
.stroke(seam.opacity(0.35), lineWidth: 1))

View File

@@ -7,7 +7,8 @@ import SwiftUI
@Observable
final class TalkOverlayController {
static let shared = TalkOverlayController()
static let overlaySize: CGFloat = 320
static let overlaySize: CGFloat = 360
static let windowInset: CGFloat = 88
private let logger = Logger(subsystem: "com.steipete.clawdis", category: "talk.overlay")
@@ -110,8 +111,8 @@ final class TalkOverlayController {
let size = NSSize(width: Self.overlaySize, height: Self.overlaySize)
let visible = screen.visibleFrame
let origin = CGPoint(
x: visible.maxX - size.width - self.padding,
y: visible.maxY - size.height - self.padding)
x: visible.maxX - size.width - self.padding + Self.windowInset,
y: visible.maxY - size.height - self.padding + Self.windowInset)
return NSRect(origin: origin, size: size)
}
}

View File

@@ -12,8 +12,8 @@ struct TalkOverlayView: View {
level: self.controller.model.level,
accent: self.seamColor)
.frame(width: 96, height: 96)
.padding(.top, 6)
.padding(.trailing, 6)
.padding(.top, 6 + TalkOverlayController.windowInset)
.padding(.trailing, 6 + TalkOverlayController.windowInset)
.contentShape(Circle())
.onTapGesture {
TalkModeController.shared.stopSpeaking(reason: .userTap)
@@ -31,7 +31,7 @@ struct TalkOverlayView: View {
}
.buttonStyle(.plain)
.contentShape(Circle())
.offset(x: -7, y: -7)
.offset(x: -5, y: -5)
.opacity(self.hoveringWindow ? 1 : 0)
.animation(.easeOut(duration: 0.12), value: self.hoveringWindow)
.allowsHitTesting(self.hoveringWindow)