fix(ui): refine talk overlays
This commit is contained in:
@@ -16,6 +16,10 @@
|
|||||||
- macOS Talk Mode: fix audio stop ordering so disabling Talk Mode always stops in-flight playback.
|
- 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: throttle audio-level updates (avoid per-buffer task creation) to reduce CPU/task churn.
|
||||||
- macOS Talk Mode: increase overlay window size so wave rings don’t clip; close button is hover-only and closer to the orb.
|
- macOS Talk Mode: increase overlay window size so wave rings don’t clip; close button is hover-only and closer to the orb.
|
||||||
|
- Talk Mode: align to the gateway’s 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).
|
- 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: 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.
|
- iOS Talk Mode: keep recognition running during playback to support interrupt-on-speech.
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fun TalkOrbOverlay(
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
Canvas(modifier = Modifier.size(240.dp)) {
|
Canvas(modifier = Modifier.size(300.dp)) {
|
||||||
val center = this.center
|
val center = this.center
|
||||||
val baseRadius = size.minDimension * 0.27f
|
val baseRadius = size.minDimension * 0.27f
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ struct TalkOrbOverlay: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(seam.opacity(0.26), lineWidth: 2)
|
.stroke(seam.opacity(0.26), lineWidth: 2)
|
||||||
.frame(width: 220, height: 220)
|
.frame(width: 280, height: 280)
|
||||||
.scaleEffect(self.pulse ? 1.15 : 0.96)
|
.scaleEffect(self.pulse ? 1.15 : 0.96)
|
||||||
.opacity(self.pulse ? 0.0 : 1.0)
|
.opacity(self.pulse ? 0.0 : 1.0)
|
||||||
.animation(.easeOut(duration: 1.3).repeatForever(autoreverses: false), value: self.pulse)
|
.animation(.easeOut(duration: 1.3).repeatForever(autoreverses: false), value: self.pulse)
|
||||||
|
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(seam.opacity(0.18), lineWidth: 2)
|
.stroke(seam.opacity(0.18), lineWidth: 2)
|
||||||
.frame(width: 220, height: 220)
|
.frame(width: 280, height: 280)
|
||||||
.scaleEffect(self.pulse ? 1.45 : 1.02)
|
.scaleEffect(self.pulse ? 1.45 : 1.02)
|
||||||
.opacity(self.pulse ? 0.0 : 0.9)
|
.opacity(self.pulse ? 0.0 : 0.9)
|
||||||
.animation(.easeOut(duration: 1.9).repeatForever(autoreverses: false).delay(0.2), value: self.pulse)
|
.animation(.easeOut(duration: 1.9).repeatForever(autoreverses: false).delay(0.2), value: self.pulse)
|
||||||
@@ -34,8 +34,8 @@ struct TalkOrbOverlay: View {
|
|||||||
],
|
],
|
||||||
center: .center,
|
center: .center,
|
||||||
startRadius: 1,
|
startRadius: 1,
|
||||||
endRadius: 92))
|
endRadius: 112))
|
||||||
.frame(width: 136, height: 136)
|
.frame(width: 168, height: 168)
|
||||||
.overlay(
|
.overlay(
|
||||||
Circle()
|
Circle()
|
||||||
.stroke(seam.opacity(0.35), lineWidth: 1))
|
.stroke(seam.opacity(0.35), lineWidth: 1))
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import SwiftUI
|
|||||||
@Observable
|
@Observable
|
||||||
final class TalkOverlayController {
|
final class TalkOverlayController {
|
||||||
static let shared = 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")
|
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 size = NSSize(width: Self.overlaySize, height: Self.overlaySize)
|
||||||
let visible = screen.visibleFrame
|
let visible = screen.visibleFrame
|
||||||
let origin = CGPoint(
|
let origin = CGPoint(
|
||||||
x: visible.maxX - size.width - self.padding,
|
x: visible.maxX - size.width - self.padding + Self.windowInset,
|
||||||
y: visible.maxY - size.height - self.padding)
|
y: visible.maxY - size.height - self.padding + Self.windowInset)
|
||||||
return NSRect(origin: origin, size: size)
|
return NSRect(origin: origin, size: size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ struct TalkOverlayView: View {
|
|||||||
level: self.controller.model.level,
|
level: self.controller.model.level,
|
||||||
accent: self.seamColor)
|
accent: self.seamColor)
|
||||||
.frame(width: 96, height: 96)
|
.frame(width: 96, height: 96)
|
||||||
.padding(.top, 6)
|
.padding(.top, 6 + TalkOverlayController.windowInset)
|
||||||
.padding(.trailing, 6)
|
.padding(.trailing, 6 + TalkOverlayController.windowInset)
|
||||||
.contentShape(Circle())
|
.contentShape(Circle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
TalkModeController.shared.stopSpeaking(reason: .userTap)
|
TalkModeController.shared.stopSpeaking(reason: .userTap)
|
||||||
@@ -31,7 +31,7 @@ struct TalkOverlayView: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.contentShape(Circle())
|
.contentShape(Circle())
|
||||||
.offset(x: -7, y: -7)
|
.offset(x: -5, y: -5)
|
||||||
.opacity(self.hoveringWindow ? 1 : 0)
|
.opacity(self.hoveringWindow ? 1 : 0)
|
||||||
.animation(.easeOut(duration: 0.12), value: self.hoveringWindow)
|
.animation(.easeOut(duration: 0.12), value: self.hoveringWindow)
|
||||||
.allowsHitTesting(self.hoveringWindow)
|
.allowsHitTesting(self.hoveringWindow)
|
||||||
|
|||||||
Reference in New Issue
Block a user