Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions macos/Onit/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class AppState: NSObject, SPUUpdaterDelegate {
var showProLimitAlert: Bool = false
var subscriptionPlanError: String = ""

var showAddModelAlert: Bool = false

private var authCancellable: AnyCancellable? = nil

// MARK: - Initializer
Expand Down
15 changes: 15 additions & 0 deletions macos/Onit/Assets.xcassets/Icons/plus-thin.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "plus-thin.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions macos/Onit/Assets.xcassets/Icons/user.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "user.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
3 changes: 3 additions & 0 deletions macos/Onit/Assets.xcassets/Icons/user.imageset/user.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions macos/Onit/UI/Alerts/AddModelAlert.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// AddModelAlert.swift
// Onit
//
// Created by Loyd Kim on 8/4/25.
//

import Defaults
import SwiftUI

struct AddModelAlert: View {
@Environment(\.appState) var appState
@Environment(\.openSettings) var openSettings

var body: some View {
SubscriptionAlert(
title: "Add a model to continue",
description: "Connect at least one remote or local model in Settings to chat with Onit.",
spacingBetweenSections: 10,
showApiCta: false,
footerSupportingText: "Or, sign up for free access to 30+ models from OpenAI, Anthropic and more!"
) {
HStack(alignment: .center, spacing: 8) {
ctaButton(
text: "Add in Settings",
background: .gray500,
hoverBackground: .gray400
) {
appState.settingsTab = .models
openSettings()
}

ctaButton(
text: "Sign up",
background: .blue400,
hoverBackground: .blue350
) {
Defaults[.authFlowStatus] = .showSignUp
}
}
.padding(.top, 4)
}
}

// MARK: Child Components

private func ctaButton(
text: String,
background: Color,
hoverBackground: Color,
action: @escaping () -> Void
) -> some View {
TextButton(
fillContainer: false,
cornerRadius: 6,
background: background,
hoverBackground: hoverBackground
) {
Text(text)
.frame(maxWidth: .infinity)
.styleText(size: 13, weight: .regular, align: .center)
Comment thread
lk340 marked this conversation as resolved.
} action: {
action()
}
}
}
53 changes: 30 additions & 23 deletions macos/Onit/UI/Alerts/SubscriptionAlert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import SwiftUI

struct SubscriptionAlert: View {
struct SubscriptionAlert<Child: View>: View {
@Environment(\.appState) var appState
@Environment(\.openSettings) var openSettings

Expand All @@ -18,11 +18,14 @@ struct SubscriptionAlert: View {
private let descriptionAction: (() -> Void)?
private let descriptionActionLoading: Bool
private let caption: String?
private let spacingBetweenSections: CGFloat
private let showApiCta: Bool
private let subscriptionText: String?
private let subscriptionAction: (() -> Void)?
private let showSubscriptionPerks: Bool
private let footerSupportingText: String?
private let errorMessage: Binding<String>?
@ViewBuilder private let child: () -> Child

init(
title: String,
Expand All @@ -32,11 +35,14 @@ struct SubscriptionAlert: View {
descriptionAction: (() -> Void)? = nil,
descriptionActionLoading: Bool = false,
caption: String? = nil,
spacingBetweenSections: CGFloat = 16,
showApiCta: Bool = true,
subscriptionText: String? = nil,
subscriptionAction: (() -> Void)? = nil,
showSubscriptionPerks: Bool = false,
footerSupportingText: String? = nil,
errorMessage: Binding<String>? = nil
errorMessage: Binding<String>? = nil,
@ViewBuilder child: @escaping () -> Child = { EmptyView() }
) {
self.title = title
self.close = close
Expand All @@ -45,16 +51,19 @@ struct SubscriptionAlert: View {
self.descriptionAction = descriptionAction
self.descriptionActionLoading = descriptionActionLoading
self.caption = caption
self.spacingBetweenSections = spacingBetweenSections
self.showApiCta = showApiCta
self.subscriptionText = subscriptionText
self.subscriptionAction = subscriptionAction
self.showSubscriptionPerks = showSubscriptionPerks
self.footerSupportingText = footerSupportingText
self.errorMessage = errorMessage
self.child = child
}

var body: some View {
HStack(alignment: .center) {
VStack(alignment: .center, spacing: 16) {
VStack(alignment: .center, spacing: spacingBetweenSections) {
if let errorMessage = errorMessage,
!errorMessage.wrappedValue.isEmpty
{
Expand All @@ -80,7 +89,7 @@ struct SubscriptionAlert: View {
}

if let caption = caption {
Text(caption).styleText(size: 13, weight: .regular)
captionText(caption)
}
}

Expand All @@ -93,6 +102,8 @@ struct SubscriptionAlert: View {
}

footer

child()
}
.padding(16)
.background(.gray900)
Expand Down Expand Up @@ -138,13 +149,7 @@ extension SubscriptionAlert {
}

private var descriptionText: some View {
Text(description)
.styleText(
size: 13,
weight: .regular,
color: .gray100,
align: .center
)
captionText(description)
}

private func descriptionButton(_ action: @escaping () -> Void) -> some View {
Expand Down Expand Up @@ -179,28 +184,30 @@ extension SubscriptionAlert {
)
}

private func footerTextView(_ text: String) -> some View {
Text(text).styleText(size: 11, color: .gray200)
private func captionText(_ text: String) -> some View {
Text(text).styleText(size: 13, weight: .regular, color: .gray100, align: .center)
}

private var footer: some View {
VStack(alignment: .center, spacing: 16) {
VStack(alignment: .center, spacing: spacingBetweenSections) {
footerDivider

VStack(alignment: .center, spacing: 4) {
if let footerSupportingText = footerSupportingText {
footerTextView(footerSupportingText)
captionText(footerSupportingText)
}

HStack(spacing: 4) {
footerTextView("Or, add your API key in")

Button {
openModelSettings()
} label: {
Text("Settings").styleText(size: 11, color: .gray100)
if showApiCta {
HStack(spacing: 4) {
captionText("Or, add your API key in")

Button {
openModelSettings()
} label: {
Text("Settings").styleText(size: 13, weight: .regular, color: .primary, align: .center)
}
.buttonStyle(PlainButtonStyle())
}
.buttonStyle(PlainButtonStyle())
}
}
}
Expand Down
Loading