8 Commits
v1.0 ... v1.2

Author SHA1 Message Date
f2945061ca bump version 2023-04-19 17:17:09 -04:00
d3b668bbfe implement errorDescription for OpenAIError 2023-04-19 17:14:44 -04:00
a0f708859d bump version 2023-04-19 14:45:21 -04:00
60c882000f add menu items to change API key and toggle GPT-4 2023-04-19 14:40:01 -04:00
b91561d6df add error alert 2023-04-18 19:42:42 -04:00
82da6641bb Update README.md 2023-04-18 13:14:50 -04:00
22cd854c37 Update README.md 2023-04-17 13:21:25 -04:00
5c85e34b41 Update README.md
fixes #3
2023-04-09 14:51:52 -04:00
9 changed files with 114 additions and 34 deletions

View File

@ -786,7 +786,7 @@
CODE_SIGN_ENTITLEMENTS = Cheetah/Cheetah.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Cheetah/Preview Content\"";
DEVELOPMENT_TEAM = 5JL49Y835V;
ENABLE_HARDENED_RUNTIME = NO;
@ -799,7 +799,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = org.phrack.Cheetah;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -816,7 +816,7 @@
CODE_SIGN_ENTITLEMENTS = Cheetah/Cheetah.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Cheetah/Preview Content\"";
DEVELOPMENT_TEAM = 5JL49Y835V;
ENABLE_HARDENED_RUNTIME = NO;
@ -829,7 +829,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = org.phrack.Cheetah;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;

View File

@ -24,6 +24,7 @@ class AppViewModel: ObservableObject {
@Published var analyzer: ConversationAnalyzer?
@Published var answerRequest = AnswerRequest.none
@Published var errorDescription: String?
@Published var transcript: String?
@Published var answer: String?
@ -67,35 +68,40 @@ struct CheetahApp: App {
// Install manifest needed for the browser extension to talk to ExtensionHelper
_ = try? installNativeMessagingManifest()
do {
for try await request in viewModel.$answerRequest.receive(on: RunLoop.main).values {
if let analyzer = viewModel.analyzer {
switch request {
case .answerQuestion:
try await analyzer.answer()
viewModel.answer = analyzer.context[.answer]
viewModel.codeAnswer = analyzer.context[.codeAnswer]
viewModel.answerRequest = .none
case .refineAnswer(let selection):
try await analyzer.answer(refine: true, selection: selection)
viewModel.answer = analyzer.context[.answer]
viewModel.codeAnswer = analyzer.context[.codeAnswer]
viewModel.answerRequest = .none
case .analyzeCode:
try await analyzer.analyzeCode(extensionState: extensionState)
viewModel.answer = analyzer.context[.answer]
viewModel.answerRequest = .none
case .none:
break
while true {
do {
for try await request in viewModel.$answerRequest.receive(on: RunLoop.main).values {
if let analyzer = viewModel.analyzer {
switch request {
case .answerQuestion:
try await analyzer.answer()
viewModel.answer = analyzer.context[.answer]
viewModel.codeAnswer = analyzer.context[.codeAnswer]
viewModel.answerRequest = .none
case .refineAnswer(let selection):
try await analyzer.answer(refine: true, selection: selection)
viewModel.answer = analyzer.context[.answer]
viewModel.codeAnswer = analyzer.context[.codeAnswer]
viewModel.answerRequest = .none
case .analyzeCode:
try await analyzer.analyzeCode(extensionState: extensionState)
viewModel.answer = analyzer.context[.answer]
viewModel.answerRequest = .none
case .none:
break
}
}
}
} catch let error as ErrorResult {
viewModel.errorDescription = error.message
viewModel.answerRequest = .none
} catch {
viewModel.errorDescription = error.localizedDescription
viewModel.answerRequest = .none
}
} catch {
viewModel.answerRequest = .none
//TODO: handle error
}
}
@ -111,6 +117,34 @@ struct CheetahApp: App {
}
.windowResizability(.contentSize)
.windowStyle(.hiddenTitleBar)
.commands {
CommandGroup(replacing: .appSettings) {
Button(action: {
viewModel.authToken = nil
resetAfterSettingsChanged()
}) {
Text("Change API Key…")
}
Button(action: {
if viewModel.useGPT4 == true {
viewModel.useGPT4 = false
} else {
viewModel.useGPT4 = true
}
resetAfterSettingsChanged()
}) {
Text("Use GPT-4")
if viewModel.useGPT4 == true {
Image(systemName: "checkmark")
}
}
}
}
}
func resetAfterSettingsChanged() {
viewModel.selectedDevice = nil
viewModel.analyzer = nil
}
func setCaptureDevice(_ device: CaptureDevice?) {

View File

@ -77,6 +77,8 @@ class OpenAIExecutor {
let text = result.choices?.first?.text
if let text = text {
log(completion: text)
} else if let error = result.error {
throw error
}
return text
}
@ -87,6 +89,8 @@ class OpenAIExecutor {
let content = result.choices?.first?.message.content
if let content = content {
log(completion: content)
} else if let error = result.error {
throw error
}
return content
}

View File

@ -18,7 +18,7 @@ struct AuthTokenView: View {
}
.privacySensitive()
.frame(width: 300)
Toggle("Use GPT-4 (access required)", isOn: $toggleValue)
Toggle("Use GPT-4 (API access required)", isOn: $toggleValue)
Button("Save") {
storedToken = tokenValue
useGPT4 = toggleValue
@ -30,7 +30,7 @@ struct AuthTokenView: View {
}
}
struct APIKeyView_Previews: PreviewProvider {
struct AuthTokenView_Previews: PreviewProvider {
static var previews: some View {
return AuthTokenView(
storedToken: Binding.constant(nil),

View File

@ -7,6 +7,9 @@ struct CoachView: View {
@State var answer: String
@State var answerSelection = NSRange()
@State var showError = false
@State var errorDescription = ""
init(viewModel: AppViewModel) {
self.viewModel = viewModel
self.answer = viewModel.answer ?? ""
@ -55,6 +58,17 @@ struct CoachView: View {
}
}
}
.onReceive(viewModel.$errorDescription) {
if let error = $0 {
self.showError = true
self.errorDescription = error
}
}
.alert(errorDescription, isPresented: $showError) {
Button("OK", role: .cancel) {
self.showError = false
}
}
HStack {
VStack(alignment: .leading, spacing: 20) {
if let transcript = viewModel.transcript {

View File

@ -6,7 +6,7 @@ struct ContentView: View {
@ViewBuilder
var body: some View {
if viewModel.authToken != nil {
if viewModel.authToken?.isEmpty == false {
VStack(spacing: 16) {
switch viewModel.downloadState {
case .pending:

View File

@ -12,6 +12,12 @@ public struct OpenAI<T: Payload>: Codable {
public let choices: [T]?
public let usage: UsageResult?
public let data: [T]?
public let error: ErrorResult?
}
public struct ErrorResult: Codable, Error {
public let message: String
public let code: String
}
public struct TextResult: Payload {

View File

@ -9,6 +9,15 @@ public enum OpenAIError: Error {
case decodingError(error: Error)
}
extension OpenAIError: LocalizedError {
public var errorDescription: String? {
switch self {
case .genericError(let error), .decodingError(let error):
return error.localizedDescription
}
}
}
public class OpenAISwift {
fileprivate(set) var token: String?
fileprivate let config: Config
@ -132,6 +141,9 @@ extension OpenAISwift {
let res = try JSONDecoder().decode(OpenAI<MessageResult>.self, from: success)
completionHandler(.success(res))
} catch {
if let resp = String(data: success, encoding: .utf8) {
print("Failed to decode response:\n", resp)
}
completionHandler(.failure(.decodingError(error: error)))
}
case .failure(let failure):

View File

@ -2,7 +2,7 @@
Cheetah is an AI-powered macOS app designed to assist users during remote software engineering interviews by providing real-time, discreet coaching and live coding platform integration.
![Quick demo video (1:28)](https://user-images.githubusercontent.com/106342593/229961889-489e2b36-f3e6-453a-9784-f160bc1c4f8d.mp4)
[Quick demo video (1:28)](https://user-images.githubusercontent.com/106342593/229961889-489e2b36-f3e6-453a-9784-f160bc1c4f8d.mp4)
<img src="https://github.com/leetcode-mafia/cheetah/raw/91cc5b89864fe28476a7e2062ede2c8322c17896/cheetah.jpg" alt="Screenshot">
@ -17,6 +17,16 @@ Whisper runs locally on your system, utilizing Georgi Gerganov's [whisper.cpp](h
## Getting started
### Prerequisites
Requires macOS 13.1 or later.
SDL2 must be installed or the app will crash on launch:
```shell
brew install sdl2
```
### Audio driver setup
For the best results, ensure the audio input captures both sides of the conversation.