fix: polish voice overlay and webchat lint

This commit is contained in:
Peter Steinberger
2025-12-08 17:32:34 +01:00
parent 9625d94aa0
commit db3b3ed9eb
5 changed files with 128 additions and 47 deletions

View File

@@ -25,6 +25,9 @@ final class VoiceWakeOverlayController: ObservableObject {
private let width: CGFloat = 360
private let padding: CGFloat = 10
private let buttonWidth: CGFloat = 36
private let spacing: CGFloat = 8
private let verticalPadding: CGFloat = 8
func showPartial(transcript: String, attributed: NSAttributedString? = nil) {
self.autoSendTask?.cancel()
@@ -33,7 +36,7 @@ final class VoiceWakeOverlayController: ObservableObject {
self.model.isFinal = false
self.model.forwardEnabled = false
self.model.isSending = false
self.model.attributed = attributed ?? NSAttributedString(string: transcript)
self.model.attributed = attributed ?? self.makeAttributed(from: transcript)
self.present()
self.updateWindowFrame(animate: true)
}
@@ -45,7 +48,7 @@ final class VoiceWakeOverlayController: ObservableObject {
self.model.isFinal = true
self.model.forwardEnabled = forwardConfig.enabled
self.model.isSending = false
self.model.attributed = attributed ?? NSAttributedString(string: transcript)
self.model.attributed = attributed ?? self.makeAttributed(from: transcript)
self.present()
self.scheduleAutoSend(after: delay)
}
@@ -58,7 +61,7 @@ final class VoiceWakeOverlayController: ObservableObject {
func updateText(_ text: String) {
self.model.text = text
self.model.isSending = false
self.model.attributed = NSAttributedString(string: text)
self.model.attributed = self.makeAttributed(from: text)
self.updateWindowFrame(animate: true)
}
@@ -160,13 +163,8 @@ final class VoiceWakeOverlayController: ObservableObject {
}
private func targetFrame() -> NSRect {
guard let screen = NSScreen.main, let host = self.hostingView else {
return .zero
}
host.layoutSubtreeIfNeeded()
host.invalidateIntrinsicContentSize()
let fit = host.fittingSize
let height = max(42, min(fit.height, 180))
guard let screen = NSScreen.main else { return .zero }
let height = self.measuredHeight()
let size = NSSize(width: self.width, height: height)
let visible = screen.visibleFrame
let origin = CGPoint(
@@ -189,6 +187,18 @@ final class VoiceWakeOverlayController: ObservableObject {
}
}
private func measuredHeight() -> CGFloat {
let attributed = self.model.attributed.length > 0 ? self.model.attributed : self.makeAttributed(from: self.model.text)
let maxWidth = self.width - (self.padding * 2) - self.spacing - self.buttonWidth
let rect = attributed.boundingRect(
with: CGSize(width: maxWidth, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
let contentHeight = ceil(rect.height)
let total = contentHeight + self.verticalPadding * 2
return max(42, min(total, 220))
}
private func dismissTargetFrame(for frame: NSRect, reason: DismissReason, outcome: SendOutcome) -> NSRect? {
switch (reason, outcome) {
case (.empty, _):
@@ -212,6 +222,15 @@ final class VoiceWakeOverlayController: ObservableObject {
self?.sendNow()
}
}
private func makeAttributed(from text: String) -> NSAttributedString {
NSAttributedString(
string: text,
attributes: [
.foregroundColor: NSColor.labelColor,
.font: NSFont.systemFont(ofSize: 13, weight: .regular),
])
}
}
private struct VoiceWakeOverlayView: View {
@@ -289,7 +308,7 @@ private struct TranscriptTextView: NSViewRepresentable {
let textView = TranscriptNSTextView()
textView.delegate = context.coordinator
textView.drawsBackground = false
textView.isRichText = false
textView.isRichText = true
textView.isAutomaticQuoteSubstitutionEnabled = false
textView.isAutomaticTextReplacementEnabled = false
textView.font = .systemFont(ofSize: 13, weight: .regular)
@@ -299,6 +318,8 @@ private struct TranscriptTextView: NSViewRepresentable {
textView.textContainer?.widthTracksTextView = true
textView.textContainer?.containerSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.string = self.text
textView.textStorage?.setAttributedString(self.attributed)
textView.focusRingType = .none
textView.onSend = { [weak textView] in
textView?.window?.makeFirstResponder(nil)
self.onSend()