feat: add floating transcription panel with auto-scroll and copy
This commit is contained in:
parent
285b833ba9
commit
b26995b15b
1 changed files with 78 additions and 0 deletions
78
MyVoxtral/MyVoxtral/Views/TranscriptionWindow.swift
Normal file
78
MyVoxtral/MyVoxtral/Views/TranscriptionWindow.swift
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import SwiftUI
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
struct TranscriptionContentView: View {
|
||||||
|
@ObservedObject var manager: TranscriptionManager
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
Circle()
|
||||||
|
.fill(manager.isRecording ? .red : .gray)
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
Text(manager.isRecording ? "Recording..." : "Idle")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
Spacer()
|
||||||
|
Button {
|
||||||
|
NSPasteboard.general.clearContents()
|
||||||
|
NSPasteboard.general.setString(manager.currentText, forType: .string)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "doc.on.doc")
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderless)
|
||||||
|
.disabled(manager.currentText.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
ScrollView {
|
||||||
|
Text(manager.currentText.isEmpty ? "Transcription will appear here..." : manager.currentText)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.foregroundStyle(manager.currentText.isEmpty ? .secondary : .primary)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
.id("bottom")
|
||||||
|
}
|
||||||
|
.onChange(of: manager.currentText) {
|
||||||
|
proxy.scrollTo("bottom", anchor: .bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(width: 320, height: 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TranscriptionPanel {
|
||||||
|
private var panel: NSPanel?
|
||||||
|
private let manager: TranscriptionManager
|
||||||
|
|
||||||
|
init(manager: TranscriptionManager) {
|
||||||
|
self.manager = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func show() {
|
||||||
|
if panel == nil {
|
||||||
|
let panel = NSPanel(
|
||||||
|
contentRect: NSRect(x: 0, y: 0, width: 320, height: 200),
|
||||||
|
styleMask: [.titled, .closable, .resizable, .nonactivatingPanel, .utilityWindow],
|
||||||
|
backing: .buffered,
|
||||||
|
defer: false
|
||||||
|
)
|
||||||
|
panel.title = "MyVoxtral"
|
||||||
|
panel.isFloatingPanel = true
|
||||||
|
panel.level = .floating
|
||||||
|
panel.contentView = NSHostingView(rootView: TranscriptionContentView(manager: manager))
|
||||||
|
panel.center()
|
||||||
|
self.panel = panel
|
||||||
|
}
|
||||||
|
panel?.orderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hide() {
|
||||||
|
panel?.orderOut(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isVisible: Bool {
|
||||||
|
panel?.isVisible ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue