diff --git a/.github/workflows/monthly-markdown-link-check.yml b/.github/workflows/monthly-markdown-link-check.yml
new file mode 100644
index 0000000..6c16315
--- /dev/null
+++ b/.github/workflows/monthly-markdown-link-check.yml
@@ -0,0 +1,19 @@
+#
+# This source file is part of the Stanford Spezi open source project
+#
+# SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+#
+# SPDX-License-Identifier: MIT
+#
+
+name: Monthly Markdown Link Check
+
+on:
+ # Runs at midnight on the first of every month
+ schedule:
+ - cron: "0 0 1 * *"
+
+jobs:
+ markdown_link_check:
+ name: Markdown Link Check
+ uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
\ No newline at end of file
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 312842b..18048e6 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -19,3 +19,6 @@ jobs:
swiftlint:
name: SwiftLint
uses: StanfordSpezi/.github/.github/workflows/swiftlint.yml@v2
+ markdown_link_check:
+ name: Markdown Link Check
+ uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
diff --git a/.spi.yml b/.spi.yml
index 8342883..b32a84a 100644
--- a/.spi.yml
+++ b/.spi.yml
@@ -10,3 +10,7 @@ version: 1
builder:
configs:
- platform: ios
+ documentation_targets:
+ - SpeziOpenAI
+ - SpeziSpeechRecognizer
+ - SpeziSpeechSynthesizer
diff --git a/README.md b/README.md
index 8f8cb30..59880de 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
This source file is part of the Stanford Spezi open source project
-SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
SPDX-License-Identifier: MIT
@@ -17,19 +17,144 @@ SPDX-License-Identifier: MIT
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FStanfordSpezi%2FSpeziML%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/StanfordSpezi/SpeziML)
-The Spezi machine learning module simplifies the integration to incorporate machine learning-related functionality in their Spezi-based applications.
+# Overview
-For more information, please refer to the [API documentation](https://swiftpackageindex.com/StanfordSpezi/SpeziML/documentation).
+The Spezi ML Swift Package includes modules that are helpful to integrate ML-related functionality in your application.
+
+# Spezi Open AI
+
+A module that allows you to interact with GPT-based large language models (LLMs) from OpenAI within your Spezi application.
+
+||||
+|:--:|:--:|:--:|
+|`API Key Onboarding`|`Model Selection`|`Chat View`|
+
+
+## Setup
+
+### 1. Add Spezi ML as a Dependency
+
+First, you will need to add the SpeziML Swift package to
+[your app in Xcode](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#) or
+[Swift package](https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode#Add-a-dependency-on-another-Swift-package). When adding the package, select the `SpeziOpenAI` target to add.
+
+### 2. Register the Open AI Component
+
+> [!IMPORTANT]
+> If your application is not yet configured to use Spezi, follow the [Spezi setup article](https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/initial-setup) to set up the core Spezi infrastructure.
+
+You can configure the `OpenAIComponent` in the `SpeziAppDelegate` as follows.
+
+```swift
+import Spezi
+import SpeziOpenAI
-## Spezi ML Modules
+class ExampleDelegate: SpeziAppDelegate {
+ override var configuration: Configuration {
+ Configuration {
+ OpenAIComponent(apiToken: "API_KEY", openAIModel: .gpt4)
+ }
+ }
+}
+```
-The Spezi ML Swift Package contains the following modules that are helpful to integrate ML-related functionality in your application.
+In the example above, we have configured the `OpenAIComponent` to use the GPT-4 model with a default API key. Note that the choice of model and API key are persisted across application launches. The `apiToken` and `openAIModel` can also be accessed and changed at runtime.
-### SpeziML
+The `SpeziOpenAI` package also provides an `OpenAIAPIKeyOnboardingStep` that can be used to allow the user to provide their API key during the onboarding process instead (see `Examples` below). If using the `OpenAIAPIKeyOnboardingStep`, the `apiToken` property can be omitted here.
-A placeholder Swift Package Target.
+> [!NOTE]
+> You can learn more about a [`Component` in the Spezi documentation](https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/component).
+## Examples
+
+### Creating a Chat Interface
+
+In this example, we will create a chat interface that allows the user to converse with the model. Responses from the model will be streamed.
+
+```swift
+import OpenAI
+import SpeziOpenAI
+import SwiftUI
+
+struct OpenAIChatView: View {
+ @EnvironmentObject private var openAIComponent: OpenAIComponent
+ @State private var chat: [Chat]
+
+ var body: some View {
+ ChatView($chat)
+ .onChange(of: chat) { _ in
+ let chatStreamResults = try await openAIComponent.queryAPI(withChat: chat)
+
+ for try await chatStreamResult in chatStreamResults {
+ for choice in chatStreamResult.choices {
+ guard let newContent = choice.delta.content else {
+ continue
+ }
+
+ if chat.last?.role == .assistent, let previousContent = chat.last?.content {
+ chat[chat.count - 1] = Chat(
+ role: .assistant,
+ content: previousContent + newContent
+ )
+ } else {
+ chat.append(Chat(role: .assistent, content: newContent))
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+### Setting the API Key During Onboarding
+
+The `OpenAIAPIKeyOnboardingStep` provides a view that can be used for the user to enter an OpenAI API key during onboarding in your Spezi application. We will show an example of how you can add an OpenAI onboarding step within an application created from the Spezi Template Application below.
+
+First, create a new view to show the onboarding step:
+
+```swift
+import SpeziOnboarding
+import SpeziOpenAI
+import SwiftUI
+
+
+struct OpenAIAPIKey: View {
+ @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath
+
+ var body: some View {
+ OpenAIAPIKeyOnboardingStep {
+ onboardingNavigationPath.nextStep()
+ }
+ }
+}
+```
+
+This view can then be added to the `OnboardingFlow` within the Spezi Template Application:
+
+```swift
+import SpeziOnboarding
+import SpeziOpenAI
+import SwiftUI
+
+
+struct OnboardingFlow: View {
+ @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false
+
+
+ var body: some View {
+ OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) {
+ // ... other steps
+ OpenAIAPIKey()
+ // ... other steps
+ }
+ }
+}
+```
+
+Now the OpenAI API Key entry view will appear within your application's onboarding process. The API Key entered will be persisted across application launches.
+
+For more information, please refer to the [API documentation](https://swiftpackageindex.com/StanfordSpezi/SpeziML/documentation).
## Contributing
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png
new file mode 100644
index 0000000..491658f
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png
new file mode 100644
index 0000000..930190e
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/ChatView~dark.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png
new file mode 100644
index 0000000..3acee1b
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png
new file mode 100644
index 0000000..3725035
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIAPIKeyOnboardingStep~dark.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png
new file mode 100644
index 0000000..7e23604
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png
new file mode 100644
index 0000000..74f4782
Binary files /dev/null and b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png differ
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png.license b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png.license
new file mode 100644
index 0000000..9a52dec
--- /dev/null
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/Resources/OpenAIModelSelectionOnboardingStep~dark.png.license
@@ -0,0 +1,5 @@
+This source file is part of the SpeziML open-source project
+
+SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
+
+SPDX-License-Identifier: MIT
\ No newline at end of file
diff --git a/Sources/SpeziOpenAI/SpeziOpenAI.docc/SpeziOpenAI.md b/Sources/SpeziOpenAI/SpeziOpenAI.docc/SpeziOpenAI.md
index dab97ab..3ddc682 100644
--- a/Sources/SpeziOpenAI/SpeziOpenAI.docc/SpeziOpenAI.md
+++ b/Sources/SpeziOpenAI/SpeziOpenAI.docc/SpeziOpenAI.md
@@ -4,45 +4,156 @@
#
# This source file is part of the Stanford Spezi open source project
#
-# SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
+# SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
#
# SPDX-License-Identifier: MIT
#
-->
-Module to interact with the OpenAI API to interact with GPT-based large language models (LLMs).
+## Overview
+
+A module that allows you to interact with GPT-based large language models (LLMs) from OpenAI within your Spezi application.
+
+@Row {
+ @Column {
+ @Image(source: "OpenAIAPIKeyOnboardingStep", alt: "Screenshot displaying the OpenAI API Key Onboarding view from Spezi OpenAI") {
+ ``OpenAIAPIKeyOnboardingStep``
+ }
+ }
+ @Column {
+ @Image(source: "OpenAIModelSelectionOnboardingStep", alt: "Screenshot displaying the Open AI Model Selection Onboarding Step"){
+ ``OpenAIModelSelectionOnboardingStep``
+ }
+ }
+ @Column {
+ @Image(source: "ChatView", alt: "Screenshot displaying the Chat View."){
+ ``ChatView``
+ }
+ }
+}
+
+## Setup
+
+### 1. Add Spezi ML as a Dependency
+
+First, you will need to add the SpeziML Swift package to
+[your app in Xcode](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app#) or
+[Swift package](https://developer.apple.com/documentation/xcode/creating-a-standalone-swift-package-with-xcode#Add-a-dependency-on-another-Swift-package). When adding the package, select the `SpeziOpenAI` target to add.
+
+### 2. Register the Open AI Component
+
+> If your application is not yet configured to use Spezi, follow the [Spezi setup article](https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/initial-setup) to set up the core Spezi infrastructure.
+
+You can configure the `OpenAIComponent` in the `SpeziAppDelegate` as follows.
+
+```swift
+import Spezi
+import SpeziOpenAI
-## Configuration
-```
class ExampleDelegate: SpeziAppDelegate {
override var configuration: Configuration {
Configuration {
- OpenAIComponent()
- // ...
+ OpenAIComponent(apiToken: "API_KEY", openAIModel: .gpt4)
}
}
}
```
-You can provide a default API token or model configuration to the OpenAI component's ``OpenAIComponent/init(apiToken:openAIModel:)`` initializer in the configuration.
-The choice of model and the API key are persisted across application launches.
+In the example above, we have configured the `OpenAIComponent` to use the GPT-4 model with a default API key. Note that the choice of model and API key are persisted across application launches. The `apiToken` and `openAIModel` can also be accessed and changed at runtime.
+
+The `SpeziOpenAI` package also provides an `OpenAIAPIKeyOnboardingStep` that can be used to allow the user to provide their API key during the onboarding process instead (see `Examples` below). If using the `OpenAIAPIKeyOnboardingStep`, the `apiToken` property can be omitted here.
+
+> You can learn more about a [`Component` in the Spezi documentation](https://swiftpackageindex.com/stanfordspezi/spezi/documentation/spezi/component).
+
+## Examples
+
+### Creating a Chat Interface
+
+In this example, we will create a chat interface that allows the user to converse with the model. Responses from the model will be streamed.
+
+```swift
+import OpenAI
+import SpeziOpenAI
+import SwiftUI
+
+struct OpenAIChatView: View {
+ @EnvironmentObject private var openAIComponent: OpenAIComponent
+ @State private var chat: [Chat]
+
+ var body: some View {
+ ChatView($chat)
+ .onChange(of: chat) { _ in
+ let chatStreamResults = try await openAIComponent.queryAPI(withChat: chat)
+
+ for try await chatStreamResult in chatStreamResults {
+ for choice in chatStreamResult.choices {
+ guard let newContent = choice.delta.content else {
+ continue
+ }
+
+ if chat.last?.role == .assistent, let previousContent = chat.last?.content {
+ chat[chat.count - 1] = Chat(
+ role: .assistant,
+ content: previousContent + newContent
+ )
+ } else {
+ chat.append(Chat(role: .assistent, content: newContent))
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+### Setting the API Key During Onboarding
+
+The `OpenAIAPIKeyOnboardingStep` provides a view that can be used for the user to enter an OpenAI API key during onboarding in your Spezi application. We will show an example of how you can add an OpenAI onboarding step within an application created from the Spezi Template Application below.
+First, create a new view to show the onboarding step:
-## Usage
+```swift
+import SpeziOnboarding
+import SpeziOpenAI
+import SwiftUI
-The ``OpenAIComponent`` can subsequentially be used in a SwiftUI View using the environment dependency injection mechanism.
+struct OpenAIAPIKey: View {
+ @EnvironmentObject private var onboardingNavigationPath: OnboardingNavigationPath
+
+ var body: some View {
+ OpenAIAPIKeyOnboardingStep {
+ onboardingNavigationPath.nextStep()
+ }
+ }
+}
```
-struct ExampleOpenAIView: View {
- @EnvironmentObject var openAI: OpenAIComponent* ... */>
- // ...
+This view can then be added to the `OnboardingFlow` within the Spezi Template Application:
+
+```swift
+import SpeziOnboarding
+import SpeziOpenAI
+import SwiftUI
+
+
+struct OnboardingFlow: View {
+ @AppStorage(StorageKeys.onboardingFlowComplete) var completedOnboardingFlow = false
+
+
+ var body: some View {
+ OnboardingStack(onboardingFlowComplete: $completedOnboardingFlow) {
+ // ... other steps
+ OpenAIAPIKey()
+ // ... other steps
+ }
+ }
}
```
-The ``OpenAIComponent``'s ``OpenAIComponent/apiToken`` and ``OpenAIComponent/openAIModel`` can be accessed and changed at runtime.
-The ``OpenAIComponent/queryAPI(withChat:)`` function allows the interaction with the GPT-based OpenAI models.
+Now the OpenAI API Key entry view will appear within your application's onboarding process. The API Key entered will be persisted across application launches.
+
## Types