initial commit

This commit is contained in:
Sam
2023-03-26 17:31:42 -04:00
commit e3b5b090fb
51 changed files with 4222 additions and 0 deletions

View File

@ -0,0 +1,39 @@
import SwiftUI
import LibWhisper
struct AuthTokenView: View {
@Binding var storedToken: String?
@Binding var useGPT4: Bool
@State var tokenValue = ""
@State var toggleValue = true
var body: some View {
VStack(spacing: 16) {
Link(destination: URL(string: "https://platform.openai.com/account/api-keys")!) {
Text("Click here to create an OpenAI API key")
}
TextField(text: $tokenValue) {
Text("Paste your API key here")
}
.privacySensitive()
.frame(width: 300)
Toggle("Use GPT-4 (access required)", isOn: $toggleValue)
Button("Save") {
storedToken = tokenValue
useGPT4 = toggleValue
}
.disabled(tokenValue.isEmpty)
}
.padding()
.fixedSize()
}
}
struct APIKeyView_Previews: PreviewProvider {
static var previews: some View {
return AuthTokenView(
storedToken: Binding.constant(nil),
useGPT4: Binding.constant(false))
}
}

View File

@ -0,0 +1,86 @@
import SwiftUI
import LibWhisper
struct CoachView: View {
@ObservedObject var viewModel: AppViewModel
@State var answer: String
@State var answerSelection = NSRange()
init(viewModel: AppViewModel) {
self.viewModel = viewModel
self.answer = viewModel.answer ?? ""
}
var spinner: some View {
ProgressView().scaleEffect(0.5)
}
@ViewBuilder
var body: some View {
Picker("Audio input device", selection: $viewModel.selectedDevice) {
Text("-").tag(nil as CaptureDevice?)
ForEach(viewModel.devices, id: \.self) {
Text($0.name).tag($0 as CaptureDevice?)
}
}
.labelsHidden()
.pickerStyle(.menu)
ZStack {
HStack(spacing: 10) {
Button(action: {
viewModel.answerRequest = .answerQuestion
}, label: {
Text("Answer")
})
Button(action: {
viewModel.answerRequest = .refineAnswer(selection: Range(answerSelection, in: answer))
}, label: {
Text("Refine")
})
Button(action: {
viewModel.answerRequest = .analyzeCode
}, label: {
Text("Analyze")
})
}
.disabled((viewModel.authToken == nil || viewModel.analyzer == nil) && !viewModel.buttonsAlwaysEnabled)
HStack {
Spacer()
switch viewModel.answerRequest {
case .none:
spinner.hidden()
default:
spinner
}
}
}
HStack {
VStack(alignment: .leading, spacing: 20) {
if let transcript = viewModel.transcript {
Text(transcript)
.lineLimit(1)
.truncationMode(.head)
.font(.footnote.italic())
}
ScrollView {
NSTextFieldWrapper(text: $answer, selectedRange: $answerSelection)
.onChange(of: viewModel.answer) {
if let newAnswer = $0 {
self.answer = newAnswer
}
}
}
.frame(maxHeight: 600)
if let solution = viewModel.codeAnswer {
Text(solution)
.textSelection(.enabled)
.font(.footnote)
.monospaced()
}
Spacer()
}
Spacer()
}
}
}

View File

@ -0,0 +1,68 @@
import SwiftUI
import LibWhisper
struct ContentView: View {
@ObservedObject var viewModel: AppViewModel
@ViewBuilder
var body: some View {
if viewModel.authToken != nil {
VStack(spacing: 16) {
switch viewModel.downloadState {
case .pending:
Text("Downloading \(viewModel.whisperModel)...")
case .failed(let error):
if let error = error {
Text("Failed to download model. \(error.localizedDescription)")
} else {
Text("Failed to download model. An unknown error occurred.")
}
case .completed:
CoachView(viewModel: viewModel)
}
}
.padding()
.frame(minWidth: 300, minHeight: 350)
} else {
AuthTokenView(storedToken: viewModel.$authToken,
useGPT4: viewModel.$useGPT4.nonEmpty)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let viewModel = AppViewModel()
viewModel.devices = [CaptureDevice(id: 0, name: "Audio Loopback Device")]
viewModel.buttonsAlwaysEnabled = true
viewModel.authToken = ""
viewModel.downloadState = .completed
viewModel.transcript = "So how would we break this app down into components?"
viewModel.answer = """
• Header Component: Contains two sub-components: Logo and Title.
Props: logoUrl, title
• Content Component: Contains an image and a paragraph.
Props: imageUrl, message
• Footer Component: Simple component that displays a message.
Props: message
• App Component: Renders the Header, Content, and Footer components
"""
return ContentView(viewModel: viewModel)
.previewLayout(.fixed(width: 300, height: 500))
.previewDisplayName("Cheetah")
}
}
extension Binding where Value == Bool? {
var nonEmpty: Binding<Bool> {
Binding<Bool>(
get: { self.wrappedValue ?? false },
set: { self.wrappedValue = $0 }
)
}
}

View File

@ -0,0 +1,53 @@
import SwiftUI
import AppKit
class CustomNSTextField: RSHeightHuggingTextField {
var onSelectedRangesChanged: ((NSRange?) -> Void)?
@objc func textViewDidChangeSelection(_ notification: NSNotification) {
onSelectedRangesChanged?(currentEditor()?.selectedRange)
}
}
struct NSTextFieldWrapper: NSViewRepresentable {
@Binding var text: String
@Binding var selectedRange: NSRange
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> CustomNSTextField {
let textField = CustomNSTextField(frame: NSRect())
textField.isBezeled = false
textField.isEditable = false
textField.isSelectable = true
textField.drawsBackground = false
textField.delegate = context.coordinator
textField.onSelectedRangesChanged = { range in
if let range = range {
DispatchQueue.main.async {
self.selectedRange = range
}
}
}
return textField
}
func updateNSView(_ nsView: CustomNSTextField, context: Context) {
nsView.stringValue = text
}
class Coordinator: NSObject, NSTextFieldDelegate {
var parent: NSTextFieldWrapper
init(_ textField: NSTextFieldWrapper) {
self.parent = textField
}
func controlTextDidChange(_ obj: Notification) {
guard let textField = obj.object as? NSTextField else { return }
self.parent.text = textField.stringValue
}
}
}

View File

@ -0,0 +1,106 @@
//
// RSDimensionHuggingTextField.swift
// RSUIKit
//
// Created by Daniel Jalkut on 6/13/18.
// Copyright © 2018 Red Sweater. All rights reserved.
//
import Cocoa
// You probably want to use one of RSHeightHuggingTextField or RSWidthHuggingTextField, below
open class RSDimensionHuggingTextField: NSTextField {
public enum Dimension {
case vertical
case horizontal
}
var huggedDimension: Dimension
init(frame frameRect: NSRect, huggedDimension: Dimension) {
self.huggedDimension = huggedDimension
super.init(frame: frameRect)
}
// For subclasses to pass in the dimension setting
public init?(coder: NSCoder, huggedDimension: Dimension) {
self.huggedDimension = huggedDimension
super.init(coder: coder)
}
public required init?(coder: NSCoder) {
// We don't yet support dimension being coded, just default to vertical
self.huggedDimension = .vertical
super.init(coder: coder)
}
open override var intrinsicContentSize: NSSize {
get {
guard let textCell = self.cell else {
return super.intrinsicContentSize
}
// Set up the bounds to induce unlimited sizing in the desired dimension
var cellSizeBounds = self.bounds
switch self.huggedDimension {
case .vertical: cellSizeBounds.size.height = CGFloat(Float.greatestFiniteMagnitude)
case .horizontal: cellSizeBounds.size.width = CGFloat(Float.greatestFiniteMagnitude)
}
// Do the actual sizing
let nativeCellSize = textCell.cellSize(forBounds: cellSizeBounds)
// Return an intrinsic size that imposes calculated (hugged) dimensional size
var intrinsicSize = NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
switch self.huggedDimension {
case .vertical:
intrinsicSize.height = nativeCellSize.height
case .horizontal:
intrinsicSize.width = nativeCellSize.width
}
return intrinsicSize
}
}
open override func textDidChange(_ notification: Notification) {
super.textDidChange(notification)
self.invalidateIntrinsicContentSize()
// It seems important to set the string from the cell on ourself to
// get the change to be respected by the cell and to get the cellSize
// computation to update!
if let changedCell = self.cell {
self.stringValue = changedCell.stringValue
}
}
}
open class RSHeightHuggingTextField: RSDimensionHuggingTextField {
@objc init(frame frameRect: NSRect) {
super.init(frame: frameRect, huggedDimension: .vertical)
}
public required init?(coder: NSCoder) {
super.init(coder: coder, huggedDimension: .vertical)
}
public override init(frame frameRect: NSRect, huggedDimension: Dimension = .vertical) {
super.init(frame: frameRect, huggedDimension: huggedDimension)
}
}
open class RSWidthHuggingTextField: RSDimensionHuggingTextField {
@objc init(frame frameRect: NSRect) {
super.init(frame: frameRect, huggedDimension: .horizontal)
}
public required init?(coder: NSCoder) {
super.init(coder: coder, huggedDimension: .horizontal)
}
public override init(frame frameRect: NSRect, huggedDimension: Dimension = .horizontal) {
super.init(frame: frameRect, huggedDimension: huggedDimension)
}
}