Skip to content

Commit

Permalink
SpeziLLM in-class Demo (#8)
Browse files Browse the repository at this point in the history
# SpeziLLM in-class Demo

## ♻️ Current situation & Problem
As of now, the CS342 example application doesn't contain any `SpeziLLM`
functionality. For the LLM class demo, we want demonstrate a minimal use
case of SpeziLLM.


## ⚙️ Release Notes 
- Added a minimal demo of SpeziLLM for the CS342 LLM class.


## 📚 Documentation
--


## ✅ Testing
Proper testing on simulator and local device


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/CS342/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/CS342/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
philippzagar authored Jan 26, 2024
1 parent 5993b5d commit 8e25b04
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 25 deletions.
5 changes: 0 additions & 5 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
only_rules:
# All Images that provide context should have an accessibility label. Purely decorative images can be hidden from accessibility.
- accessibility_label_for_image
# Attributes should be on their own lines in functions and types, but on the same line as variables and imports.
- attributes
# Prefer using Array(seq) over seq.map { $0 } to convert a sequence into an Array.
- array_init
# Prefer the new block based KVO API with keypaths when using Swift 3.2 or later.
Expand Down Expand Up @@ -369,9 +367,6 @@ only_rules:
# The variable should be placed on the left, the constant on the right of a comparison operator.
- yoda_condition

attributes:
attributes_with_arguments_always_on_line_above: false

deployment_target: # Availability checks or attributes shouldn’t be using older versions that are satisfied by the deployment target.
iOSApplicationExtension_deployment_target: 16.0
iOS_deployment_target: 16.0
Expand Down
88 changes: 88 additions & 0 deletions ExampleApplication.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@
653A256228338800005D4D48 /* ExampleApplicationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* ExampleApplicationTests.swift */; };
9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */; };
9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */; };
975AF9212B63177500CB02A2 /* SpeziLLM in Frameworks */ = {isa = PBXBuildFile; productRef = 975AF9202B63177500CB02A2 /* SpeziLLM */; };
975AF9232B63177500CB02A2 /* SpeziLLMLocal in Frameworks */ = {isa = PBXBuildFile; productRef = 975AF9222B63177500CB02A2 /* SpeziLLMLocal */; };
975AF9252B63177500CB02A2 /* SpeziLLMLocalDownload in Frameworks */ = {isa = PBXBuildFile; productRef = 975AF9242B63177500CB02A2 /* SpeziLLMLocalDownload */; };
975AF9272B63177500CB02A2 /* SpeziLLMOpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 975AF9262B63177500CB02A2 /* SpeziLLMOpenAI */; };
979EC7732B631A1800B63213 /* LLMInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979EC76C2B631A1800B63213 /* LLMInteraction.swift */; };
979EC7742B631A1800B63213 /* LLMLocalDownloadOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979EC76E2B631A1800B63213 /* LLMLocalDownloadOnboarding.swift */; };
979EC7752B631A1800B63213 /* LLMOpenAIFunctionWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979EC7702B631A1800B63213 /* LLMOpenAIFunctionWeather.swift */; };
979EC7762B631A1800B63213 /* LLMOpenAITokenOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979EC7712B631A1800B63213 /* LLMOpenAITokenOnboarding.swift */; };
979EC7772B631A1800B63213 /* LLMOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979EC7722B631A1800B63213 /* LLMOnboardingView.swift */; };
97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; };
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */; };
A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */; };
Expand Down Expand Up @@ -139,6 +148,11 @@
653A256128338800005D4D48 /* ExampleApplicationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApplicationTests.swift; sourceTree = "<group>"; };
653A256728338800005D4D48 /* ExampleApplicationUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleApplicationUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
979EC76C2B631A1800B63213 /* LLMInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMInteraction.swift; sourceTree = "<group>"; };
979EC76E2B631A1800B63213 /* LLMLocalDownloadOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMLocalDownloadOnboarding.swift; sourceTree = "<group>"; };
979EC7702B631A1800B63213 /* LLMOpenAIFunctionWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMOpenAIFunctionWeather.swift; sourceTree = "<group>"; };
979EC7712B631A1800B63213 /* LLMOpenAITokenOnboarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMOpenAITokenOnboarding.swift; sourceTree = "<group>"; };
979EC7722B631A1800B63213 /* LLMOnboardingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMOnboardingView.swift; sourceTree = "<group>"; };
A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupHeader.swift; sourceTree = "<group>"; };
A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = "<group>"; };
A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = "<group>"; };
Expand All @@ -149,16 +163,20 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
975AF9212B63177500CB02A2 /* SpeziLLM in Frameworks */,
9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */,
2FE5DC6429EDD883004B9AB4 /* SpeziAccount in Frameworks */,
975AF9232B63177500CB02A2 /* SpeziLLMLocal in Frameworks */,
2FB099AF2A875DF100B20952 /* FirebaseAuth in Frameworks */,
97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */,
975AF9272B63177500CB02A2 /* SpeziLLMOpenAI in Frameworks */,
2FE5DC6729EDD894004B9AB4 /* SpeziContact in Frameworks */,
2FE5DC8429EDD934004B9AB4 /* SpeziQuestionnaire in Frameworks */,
2FB099B32A875DF100B20952 /* FirebaseFirestoreSwift in Frameworks */,
5661551D2AB8384200209B80 /* SwiftPackageList in Frameworks */,
2FB099B12A875DF100B20952 /* FirebaseFirestore in Frameworks */,
A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */,
975AF9252B63177500CB02A2 /* SpeziLLMLocalDownload in Frameworks */,
2FB099B62A875E2B00B20952 /* HealthKitOnFHIR in Frameworks */,
2FE5DC8A29EDD972004B9AB4 /* SpeziLocalStorage in Frameworks */,
2FE5DC8C29EDD972004B9AB4 /* SpeziSecureStorage in Frameworks */,
Expand Down Expand Up @@ -321,6 +339,7 @@
2FF53D8C2A8729D600042B76 /* ExampleApplicationStandard.swift */,
2F4E23822989D51F0013F3D9 /* ExampleApplicationTestingSetup.swift */,
2FC975A72978F11A00BA99FE /* Home.swift */,
979EC76B2B631A1800B63213 /* LLMInteraction */,
A9720E412ABB68B300872D23 /* Account */,
2FE5DC2829EDD398004B9AB4 /* Onboarding */,
2FE5DC3B29EDD7D0004B9AB4 /* Schedule */,
Expand Down Expand Up @@ -358,6 +377,34 @@
name = Frameworks;
sourceTree = "<group>";
};
979EC76B2B631A1800B63213 /* LLMInteraction */ = {
isa = PBXGroup;
children = (
979EC76D2B631A1800B63213 /* Local */,
979EC76F2B631A1800B63213 /* OpenAI */,
979EC7722B631A1800B63213 /* LLMOnboardingView.swift */,
979EC76C2B631A1800B63213 /* LLMInteraction.swift */,
);
path = LLMInteraction;
sourceTree = "<group>";
};
979EC76D2B631A1800B63213 /* Local */ = {
isa = PBXGroup;
children = (
979EC76E2B631A1800B63213 /* LLMLocalDownloadOnboarding.swift */,
);
path = Local;
sourceTree = "<group>";
};
979EC76F2B631A1800B63213 /* OpenAI */ = {
isa = PBXGroup;
children = (
979EC7702B631A1800B63213 /* LLMOpenAIFunctionWeather.swift */,
979EC7712B631A1800B63213 /* LLMOpenAITokenOnboarding.swift */,
);
path = OpenAI;
sourceTree = "<group>";
};
A9720E412ABB68B300872D23 /* Account */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -410,6 +457,10 @@
9739A0C52AD7B5730084BEA5 /* FirebaseStorage */,
97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */,
A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */,
975AF9202B63177500CB02A2 /* SpeziLLM */,
975AF9222B63177500CB02A2 /* SpeziLLMLocal */,
975AF9242B63177500CB02A2 /* SpeziLLMLocalDownload */,
975AF9262B63177500CB02A2 /* SpeziLLMOpenAI */,
);
productName = ExampleApplication;
productReference = 653A254D283387FE005D4D48 /* ExampleApplication.app */;
Expand Down Expand Up @@ -504,6 +555,7 @@
2FE750CA2A87240100723EAE /* XCRemoteSwiftPackageReference "SpeziMockWebService" */,
2FB099B42A875E2B00B20952 /* XCRemoteSwiftPackageReference "HealthKitOnFHIR" */,
5661551B2AB8384200209B80 /* XCRemoteSwiftPackageReference "swift-package-list" */,
975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */,
);
productRefGroup = 653A254E283387FE005D4D48 /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -576,17 +628,22 @@
2FE5DCB129EE6107004B9AB4 /* AccountOnboarding.swift in Sources */,
2F4FC8D729EE69D300BFFE26 /* MockUpload.swift in Sources */,
2FE5DC3A29EDD7CA004B9AB4 /* Welcome.swift in Sources */,
979EC7732B631A1800B63213 /* LLMInteraction.swift in Sources */,
2FE5DC3829EDD7CA004B9AB4 /* InterestingModules.swift in Sources */,
979EC7772B631A1800B63213 /* LLMOnboardingView.swift in Sources */,
979EC7752B631A1800B63213 /* LLMOpenAIFunctionWeather.swift in Sources */,
2FE5DC3529EDD7CA004B9AB4 /* Consent.swift in Sources */,
2FE5DC4529EDD7F2004B9AB4 /* Binding+Negate.swift in Sources */,
2FC975A82978F11A00BA99FE /* Home.swift in Sources */,
2FE5DC4E29EDD7FA004B9AB4 /* ScheduleView.swift in Sources */,
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */,
2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */,
979EC7742B631A1800B63213 /* LLMLocalDownloadOnboarding.swift in Sources */,
2F1AC9DF2B4E840E00C24973 /* ExampleApplication.docc in Sources */,
2FF53D8D2A8729D600042B76 /* ExampleApplicationStandard.swift in Sources */,
2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */,
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */,
979EC7762B631A1800B63213 /* LLMOpenAITokenOnboarding.swift in Sources */,
2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */,
2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */,
2FE5DC4F29EDD7FA004B9AB4 /* EventContext.swift in Sources */,
Expand Down Expand Up @@ -704,6 +761,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = TEST;
SWIFT_OBJC_INTEROP_MODE = objcxx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Test;
Expand Down Expand Up @@ -852,6 +910,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OBJC_INTEROP_MODE = objcxx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
Expand Down Expand Up @@ -908,6 +967,7 @@
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OBJC_INTEROP_MODE = objcxx;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
Expand Down Expand Up @@ -1252,6 +1312,14 @@
minimumVersion = 3.0.10;
};
};
975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/StanfordSpezi/SpeziLLM";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 0.6.1;
};
};
97F466E62A76BBEE005DC9B4 /* XCRemoteSwiftPackageReference "SpeziOnboarding" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/StanfordSpezi/SpeziOnboarding";
Expand Down Expand Up @@ -1382,6 +1450,26 @@
package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseStorage;
};
975AF9202B63177500CB02A2 /* SpeziLLM */ = {
isa = XCSwiftPackageProductDependency;
package = 975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */;
productName = SpeziLLM;
};
975AF9222B63177500CB02A2 /* SpeziLLMLocal */ = {
isa = XCSwiftPackageProductDependency;
package = 975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */;
productName = SpeziLLMLocal;
};
975AF9242B63177500CB02A2 /* SpeziLLMLocalDownload */ = {
isa = XCSwiftPackageProductDependency;
package = 975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */;
productName = SpeziLLMLocalDownload;
};
975AF9262B63177500CB02A2 /* SpeziLLMOpenAI */ = {
isa = XCSwiftPackageProductDependency;
package = 975AF91F2B63177400CB02A2 /* XCRemoteSwiftPackageReference "SpeziLLM" */;
productName = SpeziLLMOpenAI;
};
97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */ = {
isa = XCSwiftPackageProductDependency;
package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "5746b2d35c91c50581590ed97abe4c06b5037274",
"version" : "10.18.0"
"revision" : "3e464dad87dad2d29bb29a97836789bf0f8f67d2",
"version" : "10.18.1"
}
},
{
Expand All @@ -32,17 +32,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : {
"revision" : "c60c958e707c50a9cf8bcb7cfd7d51c566d726c5",
"version" : "10.19.1"
"revision" : "b880ec8ec927a838c51c12862c6222c30d7097d7",
"version" : "10.20.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "6b332152355c372ace9966d8ee76ed191f97025e",
"version" : "10.17.0"
"revision" : "ceec9f28dea12b7cf3dabf18b5ed7621c88fd4aa",
"version" : "10.20.0"
}
},
{
Expand Down Expand Up @@ -108,6 +108,15 @@
"version" : "1.22.3"
}
},
{
"identity" : "llama.cpp",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/llama.cpp",
"state" : {
"revision" : "b0611c7d3cb049822f9911878514e4706b80e2ac",
"version" : "0.1.8"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
Expand All @@ -117,6 +126,15 @@
"version" : "2.30909.0"
}
},
{
"identity" : "openai",
"kind" : "remoteSourceControl",
"location" : "https://github.com/MacPaw/OpenAI",
"state" : {
"revision" : "ac5892fd0de8d283362ddc30f8e9f1a0eaba8cc0",
"version" : "0.2.5"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
Expand All @@ -131,17 +149,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/ResearchKit",
"state" : {
"revision" : "cf79a15c7d8c436f98937fe93e72e880dd2f73e4",
"version" : "2.2.20"
"revision" : "209164ed20592a2213c4bd69cefcb078d9de0692",
"version" : "2.2.21"
}
},
{
"identity" : "researchkitonfhir",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/ResearchKitOnFHIR",
"state" : {
"revision" : "7dc09f7acd7fb19673594e0fdd4d72d0869ee006",
"version" : "1.0.0"
"revision" : "ea4d9691591594177e7dfbc8c246324855d73eb5",
"version" : "1.0.1"
}
},
{
Expand All @@ -162,6 +180,15 @@
"version" : "1.1.0"
}
},
{
"identity" : "spezichat",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziChat",
"state" : {
"revision" : "9d45c10bcf859c98f2998ecd4f6a80f31894fe2c",
"version" : "0.1.4"
}
},
{
"identity" : "spezicontact",
"kind" : "remoteSourceControl",
Expand All @@ -185,8 +212,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziFoundation.git",
"state" : {
"revision" : "683c66f922a4cfe0882c4a86a43854f613b48541",
"version" : "1.0.0"
"revision" : "d1e6d4cddcf236038d21a73d671806d8ba51b01c",
"version" : "1.0.1"
}
},
{
Expand All @@ -198,6 +225,15 @@
"version" : "0.5.0"
}
},
{
"identity" : "spezillm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziLLM",
"state" : {
"revision" : "94c1f3bd3eb9412e3321a46730c1572da11bd951",
"version" : "0.6.1"
}
},
{
"identity" : "spezimockwebservice",
"kind" : "remoteSourceControl",
Expand All @@ -212,17 +248,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziOnboarding",
"state" : {
"revision" : "ae7b18a18453557cd95c7adeb8f75846f48c343c",
"version" : "1.0.0"
"revision" : "8fb6d9f1a080661c0cc564a93b82ead3c8d44d4f",
"version" : "1.0.2"
}
},
{
"identity" : "speziquestionnaire",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziQuestionnaire.git",
"state" : {
"revision" : "930a4099db1aca9db0b6ed4e77687141c4780052",
"version" : "1.0.0"
"revision" : "fac0bb02f7027b4c09bd7afdad55eb7b47ec67f3",
"version" : "1.0.1"
}
},
{
Expand All @@ -234,6 +270,15 @@
"version" : "0.8.0"
}
},
{
"identity" : "spezispeech",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziSpeech",
"state" : {
"revision" : "a1e1d021d8f605b5e6b23aee773115d7125a57e3",
"version" : "1.0.0"
}
},
{
"identity" : "spezistorage",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -293,8 +338,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/XCTestExtensions.git",
"state" : {
"revision" : "388a6d6a5be48eff5d98a2c45e0b50f30ed21dc3",
"version" : "0.4.7"
"revision" : "fb7fcee97c574b950e03b0a53874e26db27db2fe",
"version" : "0.4.8"
}
},
{
Expand Down
Loading

0 comments on commit 8e25b04

Please sign in to comment.