Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
AI Reward Cards (#1129)
Browse files Browse the repository at this point in the history
- OpenAI integration and developer settings
- Gated behind a flag (default off)

I introduced a dependency on a keychain interfacing library, I was
tempted to avoid it but the direct interface to keychain immediately
made me understand why everyone is using a library.

AI features can be enabled from the Developer Settings menu: 
<img width="383" alt="image"
src="https://github.com/subconsciousnetwork/subconscious/assets/5009316/82a54ea2-bf01-46bd-8af8-e389d2a675a2">



https://github.com/subconsciousnetwork/subconscious/assets/5009316/2868e602-2bcb-4c8e-bb1a-5c3ab0804847
  • Loading branch information
bfollington authored Mar 27, 2024
1 parent 3260c01 commit c5761ec
Show file tree
Hide file tree
Showing 18 changed files with 768 additions and 97 deletions.
94 changes: 92 additions & 2 deletions xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ enum AppAction: Hashable {
case appear

case setAppUpgraded(_ isUpgraded: Bool)

/// Set sphere/user nickname
/// Sets form field, and persists if needed.
case setNickname(_ nickname: String)
Expand Down Expand Up @@ -169,6 +169,10 @@ enum AppAction: Hashable {
/// Set and persist experimental block editor enabled
case persistBlockEditorEnabled(Bool)
case persistNoosphereLogLevel(Noosphere.NoosphereLogLevel)
case persistAiFeaturesEnabled(Bool)
case persistPreferredLlm(String)
case loadOpenAIKey(OpenAIKey)
case persistOpenAIKey(OpenAIKey)

/// Reset Noosphere Service.
/// This calls `Noosphere.reset` which resets memoized instances of
Expand Down Expand Up @@ -580,6 +584,9 @@ struct AppModel: ModelProtocol {
/// Is experimental block editor enabled?
var isBlockEditorEnabled = false
var noosphereLogLevel: Noosphere.NoosphereLogLevel = .basic
var areAiFeaturesEnabled = false
var openAiApiKey = OpenAIKey(key: "sk-")
var preferredLlm: String = AppDefaults.standard.preferredLlm

/// Should recovery mode be presented?
var isRecoveryModePresented = false
Expand Down Expand Up @@ -911,6 +918,30 @@ struct AppModel: ModelProtocol {
environment: environment,
isBlockEditorEnabled: isBlockEditorEnabled
)
case let .persistAiFeaturesEnabled(areAiFeaturesEnabled):
return persistAiFeaturesEnabled(
state: state,
environment: environment,
areAiFeaturesEnabled: areAiFeaturesEnabled
)
case let .loadOpenAIKey(key):
return loadOpenAIKey(
state: state,
environment: environment,
key: key
)
case let .persistOpenAIKey(key):
return persistOpenAIKey(
state: state,
environment: environment,
key: key
)
case let .persistPreferredLlm(llm):
return persistPreferredLlm(
state: state,
environment: environment,
llm: llm
)
case let .persistNoosphereLogLevel(level):
return persistNoosphereLogLevel(
state: state,
Expand Down Expand Up @@ -1442,6 +1473,13 @@ struct AppModel: ModelProtocol {
model.selectedAppTab = AppTab(rawValue: AppDefaults.standard.selectedAppTab) ?? state.selectedAppTab
model.noosphereLogLevel = Noosphere.NoosphereLogLevel(description: AppDefaults.standard.noosphereLogLevel)
model.isBlockEditorEnabled = AppDefaults.standard.isBlockEditorEnabled
model.areAiFeaturesEnabled = AppDefaults.standard.areAiFeaturesEnabled
model.preferredLlm = AppDefaults.standard.preferredLlm

let fx: Fx<AppAction> = Future.detached {
let key = await environment.keychainService.getApiKey() ?? ""
return .loadOpenAIKey(OpenAIKey(key: key))
}.eraseToAnyPublisher()

// Update model from app defaults
return update(
Expand All @@ -1459,7 +1497,7 @@ struct AppModel: ModelProtocol {
)
],
environment: environment
)
).mergeFx(fx)
}

/// Handle scene phase change
Expand Down Expand Up @@ -1822,6 +1860,53 @@ struct AppModel: ModelProtocol {
return Update(state: model)
}

static func persistAiFeaturesEnabled(
state: AppModel,
environment: AppEnvironment,
areAiFeaturesEnabled: Bool
) -> Update<AppModel> {
// Persist value
AppDefaults.standard.areAiFeaturesEnabled = areAiFeaturesEnabled
var model = state
model.areAiFeaturesEnabled = areAiFeaturesEnabled
return Update(state: model)
}

static func persistPreferredLlm(
state: AppModel,
environment: AppEnvironment,
llm: String
) -> Update<AppModel> {
AppDefaults.standard.preferredLlm = llm
var model = state
model.preferredLlm = llm
return Update(state: model)
}

static func loadOpenAIKey(
state: AppModel,
environment: AppEnvironment,
key: OpenAIKey
) -> Update<AppModel> {
var model = state
model.openAiApiKey = key

return Update(state: model)
}

static func persistOpenAIKey(
state: AppModel,
environment: AppEnvironment,
key: OpenAIKey
) -> Update<AppModel> {
let fx: Fx<AppAction> = Future.detached {
await environment.keychainService.setApiKey(key.key)
return .loadOpenAIKey(key)
}.eraseToAnyPublisher()

return Update(state: state).mergeFx(fx)
}

static func persistNoosphereLogLevel(
state: AppModel,
environment: AppEnvironment,
Expand Down Expand Up @@ -3321,6 +3406,9 @@ struct AppEnvironment {

var gatewayProvisioningService: GatewayProvisioningService

var openAiService: OpenAIService
var keychainService: KeychainService

var pasteboard = UIPasteboard.general

/// Service for generating creative prompts and oblique strategies
Expand Down Expand Up @@ -3427,6 +3515,8 @@ struct AppEnvironment {
userProfile: userProfile
)

self.keychainService = KeychainService()
self.openAiService = OpenAIService(keychain: self.keychainService)
}
}

4 changes: 2 additions & 2 deletions xcode/Subconscious/Shared/Components/Deck/CardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum CardType: Equatable, Hashable {
author: UserProfile,
related: Set<EntryStub>
)
case action(_ message: String)
case reward(_ message: String)
case prompt(
message: String,
entry: EntryStub,
Expand Down Expand Up @@ -59,7 +59,7 @@ extension CardModel {

func update(entry: EntryStub, liked: Bool) -> Self {
switch card {
case .action(_):
case .reward(_):
return self
case let .entry(_, author, related):
return CardModel(
Expand Down
5 changes: 2 additions & 3 deletions xcode/Subconscious/Shared/Components/Deck/CardStackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ enum CardStackNotification {
case swipeLeft(CardModel)
case swipeStarted
case swipeAbandoned
case cardDetailRequested(CardModel)

case entry(EntryNotification)
}
Expand Down Expand Up @@ -227,9 +228,7 @@ struct CardStack: View {
CardGestureModifier(
offsets: $offsets,
onTapped: {
if let entry = card.entry {
notify(.entry(.requestDetail(entry)))
}
notify(.cardDetailRequested(card))
},
onSwipeStart: { notify(.swipeStarted) },
onSwipeChanged: { translation in
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ struct CardContentView: View {
// Opacity allows blendMode to show through
.foregroundStyle(.primary.opacity(0.8))
.accentColor(highlight)
.padding(DeckTheme.cardPadding)
.frame(height: DeckTheme.cardContentSize.height, alignment: .topLeading)
.truncateWithGradient(maxHeight: DeckTheme.cardContentSize.height)
.padding(DeckTheme.cardPadding)

Spacer()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ struct CardView: View {
related: related,
notify: notify
)
case let .action(message):
ActionCardView(message: message)
case let .reward(message):
RewardCardView(message: message)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// RewardCardView.swift
// Subconscious (iOS)
//
// Created by Ben Follington on 27/12/2023.
//

import Foundation
import SwiftUI

struct RewardCardView: View {
var message: String

var body: some View {
VStack {
Text(message)
.font(.title)
.italic()
}
.padding(DeckTheme.cardPadding)
.frame(
minWidth: DeckTheme.cardSize.width,
minHeight: DeckTheme.cardSize.height
)
.foregroundStyle(message.themeColor.toHighlightColor())
.background(message.themeColor.toColor())
.cornerRadius(DeckTheme.cornerRadius)
.transition(.push(from: .bottom))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ struct DeckNavigationView: View {
store.send(.cardPickedUp)
case .swipeAbandoned:
store.send(.cardReleased)
case .cardDetailRequested(let card):
store.send(.cardDetailRequested(card))
case let .entry(entryNotification):
switch entryNotification {
case let .requestDetail(entry):
store.send(.cardDetailRequested(entry))
case let .requestLinkDetail(link):
store.send(
.detailStack(.findAndPushLinkDetail(link))
Expand All @@ -45,7 +45,7 @@ struct DeckNavigationView: View {
store.send(.requestUpdateLikeStatus(address, liked: true))
case let .unlike(address):
store.send(.requestUpdateLikeStatus(address, liked: false))
case .delete:
case .delete, .requestDetail:
break
}
}
Expand Down Expand Up @@ -110,6 +110,8 @@ struct DeckNavigationView: View {
)
.offset(y: -AppTheme.unit * 8)

MiniCardStackView(cards: store.state.buffer)

case .notFound:
HStack {
Spacer()
Expand Down
4 changes: 2 additions & 2 deletions xcode/Subconscious/Shared/Components/Deck/DeckTheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ extension DeckTheme {

static let cardPadding = AppTheme.unit * 5
static let cornerRadius: CGFloat = 32.0
static let cardSize = CGSize(width: 374, height: 380)
static let cardContentSize = CGSize(width: 374, height: AppTheme.unit * 70)
static let cardSize = CGSize(width: 350, height: 380)
static let cardContentSize = CGSize(width: 374, height: AppTheme.unit * 80)

static let cardShadow = Color(red: 0.19, green: 0.09, blue: 0.33)

Expand Down
Loading

0 comments on commit c5761ec

Please sign in to comment.