Skip to content

Commit

Permalink
Add Model Selection to Onboarding Views (#23)
Browse files Browse the repository at this point in the history
# Add Model Selection to Onboarding Views

## ♻️ Current situation & Problem
- The onboarding view that displays the model selection currently does not allow the modification of the model selection beyond the two provided models.
- Fix bug that no OpenAI functions could be called due to a misused function calling parameter.

## 💡 Proposed solution
- Add a customizable initializer to provide more models that can be selected from.


### Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
PSchmiedmayer authored Aug 15, 2023
1 parent b980fc0 commit 700ab5e
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 24 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
name: Build and Test UI Tests
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
xcodeversion: latest
artifactname: TestApp.xcresult
runsonlabels: '["macOS", "self-hosted"]'
path: 'Tests/UITests'
Expand Down
6 changes: 2 additions & 4 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ only_rules:
- implicitly_unwrapped_optional
# Identifiers should use inclusive language that avoids discrimination against groups of people based on race, gender, or socioeconomic status
- inclusive_language
# If defer is at the end of its parent scope, it will be executed right where it is anyway.
- inert_defer
# Prefer using Set.isDisjoint(with:) over Set.intersection(_:).isEmpty.
- is_disjoint
# Discouraged explicit usage of the default separator.
Expand Down Expand Up @@ -329,8 +327,6 @@ only_rules:
- unowned_variable_capture
# Catch statements should not declare error variables without type casting.
- untyped_error_in_catch
# Unused reference in a capture list should be removed.
- unused_capture_list
# Unused parameter in a closure should be replaced with _.
- unused_closure_parameter
# Unused control flow label should be removed.
Expand Down Expand Up @@ -381,6 +377,8 @@ attributes:
excluded: # paths to ignore during linting. Takes precedence over `included`.
- .build
- .swiftpm
- .derivedData
- Tests/UITests/.derivedData

closure_body_length: # Closure bodies should not span too many lines.
- 35 # warning - default: 20
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ let package = Package(
.library(name: "SpeziOpenAI", targets: ["SpeziOpenAI"])
],
dependencies: [
.package(url: "https://github.com/MacPaw/OpenAI", .upToNextMinor(from: "0.2.2")),
.package(url: "https://github.com/MacPaw/OpenAI", .upToNextMinor(from: "0.2.3")),
.package(url: "https://github.com/StanfordSpezi/Spezi", .upToNextMinor(from: "0.7.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziStorage", .upToNextMinor(from: "0.4.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziOnboarding", .upToNextMinor(from: "0.3.0"))
.package(url: "https://github.com/StanfordSpezi/SpeziOnboarding", .upToNextMinor(from: "0.4.2"))
],
targets: [
.target(
Expand Down
20 changes: 17 additions & 3 deletions Sources/SpeziOpenAI/OpenAIAPIKeyOnboardingStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,27 @@ public struct OpenAIAPIKeyOnboardingStep: View {


/// - Parameters:
/// - actionText: Text that should appear on the action button.
/// - actionText: Localized text that should appear on the action button.
/// - action: Action that should be performed after the openAI API key has been persisted.
public init(
actionText: String? = nil,
actionText: LocalizedStringResource? = nil,
_ action: @escaping () -> Void
) {
self.actionText = actionText ?? String(localized: "OPENAI_API_KEY_SAVE_BUTTON", bundle: .module)
self.init(
actionText: actionText?.localizedString() ?? String(localized: "OPENAI_API_KEY_SAVE_BUTTON", bundle: .module),
action
)
}

/// - Parameters:
/// - actionText: Text that should appear on the action button without localization.
/// - action: Action that should be performed after the openAI API key has been persisted.
@_disfavoredOverload
public init<ActionText: StringProtocol>(
actionText: ActionText,
_ action: @escaping () -> Void
) {
self.actionText = String(actionText)
self.action = action
}
}
10 changes: 7 additions & 3 deletions Sources/SpeziOpenAI/OpenAIComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class OpenAIComponent: Component, ObservableObject, ObservableObjectProvi
@AppStorage(OpenAIConstants.modelStorageKey) public var openAIModel: Model = .gpt3_5Turbo
private var defaultAPIToken: String?


/// The API token used to interact with the OpenAI API
public var apiToken: String? {
get {
Expand Down Expand Up @@ -66,9 +67,10 @@ public class OpenAIComponent: Component, ObservableObject, ObservableObjectProvi


/// Queries the OpenAI API using the provided messages.
///
///
/// - Parameters:
/// - messages: A collection of chat messages used in the conversation.
/// - chat: A collection of chat messages used in the conversation.
/// - chatFunctionDeclaration: OpenAI functions that should be injected in the OpenAI query.
///
/// - Returns: The content of the response from the API.
public func queryAPI(
Expand All @@ -78,9 +80,11 @@ public class OpenAIComponent: Component, ObservableObject, ObservableObjectProvi
guard let apiToken, !apiToken.isEmpty else {
throw OpenAIError.noAPIToken
}

let functions = chatFunctionDeclaration.isEmpty ? nil : chatFunctionDeclaration

let openAIClient = OpenAI(apiToken: apiToken)
let query = ChatQuery(model: openAIModel, messages: chat, functions: chatFunctionDeclaration)
let query = ChatQuery(model: openAIModel, messages: chat, functions: functions)
return openAIClient.chatsStream(query: query)
}
}
54 changes: 44 additions & 10 deletions Sources/SpeziOpenAI/OpenAIModelSelectionOnboardingStep.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,40 @@ import SwiftUI

/// View to display an onboarding step for the user to enter change the OpenAI model.
public struct OpenAIModelSelectionOnboardingStep: View {
public enum Default {
public static let models = [Model.gpt3_5Turbo, Model.gpt4]
}

fileprivate struct ModelSelection: Identifiable {
fileprivate let id: String


fileprivate var description: String {
id.replacing("-", with: " ").capitalized.replacing("Gpt", with: "GPT")
}
}


@EnvironmentObject private var openAI: OpenAIComponent
private let actionText: String
private let action: () -> Void
private let models: [ModelSelection]


public var body: some View {
OnboardingView(
titleView: {
OnboardingTitleView(
title: String(localized: "OPENAI_MODEL_SELECTION_TITLE", bundle: .module),
subtitle: String(localized: "OPENAI_MODEL_SELECTION_SUBTITLE", bundle: .module)
title: LocalizedStringResource("OPENAI_MODEL_SELECTION_TITLE", bundle: .atURL(from: .module)),
subtitle: LocalizedStringResource("OPENAI_MODEL_SELECTION_SUBTITLE", bundle: .atURL(from: .module))
)
},
contentView: {
Picker(String(localized: "OPENAI_MODEL_SELECTION_DESCRIPTION", bundle: .module), selection: $openAI.openAIModel) {
Text("GPT 3.5 Turbo")
.tag(Model.gpt3_5Turbo)
Text("GPT 4")
.tag(Model.gpt4)
ForEach(models) { model in
Text(model.description)
.tag(model.id)
}
}
.pickerStyle(.wheel)
.accessibilityIdentifier("modelPicker")
Expand All @@ -48,15 +63,34 @@ public struct OpenAIModelSelectionOnboardingStep: View {
)
}


/// - Parameters:
/// - actionText: Text that should appear on the action button.
/// - actionText: Localized text that should appear on the action button.
/// - models: The models that should be displayed in the picker user interface.
/// - action: Action that should be performed after the openAI model selection has been persisted.
public init(
actionText: String? = nil,
actionText: LocalizedStringResource? = nil,
models: [Model] = Default.models,
_ action: @escaping () -> Void
) {
self.init(
actionText: actionText?.localizedString() ?? String(localized: "OPENAI_MODEL_SELECTION_SAVE_BUTTON", bundle: .module),
models: models,
action
)
}

/// - Parameters:
/// - actionText: Text that should appear on the action button without localization.
/// - models: The models that should be displayed in the picker user interface.
/// - action: Action that should be performed after the openAI model selection has been persisted.
@_disfavoredOverload
public init<ActionText: StringProtocol>(
actionText: ActionText,
models: [Model] = Default.models,
_ action: @escaping () -> Void
) {
self.actionText = actionText ?? String(localized: "OPENAI_MODEL_SELECTION_SAVE_BUTTON", bundle: .module)
self.actionText = String(actionText)
self.models = models.map { ModelSelection(id: $0) }
self.action = action
}
}
2 changes: 1 addition & 1 deletion Sources/SpeziOpenAI/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@

// MARK: OpenAIModelSelectionOnboardingStep
"OPENAI_MODEL_SELECTION_TITLE" = "Select an OpenAI Model";
"OPENAI_MODEL_SELECTION_SUBTITLE" = "If you have access to GPT4, you may select it below. Otherwise select GPT 3.5 Turbo.";
"OPENAI_MODEL_SELECTION_SUBTITLE" = "Select the OpenAI model that you want to use. Ensure that your API key has proper access to the model you select.";
"OPENAI_MODEL_SELECTION_DESCRIPTION" = "OpenAI GPT Model";
"OPENAI_MODEL_SELECTION_SAVE_BUTTON" = "Next";
2 changes: 1 addition & 1 deletion Tests/UITests/UITests.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n export PATH=\"$PATH:/opt/homebrew/bin\"\n if which swiftlint > /dev/null; then\n swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n";
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n export PATH=\"$PATH:/opt/homebrew/bin\"\n if which swiftlint > /dev/null; then\n cd ../.. && swiftlint\n else\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\n fi\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down

0 comments on commit 700ab5e

Please sign in to comment.