From 5a1af0ebbbe1fcbc0b489657864f99c960c1dcf2 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:20:22 +0200 Subject: [PATCH 01/16] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4715cc8..930f695 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ Your generous donations help sustain and improve this project. Here's why suppor Every donation, no matter how small, makes a big difference. Thank you for considering supporting us!

Buy Me A Coffee +--- +# Note +The content of this README.md file has been automatically generated using SwiftyGPT. + --- # License SwiftyGPT is published under the MIT license. From bc07e9db8145e02758850d66d3283152f2c63045 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 20:33:34 +0200 Subject: [PATCH 02/16] build: add empty explorer project --- Explorer/.gitignore | 17 + Explorer/Explorer.xcodeproj/project.pbxproj | 349 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Explorer/Assets.xcassets/Contents.json | 6 + Explorer/Explorer/ContentView.swift | 24 ++ Explorer/Explorer/ExplorerApp.swift | 17 + 9 files changed, 452 insertions(+) create mode 100644 Explorer/.gitignore create mode 100644 Explorer/Explorer.xcodeproj/project.pbxproj create mode 100644 Explorer/Explorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Explorer/Explorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Explorer/Explorer/Assets.xcassets/Contents.json create mode 100644 Explorer/Explorer/ContentView.swift create mode 100644 Explorer/Explorer/ExplorerApp.swift diff --git a/Explorer/.gitignore b/Explorer/.gitignore new file mode 100644 index 0000000..466ae51 --- /dev/null +++ b/Explorer/.gitignore @@ -0,0 +1,17 @@ +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Swift Package Manager +Packages/ +Package.pins +Package.resolved +.swiftpm +.build/ \ No newline at end of file diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj new file mode 100644 index 0000000..90df9bd --- /dev/null +++ b/Explorer/Explorer.xcodeproj/project.pbxproj @@ -0,0 +1,349 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */; }; + C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3F2BDD7B980076E6E8 /* ContentView.swift */; }; + C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + C1122F3A2BDD7B980076E6E8 /* Explorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Explorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerApp.swift; sourceTree = ""; }; + C1122F3F2BDD7B980076E6E8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C1122F372BDD7B980076E6E8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C1122F312BDD7B980076E6E8 = { + isa = PBXGroup; + children = ( + C1122F3C2BDD7B980076E6E8 /* Explorer */, + C1122F3B2BDD7B980076E6E8 /* Products */, + ); + sourceTree = ""; + }; + C1122F3B2BDD7B980076E6E8 /* Products */ = { + isa = PBXGroup; + children = ( + C1122F3A2BDD7B980076E6E8 /* Explorer.app */, + ); + name = Products; + sourceTree = ""; + }; + C1122F3C2BDD7B980076E6E8 /* Explorer */ = { + isa = PBXGroup; + children = ( + C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */, + C1122F3F2BDD7B980076E6E8 /* ContentView.swift */, + C1122F412BDD7B990076E6E8 /* Assets.xcassets */, + ); + path = Explorer; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C1122F392BDD7B980076E6E8 /* Explorer */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1122F482BDD7B990076E6E8 /* Build configuration list for PBXNativeTarget "Explorer" */; + buildPhases = ( + C1122F362BDD7B980076E6E8 /* Sources */, + C1122F372BDD7B980076E6E8 /* Frameworks */, + C1122F382BDD7B980076E6E8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Explorer; + productName = Explorer; + productReference = C1122F3A2BDD7B980076E6E8 /* Explorer.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C1122F322BDD7B980076E6E8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1530; + LastUpgradeCheck = 1530; + TargetAttributes = { + C1122F392BDD7B980076E6E8 = { + CreatedOnToolsVersion = 15.3; + }; + }; + }; + buildConfigurationList = C1122F352BDD7B980076E6E8 /* Build configuration list for PBXProject "Explorer" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C1122F312BDD7B980076E6E8; + productRefGroup = C1122F3B2BDD7B980076E6E8 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C1122F392BDD7B980076E6E8 /* Explorer */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C1122F382BDD7B980076E6E8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C1122F362BDD7B980076E6E8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */, + C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C1122F462BDD7B990076E6E8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C1122F472BDD7B990076E6E8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C1122F492BDD7B990076E6E8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = 749349ZYWA; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = SwiftyGPT; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = it.antoniowar.swiftygpt.explorer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + C1122F4A2BDD7B990076E6E8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = 749349ZYWA; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = SwiftyGPT; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 0.1.0; + PRODUCT_BUNDLE_IDENTIFIER = it.antoniowar.swiftygpt.explorer; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C1122F352BDD7B980076E6E8 /* Build configuration list for PBXProject "Explorer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1122F462BDD7B990076E6E8 /* Debug */, + C1122F472BDD7B990076E6E8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1122F482BDD7B990076E6E8 /* Build configuration list for PBXNativeTarget "Explorer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1122F492BDD7B990076E6E8 /* Debug */, + C1122F4A2BDD7B990076E6E8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C1122F322BDD7B980076E6E8 /* Project object */; +} diff --git a/Explorer/Explorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Explorer/Explorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Explorer/Explorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Explorer/Explorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Explorer/Explorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Explorer/Explorer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json b/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json b/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..13613e3 --- /dev/null +++ b/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Explorer/Explorer/Assets.xcassets/Contents.json b/Explorer/Explorer/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Explorer/Explorer/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Explorer/Explorer/ContentView.swift b/Explorer/Explorer/ContentView.swift new file mode 100644 index 0000000..7ee7f78 --- /dev/null +++ b/Explorer/Explorer/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// Explorer +// +// Created by Antonio Guerra on 27/04/24. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/Explorer/Explorer/ExplorerApp.swift b/Explorer/Explorer/ExplorerApp.swift new file mode 100644 index 0000000..b606f96 --- /dev/null +++ b/Explorer/Explorer/ExplorerApp.swift @@ -0,0 +1,17 @@ +// +// ExplorerApp.swift +// Explorer +// +// Created by Antonio Guerra on 27/04/24. +// + +import SwiftUI + +@main +struct ExplorerApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} From 20a35a318c69069b207e7e4b70ebb68ae7a80fdc Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 21:13:29 +0200 Subject: [PATCH 03/16] feat: update package and explorer structure --- Explorer/Explorer.xcodeproj/project.pbxproj | 32 ++++++++++++++++-- .../AccentColor.colorset/Contents.json | 9 +++++ .../AppIcon.appiconset/Contents.json | 1 + .../AppIcon.appiconset/swifty-gpt-logo.png | Bin 0 -> 27663 bytes Explorer/Package.swift | 4 +++ Package.swift | 2 +- 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/swifty-gpt-logo.png create mode 100644 Explorer/Package.swift diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj index 90df9bd..8845d97 100644 --- a/Explorer/Explorer.xcodeproj/project.pbxproj +++ b/Explorer/Explorer.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */; }; C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3F2BDD7B980076E6E8 /* ContentView.swift */; }; C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; }; + C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */ = {isa = PBXBuildFile; productRef = C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -17,6 +18,7 @@ C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerApp.swift; sourceTree = ""; }; C1122F3F2BDD7B980076E6E8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + C188BCC92BDD84AB0057B93E /* SwiftyGPT */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyGPT; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -24,6 +26,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -33,6 +36,7 @@ C1122F312BDD7B980076E6E8 = { isa = PBXGroup; children = ( + C188BCC92BDD84AB0057B93E /* SwiftyGPT */, C1122F3C2BDD7B980076E6E8 /* Explorer */, C1122F3B2BDD7B980076E6E8 /* Products */, ); @@ -72,6 +76,9 @@ dependencies = ( ); name = Explorer; + packageProductDependencies = ( + C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */, + ); productName = Explorer; productReference = C1122F3A2BDD7B980076E6E8 /* Explorer.app */; productType = "com.apple.product-type.application"; @@ -100,6 +107,9 @@ Base, ); mainGroup = C1122F312BDD7B980076E6E8; + packageReferences = ( + C188BCC62BDD847F0057B93E /* XCRemoteSwiftPackageReference "SwiftyGPT" */, + ); productRefGroup = C1122F3B2BDD7B980076E6E8 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -275,7 +285,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 0.1.0; - PRODUCT_BUNDLE_IDENTIFIER = it.antoniowar.swiftygpt.explorer; + PRODUCT_BUNDLE_IDENTIFIER = com.swiftygpt.explorer; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -310,7 +320,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 0.1.0; - PRODUCT_BUNDLE_IDENTIFIER = it.antoniowar.swiftygpt.explorer; + PRODUCT_BUNDLE_IDENTIFIER = com.swiftygpt.explorer; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -344,6 +354,24 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C188BCC62BDD847F0057B93E /* XCRemoteSwiftPackageReference "SwiftyGPT" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/antonio-war/SwiftyGPT"; + requirement = { + branch = develop; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */ = { + isa = XCSwiftPackageProductDependency; + productName = SwiftyGPTChat; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = C1122F322BDD7B980076E6E8 /* Project object */; } diff --git a/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json b/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..7fbb105 100644 --- a/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Explorer/Explorer/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.341", + "green" : "0.169", + "red" : "0.302" + } + }, "idiom" : "universal" } ], diff --git a/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json b/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3..69a1965 100644 --- a/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "swifty-gpt-logo.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/swifty-gpt-logo.png b/Explorer/Explorer/Assets.xcassets/AppIcon.appiconset/swifty-gpt-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..26f7f62f9e91e6067b7a6c98dc2ea8d6a444c6da GIT binary patch literal 27663 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 UPBAOaV*&(cj7$xSP7?3^FKR$O82|tP literal 0 HcmV?d00001 diff --git a/Explorer/Package.swift b/Explorer/Package.swift new file mode 100644 index 0000000..f293613 --- /dev/null +++ b/Explorer/Package.swift @@ -0,0 +1,4 @@ +// swift-tools-version: 5.10 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription diff --git a/Package.swift b/Package.swift index 6f30ac2..d400b9e 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( ], products: [ .library( - name: "SwiftyGPT", + name: "SwiftyGPTChat", targets: [ "SwiftyGPTChat", ] From 1168d14d152270887f7a8731c8812cb95c109b69 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sat, 27 Apr 2024 21:15:55 +0200 Subject: [PATCH 04/16] feat: add open ai api key to environment variables --- .../xcschemes/SwiftyGPTChat.xcscheme | 67 +++++++++++++++ .../xcshareddata/xcschemes/Explorer.xcscheme | 85 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme create mode 100644 Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme new file mode 100644 index 0000000..e58709d --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftyGPTChat.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme b/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme new file mode 100644 index 0000000..d65bd87 --- /dev/null +++ b/Explorer/Explorer.xcodeproj/xcshareddata/xcschemes/Explorer.xcscheme @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 74014352a496d36e2b0652fcb5feba5f2c874fd2 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 28 Apr 2024 11:56:03 +0200 Subject: [PATCH 05/16] fix: update all object scopes --- Explorer/Explorer.xcodeproj/project.pbxproj | 24 +++++-- Explorer/Explorer/Chat/Views/ChatView.swift | 70 +++++++++++++++++++ Explorer/Explorer/ContentView.swift | 24 ------- Explorer/Explorer/ExplorerApp.swift | 24 ++++++- .../Managers/SwiftyGPTChatManager.swift | 8 +-- .../Models/SwiftyGPTChatMessage.swift | 44 ++++++------ .../Models/SwiftyGPTChatRequestBody.swift | 41 ++++++----- .../Models/SwiftyGPTChatResponse.swift | 2 +- .../Models/SwiftyGPTChatResponseBody.swift | 16 ++++- .../Models/SwiftyGPTChatResponseChoice.swift | 22 +++++- .../Models/SwiftyGPTChatResponseError.swift | 11 ++- .../Models/SwiftyGPTChatResponseFormat.swift | 4 +- .../SwiftyGPTChatResponseTokenUsage.swift | 12 +++- .../Services/SwiftyGPTChatMockService.swift | 14 ++-- .../SwiftyGPTChatNetworkingService.swift | 16 ++--- .../Services/SwiftyGPTChatService.swift | 2 +- 16 files changed, 229 insertions(+), 105 deletions(-) create mode 100644 Explorer/Explorer/Chat/Views/ChatView.swift delete mode 100644 Explorer/Explorer/ContentView.swift diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj index 8845d97..4be0ff1 100644 --- a/Explorer/Explorer.xcodeproj/project.pbxproj +++ b/Explorer/Explorer.xcodeproj/project.pbxproj @@ -8,16 +8,16 @@ /* Begin PBXBuildFile section */ C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */; }; - C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1122F3F2BDD7B980076E6E8 /* ContentView.swift */; }; C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; }; + C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13399A32BDE461600BAF2B8 /* ChatView.swift */; }; C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */ = {isa = PBXBuildFile; productRef = C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ C1122F3A2BDD7B980076E6E8 /* Explorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Explorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorerApp.swift; sourceTree = ""; }; - C1122F3F2BDD7B980076E6E8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + C13399A32BDE461600BAF2B8 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; C188BCC92BDD84AB0057B93E /* SwiftyGPT */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyGPT; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ @@ -54,12 +54,28 @@ isa = PBXGroup; children = ( C1122F3D2BDD7B980076E6E8 /* ExplorerApp.swift */, - C1122F3F2BDD7B980076E6E8 /* ContentView.swift */, + C13399A52BDE496F00BAF2B8 /* Chat */, C1122F412BDD7B990076E6E8 /* Assets.xcassets */, ); path = Explorer; sourceTree = ""; }; + C13399A52BDE496F00BAF2B8 /* Chat */ = { + isa = PBXGroup; + children = ( + C13399A62BDE497700BAF2B8 /* Views */, + ); + path = Chat; + sourceTree = ""; + }; + C13399A62BDE497700BAF2B8 /* Views */ = { + isa = PBXGroup; + children = ( + C13399A32BDE461600BAF2B8 /* ChatView.swift */, + ); + path = Views; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -135,7 +151,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1122F402BDD7B980076E6E8 /* ContentView.swift in Sources */, + C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */, C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Explorer/Explorer/Chat/Views/ChatView.swift b/Explorer/Explorer/Chat/Views/ChatView.swift new file mode 100644 index 0000000..8d8a19e --- /dev/null +++ b/Explorer/Explorer/Chat/Views/ChatView.swift @@ -0,0 +1,70 @@ +// +// ChatView.swift +// Explorer +// +// Created by Antonio Guerra on 28/04/24. +// + +import SwiftUI +import SwiftyGPTChat + +struct ChatView: View { + private let environment: ExplorerApp.Environment + @State var typedMessage: String + + init(environment: ExplorerApp.Environment, typedMessage: String = "") { + self.environment = environment + self.typedMessage = typedMessage + } + + var body: some View { + VStack(spacing: .zero) { + Spacer() + typingView + } + } + + private var typingView: some View { + HStack { + TextField("Send a message...", text: $typedMessage) + .textFieldStyle(.roundedBorder) + Button(action: {}) { + Image(systemName: "paperplane") + .padding(5) + .foregroundStyle(Color.white) + .background(Color.accentColor) + .clipShape(Circle()) + } + .disabled(typedMessage.isEmpty) + } + .padding() + } + + private var manager: SwiftyGPTChatManager { + return SwiftyGPTChatManager(service: service) + } + + private var service: any SwiftyGPTChatService { + switch environment { + case .release, .debug: + return SwiftyGPTChatNetworkingService(apiKey: apiKey) + case .test, .preview: + let choices = [ + SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop) + ] + let responseBody = SwiftyGPTChatResponseSuccessBody(id: UUID().uuidString, choices: choices, created: Date().timeIntervalSince1970, model: .gpt3_5_turbo, fingerprint: UUID().uuidString, object: .completion, usage: SwiftyGPTChatResponseTokenUsage(completion: 3, prompt: 3, total: 3)) + return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.1) + } + } + + private var apiKey: String { + guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else { + fatalError("Missing OpenAI API key. Please set the OPEN_AI_API_KEY environment variable.") + } + return apiKey + } +} + +#Preview { + ChatView(environment: .preview) +} diff --git a/Explorer/Explorer/ContentView.swift b/Explorer/Explorer/ContentView.swift deleted file mode 100644 index 7ee7f78..0000000 --- a/Explorer/Explorer/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// Explorer -// -// Created by Antonio Guerra on 27/04/24. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/Explorer/Explorer/ExplorerApp.swift b/Explorer/Explorer/ExplorerApp.swift index b606f96..aca167e 100644 --- a/Explorer/Explorer/ExplorerApp.swift +++ b/Explorer/Explorer/ExplorerApp.swift @@ -6,12 +6,34 @@ // import SwiftUI +import SwiftyGPTChat @main struct ExplorerApp: App { var body: some Scene { WindowGroup { - ContentView() + ChatView(environment: environment) } } + + private var environment: Environment { + #if DEBUG + if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil { + return Environment.test + } else if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { + return Environment.preview + } else { + return Environment.debug + } + #else + return Environment.release + #endif + } + + enum Environment { + case release + case debug + case test + case preview + } } diff --git a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift index d2f6404..36d6533 100644 --- a/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift +++ b/Sources/SwiftyGPTChat/Managers/SwiftyGPTChatManager.swift @@ -7,14 +7,14 @@ import Foundation -struct SwiftyGPTChatManager { - private let service: SwiftyGPTChatService +public struct SwiftyGPTChatManager { + public let service: SwiftyGPTChatService - init(service: SwiftyGPTChatService) { + public init(service: SwiftyGPTChatService) { self.service = service } - func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logitBias: [Int: Int]? = nil, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { + public func send(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = 0, logitBias: [Int: Int]? = nil, logprobs: Bool? = false, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = 1, presencePenalty: Double? = 0, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = 1, topP: Double? = 1, user: String? = nil) async throws -> SwiftyGPTChatResponse { let requestBody = SwiftyGPTChatRequestBody(messages: messages, model: model, frequencyPenalty: frequencyPenalty, logitBias: logitBias, logprobs: logprobs, topLogprobs: topLogprobs, maxTokens: maxTokens, n: n, presencePenalty: presencePenalty, responseFormat: responseFormat, seed: seed, temperature: temperature, topP: topP, user: user) let responseBody = try await service.request(body: requestBody) return try SwiftyGPTChatResponse(body: responseBody) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 4968661..1561d49 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,7 +7,7 @@ import Foundation -protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable { +public protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable { var role: SwiftyGPTChatRole { get } } @@ -41,23 +41,23 @@ public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { - let role: SwiftyGPTChatRole = .user - let content: String - let name: String? +public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { + public let role: SwiftyGPTChatRole = .user + public let content: String + public let name: String? - init(content: String, name: String? = nil) { + public init(content: String, name: String? = nil) { self.content = content self.name = name } - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.content = try container.decode(String.self, forKey: .content) self.name = try container.decodeIfPresent(String.self, forKey: .name) } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(role, forKey: .role) try container.encode(content, forKey: .content) @@ -71,25 +71,25 @@ struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { - let role: SwiftyGPTChatRole = .assistant +public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { + public let role: SwiftyGPTChatRole = .assistant // TODO: content will be optional once tool_calls parameter will be supported - let content: String - let name: String? + public let content: String + public let name: String? // TODO: add tool_calls parameter support - init(content: String, name: String? = nil) { + public init(content: String, name: String? = nil) { self.content = content self.name = name } - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.content = try container.decode(String.self, forKey: .content) self.name = try container.decodeIfPresent(String.self, forKey: .name) } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.role, forKey: .role) try container.encode(self.content, forKey: .content) @@ -103,21 +103,21 @@ struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { } } -struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { - let role: SwiftyGPTChatRole = .tool - let content: String +public struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { + public let role: SwiftyGPTChatRole = .tool + public let content: String // TODO: add tool_call_id - init(content: String) { + public init(content: String) { self.content = content } - init(from decoder: any Decoder) throws { + public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.content = try container.decode(String.self, forKey: .content) } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(self.role, forKey: .role) try container.encode(self.content, forKey: .content) @@ -134,7 +134,7 @@ enum SwiftyGPTChatCodableMessage: Equatable, Encodable, Decodable { case user(SwiftyGPTChatUserMessage) case assistant(SwiftyGPTChatAssistantMessage) case tool(SwiftyGPTChatToolMessage) - + init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let singleContainer = try decoder.singleValueContainer() diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift index 9959b5f..20e94a4 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatRequestBody.swift @@ -7,27 +7,27 @@ import Foundation -struct SwiftyGPTChatRequestBody: Encodable { - let messages: [any SwiftyGPTChatMessage] - let model: SwiftyGPTChatModel - let frequencyPenalty: Double? - let logitBias: [Int: Int]? - let logprobs: Bool? - let topLogprobs: Int? - let maxTokens: Int? - let n: Int? - let presencePenalty: Double? - let responseFormat: SwiftyGPTChatResponseFormat? - let seed: Int? +public struct SwiftyGPTChatRequestBody: Encodable { + public let messages: [any SwiftyGPTChatMessage] + public let model: SwiftyGPTChatModel + public let frequencyPenalty: Double? + public let logitBias: [Int: Int]? + public let logprobs: Bool? + public let topLogprobs: Int? + public let maxTokens: Int? + public let n: Int? + public let presencePenalty: Double? + public let responseFormat: SwiftyGPTChatResponseFormat? + public let seed: Int? // TODO: add stop parameter support // TODO: add stream parameter support - let temperature: Double? - let topP: Double? + public let temperature: Double? + public let topP: Double? // TODO: add tools parameter support // TODO: add tool_choice parameter support - let user: String? + public let user: String? - init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { + public init(messages: [any SwiftyGPTChatMessage], model: SwiftyGPTChatModel, frequencyPenalty: Double? = nil, logitBias: [Int: Int]? = nil, logprobs: Bool? = nil, topLogprobs: Int? = nil, maxTokens: Int? = nil, n: Int? = nil, presencePenalty: Double? = nil, responseFormat: SwiftyGPTChatResponseFormat? = nil, seed: Int? = nil, temperature: Double? = nil, topP: Double? = nil, user: String? = nil) { self.messages = messages self.model = model self.frequencyPenalty = frequencyPenalty @@ -45,10 +45,8 @@ struct SwiftyGPTChatRequestBody: Encodable { } var codableMessages: [SwiftyGPTChatCodableMessage] { - return messages.compactMap { + return messages.map { switch $0 { - case let message as SwiftyGPTChatSystemMessage: - return .system(message) case let message as SwiftyGPTChatUserMessage: return .user(message) case let message as SwiftyGPTChatAssistantMessage: @@ -56,12 +54,13 @@ struct SwiftyGPTChatRequestBody: Encodable { case let message as SwiftyGPTChatToolMessage: return .tool(message) default: - return .none + let message = $0 as! SwiftyGPTChatSystemMessage + return .system(message) } } } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(model, forKey: .model) try container.encode(codableMessages, forKey: .messages) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift index 2b55db6..7b78997 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponse.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatResponse: Equatable { +public enum SwiftyGPTChatResponse: Equatable { case success(body: SwiftyGPTChatResponseSuccessBody) case failure(body: SwiftyGPTChatResponseFailureBody) diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift index edc26ba..eaa87c0 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseBody.swift @@ -18,6 +18,16 @@ public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Ident public let object: SwiftyGPTChatResponseObject public let usage: SwiftyGPTChatResponseTokenUsage + public init(id: String, choices: [SwiftyGPTChatResponseChoice], created: TimeInterval, model: SwiftyGPTChatModel, fingerprint: String, object: SwiftyGPTChatResponseObject, usage: SwiftyGPTChatResponseTokenUsage) { + self.id = id + self.choices = choices + self.created = created + self.model = model + self.fingerprint = fingerprint + self.object = object + self.usage = usage + } + enum CodingKeys: String, CodingKey { case id case choices @@ -30,5 +40,9 @@ public struct SwiftyGPTChatResponseSuccessBody: SwiftyGPTChatResponseBody, Ident } public struct SwiftyGPTChatResponseFailureBody: SwiftyGPTChatResponseBody, Decodable { - let error: SwiftyGPTChatResponseError + public let error: SwiftyGPTChatResponseError + + public init(error: SwiftyGPTChatResponseError) { + self.error = error + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift index 1236b6e..213aa28 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseChoice.swift @@ -8,11 +8,11 @@ import Foundation public struct SwiftyGPTChatResponseChoice: Decodable, Equatable { - let index: Int + public let index: Int let codableMessage: SwiftyGPTChatCodableMessage - let finishReason: SwiftyGPTChatResponseFinishReason + public let finishReason: SwiftyGPTChatResponseFinishReason - var message: any SwiftyGPTChatMessage { + public var message: any SwiftyGPTChatMessage { switch codableMessage { case .system(let message): return message @@ -25,6 +25,22 @@ public struct SwiftyGPTChatResponseChoice: Decodable, Equatable { } } + public init(index: Int, message: any SwiftyGPTChatMessage, finishReason: SwiftyGPTChatResponseFinishReason) { + self.index = index + switch message { + case let message as SwiftyGPTChatUserMessage: + self.codableMessage = .user(message) + case let message as SwiftyGPTChatAssistantMessage: + self.codableMessage = .assistant(message) + case let message as SwiftyGPTChatToolMessage: + self.codableMessage = .tool(message) + default: + let message = message as! SwiftyGPTChatSystemMessage + self.codableMessage = .system(message) + } + self.finishReason = finishReason + } + init(index: Int, codableMessage: SwiftyGPTChatCodableMessage, finishReason: SwiftyGPTChatResponseFinishReason) { self.index = index self.codableMessage = codableMessage diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift index 611ce8e..31e072d 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift @@ -7,7 +7,12 @@ import Foundation -struct SwiftyGPTChatResponseError: Decodable, Equatable { - let type: String - let message: String +public struct SwiftyGPTChatResponseError: Decodable, Equatable { + public let type: String + public let message: String + + public init(type: String, message: String) { + self.type = type + self.message = message + } } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift index 3bd1a8d..d9d28be 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseFormat.swift @@ -7,7 +7,7 @@ import Foundation -enum SwiftyGPTChatResponseFormat: String, Encodable { +public enum SwiftyGPTChatResponseFormat: String, Encodable { case text = "text" case json = "json_object" @@ -15,7 +15,7 @@ enum SwiftyGPTChatResponseFormat: String, Encodable { case type } - func encode(to encoder: any Encoder) throws { + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(rawValue, forKey: .type) } diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift index 09c0ed6..2542c25 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseTokenUsage.swift @@ -8,9 +8,15 @@ import Foundation public struct SwiftyGPTChatResponseTokenUsage: Decodable, Equatable { - let completion: Int - let prompt: Int - let total: Int + public let completion: Int + public let prompt: Int + public let total: Int + + public init(completion: Int, prompt: Int, total: Int) { + self.completion = completion + self.prompt = prompt + self.total = total + } enum CodingKeys: String, CodingKey { case completion = "completion_tokens" diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift index 1a27c91..826c556 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -7,21 +7,21 @@ import Foundation -class SwiftyGPTChatMockService: SwiftyGPTChatService { - private let responseBody: any SwiftyGPTChatResponseBody - private let duration: TimeInterval +public class SwiftyGPTChatMockService: SwiftyGPTChatService { + let responseBody: any SwiftyGPTChatResponseBody + let duration: TimeInterval - private (set) var requestCallCount: Int = 0 - var requestCalled: Bool { + private (set) public var requestCallCount: Int = 0 + public var requestCalled: Bool { return requestCallCount > 0 } - init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) { + public init(responseBody: any SwiftyGPTChatResponseBody, duration: TimeInterval = 0.0) { self.responseBody = responseBody self.duration = duration } - func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { + public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000) requestCallCount += 1 return responseBody diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift index e3f57b8..6c87814 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatNetworkingService.swift @@ -8,14 +8,14 @@ import Foundation import SwiftyGPTNetworking -struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { - let client: SwiftyGPTNetworkingClient - let encoder: JSONEncoder - let decoder: JSONDecoder - let apiKey: String - let organization: String? +public struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { + public let client: SwiftyGPTNetworkingClient + public let encoder: JSONEncoder + public let decoder: JSONDecoder + public let apiKey: String + public let organization: String? - init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) { + public init(client: SwiftyGPTNetworkingClient = SwiftyGPTNetworkingClient(session: URLSession.shared), encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder(), apiKey: String, organization: String? = nil) { self.client = client self.encoder = encoder self.decoder = decoder @@ -23,7 +23,7 @@ struct SwiftyGPTChatNetworkingService: SwiftyGPTChatService { self.organization = organization } - func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { + public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { let body = try JSONEncoder().encode(body) let request = SwiftyGPTChatRequest(apiKey: apiKey, organization: organization, body: body) let response = try await client.send(request: request) diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift index 8989689..12bb1fd 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatService.swift @@ -7,6 +7,6 @@ import Foundation -protocol SwiftyGPTChatService { +public protocol SwiftyGPTChatService { func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody } From 26f8d9b0098db20555da15daa64fff1a48496ec1 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 28 Apr 2024 12:59:21 +0200 Subject: [PATCH 06/16] refactor: make SwiftyGPTChatMessage identifiable --- Explorer/Explorer.xcodeproj/project.pbxproj | 14 ++-- .../Explorer/Chat/{Views => }/ChatView.swift | 49 +++++++++++--- Explorer/Explorer/Chat/MessageView.swift | 65 +++++++++++++++++++ .../Models/SwiftyGPTChatMessage.swift | 7 +- .../Models/SwiftyGPTChatResponseError.swift | 2 +- .../Services/SwiftyGPTChatMockService.swift | 2 +- 6 files changed, 117 insertions(+), 22 deletions(-) rename Explorer/Explorer/Chat/{Views => }/ChatView.swift (56%) create mode 100644 Explorer/Explorer/Chat/MessageView.swift diff --git a/Explorer/Explorer.xcodeproj/project.pbxproj b/Explorer/Explorer.xcodeproj/project.pbxproj index 4be0ff1..8723bbb 100644 --- a/Explorer/Explorer.xcodeproj/project.pbxproj +++ b/Explorer/Explorer.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ C1122F422BDD7B990076E6E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1122F412BDD7B990076E6E8 /* Assets.xcassets */; }; C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13399A32BDE461600BAF2B8 /* ChatView.swift */; }; C188BCCB2BDD84BB0057B93E /* SwiftyGPTChat in Frameworks */ = {isa = PBXBuildFile; productRef = C188BCCA2BDD84BB0057B93E /* SwiftyGPTChat */; }; + C1FB23572BDE5664008F1C5F /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB23562BDE5664008F1C5F /* MessageView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -19,6 +20,7 @@ C1122F412BDD7B990076E6E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; C13399A32BDE461600BAF2B8 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; C188BCC92BDD84AB0057B93E /* SwiftyGPT */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SwiftyGPT; path = ..; sourceTree = ""; }; + C1FB23562BDE5664008F1C5F /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -61,19 +63,12 @@ sourceTree = ""; }; C13399A52BDE496F00BAF2B8 /* Chat */ = { - isa = PBXGroup; - children = ( - C13399A62BDE497700BAF2B8 /* Views */, - ); - path = Chat; - sourceTree = ""; - }; - C13399A62BDE497700BAF2B8 /* Views */ = { isa = PBXGroup; children = ( C13399A32BDE461600BAF2B8 /* ChatView.swift */, + C1FB23562BDE5664008F1C5F /* MessageView.swift */, ); - path = Views; + path = Chat; sourceTree = ""; }; /* End PBXGroup section */ @@ -151,6 +146,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1FB23572BDE5664008F1C5F /* MessageView.swift in Sources */, C13399A42BDE461600BAF2B8 /* ChatView.swift in Sources */, C1122F3E2BDD7B980076E6E8 /* ExplorerApp.swift in Sources */, ); diff --git a/Explorer/Explorer/Chat/Views/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift similarity index 56% rename from Explorer/Explorer/Chat/Views/ChatView.swift rename to Explorer/Explorer/Chat/ChatView.swift index 8d8a19e..ec0a584 100644 --- a/Explorer/Explorer/Chat/Views/ChatView.swift +++ b/Explorer/Explorer/Chat/ChatView.swift @@ -10,25 +10,31 @@ import SwiftyGPTChat struct ChatView: View { private let environment: ExplorerApp.Environment - @State var typedMessage: String + @State private var messages: [any SwiftyGPTChatMessage] + @State private var typedMessage: String - init(environment: ExplorerApp.Environment, typedMessage: String = "") { + init(environment: ExplorerApp.Environment, messages: [any SwiftyGPTChatMessage] = [], typedMessage: String = "") { self.environment = environment + self.messages = messages self.typedMessage = typedMessage } var body: some View { - VStack(spacing: .zero) { - Spacer() - typingView + ScrollView { + LazyVStack(spacing: .zero) { + ForEach(messages, id: \.id) { message in + MessageView(message: message) + } + } } + typingView } private var typingView: some View { HStack { TextField("Send a message...", text: $typedMessage) .textFieldStyle(.roundedBorder) - Button(action: {}) { + Button(action: sendMessage ) { Image(systemName: "paperplane") .padding(5) .foregroundStyle(Color.white) @@ -46,14 +52,14 @@ struct ChatView: View { private var service: any SwiftyGPTChatService { switch environment { - case .release, .debug: - return SwiftyGPTChatNetworkingService(apiKey: apiKey) - case .test, .preview: +// case .release, .debug: +// return SwiftyGPTChatNetworkingService(apiKey: apiKey) + case .release, .debug, .test, .preview: let choices = [ SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop) ] let responseBody = SwiftyGPTChatResponseSuccessBody(id: UUID().uuidString, choices: choices, created: Date().timeIntervalSince1970, model: .gpt3_5_turbo, fingerprint: UUID().uuidString, object: .completion, usage: SwiftyGPTChatResponseTokenUsage(completion: 3, prompt: 3, total: 3)) - return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.1) + return SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.3) } } @@ -63,6 +69,29 @@ struct ChatView: View { } return apiKey } + + private func sendMessage() { + let sentMessage = SwiftyGPTChatUserMessage(content: typedMessage) + messages.append(sentMessage) + typedMessage = "" + Task { + do { + let response = try await manager.send(messages: [sentMessage], model: .gpt3_5_turbo) + switch response { + case .success(let body): + if let receivedMessage = body.choices.first?.message { + messages.append(receivedMessage) + } else { + throw URLError(.badServerResponse) + } + case .failure(let body): + throw body.error + } + } catch { + print(error) + } + } + } } #Preview { diff --git a/Explorer/Explorer/Chat/MessageView.swift b/Explorer/Explorer/Chat/MessageView.swift new file mode 100644 index 0000000..6c3d771 --- /dev/null +++ b/Explorer/Explorer/Chat/MessageView.swift @@ -0,0 +1,65 @@ +// +// MessageView.swift +// Explorer +// +// Created by Antonio Guerra on 28/04/24. +// + +import SwiftUI +import SwiftyGPTChat + +struct MessageView: View { + private var message: any SwiftyGPTChatMessage + + init(message: any SwiftyGPTChatMessage) { + self.message = message + } + + var body: some View { + switch message { + case let message as SwiftyGPTChatUserMessage: + sentMessageView(content: message.content) + case let message as SwiftyGPTChatAssistantMessage: + receivedMessageView(content: message.content) + case let message as SwiftyGPTChatToolMessage: + receivedMessageView(content: message.content) + default: + let message = message as! SwiftyGPTChatSystemMessage + receivedMessageView(content: message.content) + } + } + + private func sentMessageView(content: String) -> some View { + HStack(alignment: .bottom, spacing: 10) { + Spacer() + Text(content) + .padding(10) + .foregroundColor(Color.white) + .background(Color.accentColor) + .cornerRadius(10) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + + private func receivedMessageView(content: String) -> some View { + HStack(alignment: .bottom, spacing: 10) { + Text(content) + .padding(10) + .foregroundColor(Color.black) + .background(Color(UIColor.systemGray6)) + .cornerRadius(10) + Spacer() + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } +} + +#Preview("Sent message") { + MessageView(message: SwiftyGPTChatUserMessage(content: "Hello, how are you ?")) +} + +#Preview("Received message") { + MessageView(message: SwiftyGPTChatSystemMessage(content: "Hello! I'm fine and you ?")) +} diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift index 1561d49..87eb0d0 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatMessage.swift @@ -7,11 +7,13 @@ import Foundation -public protocol SwiftyGPTChatMessage: Equatable, Encodable, Decodable { +public protocol SwiftyGPTChatMessage: Identifiable, Equatable, Encodable, Decodable, Hashable { + var id: UUID { get } var role: SwiftyGPTChatRole { get } } public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { + public let id: UUID = UUID() public let role: SwiftyGPTChatRole = .system public let content: String public let name: String? @@ -42,6 +44,7 @@ public struct SwiftyGPTChatSystemMessage: SwiftyGPTChatMessage { } public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { + public let id: UUID = UUID() public let role: SwiftyGPTChatRole = .user public let content: String public let name: String? @@ -72,6 +75,7 @@ public struct SwiftyGPTChatUserMessage: SwiftyGPTChatMessage { } public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { + public let id: UUID = UUID() public let role: SwiftyGPTChatRole = .assistant // TODO: content will be optional once tool_calls parameter will be supported public let content: String @@ -104,6 +108,7 @@ public struct SwiftyGPTChatAssistantMessage: SwiftyGPTChatMessage { } public struct SwiftyGPTChatToolMessage: SwiftyGPTChatMessage { + public let id: UUID = UUID() public let role: SwiftyGPTChatRole = .tool public let content: String // TODO: add tool_call_id diff --git a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift index 31e072d..83a6845 100644 --- a/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift +++ b/Sources/SwiftyGPTChat/Models/SwiftyGPTChatResponseError.swift @@ -7,7 +7,7 @@ import Foundation -public struct SwiftyGPTChatResponseError: Decodable, Equatable { +public struct SwiftyGPTChatResponseError: Decodable, Equatable, Error { public let type: String public let message: String diff --git a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift index 826c556..c2a431d 100644 --- a/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift +++ b/Sources/SwiftyGPTChat/Services/SwiftyGPTChatMockService.swift @@ -22,7 +22,7 @@ public class SwiftyGPTChatMockService: SwiftyGPTChatService { } public func request(body: SwiftyGPTChatRequestBody) async throws -> any SwiftyGPTChatResponseBody { - try await Task.sleep(nanoseconds: UInt64(duration) * 1_000_000_000) + try await Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000)) requestCallCount += 1 return responseBody } From 4acb947aa8af12ace6d55f5a256f5cb3a0ca1276 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 28 Apr 2024 13:00:01 +0200 Subject: [PATCH 07/16] fix: remove unwanted comment --- Explorer/Explorer/Chat/ChatView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Explorer/Explorer/Chat/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift index ec0a584..af47176 100644 --- a/Explorer/Explorer/Chat/ChatView.swift +++ b/Explorer/Explorer/Chat/ChatView.swift @@ -52,9 +52,9 @@ struct ChatView: View { private var service: any SwiftyGPTChatService { switch environment { -// case .release, .debug: -// return SwiftyGPTChatNetworkingService(apiKey: apiKey) - case .release, .debug, .test, .preview: + case .release, .debug: + return SwiftyGPTChatNetworkingService(apiKey: apiKey) + case .test, .preview: let choices = [ SwiftyGPTChatResponseChoice(index: 0, message: SwiftyGPTChatAssistantMessage(content: "Hello, how can I assist you ?"), finishReason: .stop) ] From ea7803dcbde791b0020068f601bfaa2c6ebe32f0 Mon Sep 17 00:00:00 2001 From: Antonio War Date: Sun, 28 Apr 2024 15:19:02 +0200 Subject: [PATCH 08/16] fix: error description --- Explorer/Explorer/Chat/ChatView.swift | 2 +- Explorer/Explorer/ExplorerApp.swift | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Explorer/Explorer/Chat/ChatView.swift b/Explorer/Explorer/Chat/ChatView.swift index af47176..c7f503d 100644 --- a/Explorer/Explorer/Chat/ChatView.swift +++ b/Explorer/Explorer/Chat/ChatView.swift @@ -88,7 +88,7 @@ struct ChatView: View { throw body.error } } catch { - print(error) + messages.append(SwiftyGPTChatSystemMessage(content: "Oops, something went wrong!")) } } } diff --git a/Explorer/Explorer/ExplorerApp.swift b/Explorer/Explorer/ExplorerApp.swift index aca167e..98b0539 100644 --- a/Explorer/Explorer/ExplorerApp.swift +++ b/Explorer/Explorer/ExplorerApp.swift @@ -12,7 +12,10 @@ import SwiftyGPTChat struct ExplorerApp: App { var body: some Scene { WindowGroup { - ChatView(environment: environment) + NavigationStack { + ChatView(environment: environment) + .navigationTitle("Chat") + } } } From 74a0787cc499231a9d6dddc897175a4f9ca23e45 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 15:35:50 +0200 Subject: [PATCH 09/16] Update README.md --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 930f695..091fbb6 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ - **Modular**: SwiftyGPT is divided into modules, utilizing a different target for each feature exposed by the API. Import only the code that you really need, keeping your project lightweight and efficient. - **Mockable**: SwiftyGPT employs protocol-oriented programming to guarantee testability and maintainability. It already exposes some mock objects that can be seamlessly utilized in testing or SwiftUI previews, eliminating the need for actual API calls. +--- +# Explore +Explore SwiftGPT's capabilities with Explorer app that showcases some of the most popular use cases and provides practical examples of how to utilize it in your projects.
+Whether you're interested in generating text, having conversations, or summarizing documents, Explorer demonstrates the power and versatility of SwiftGPT. + --- # Recommendations @@ -21,8 +26,8 @@ To ensure security and flexibility, we recommend loading your OpenAI API key usi Here's a simple example of how you can load your OpenAI API key from an environment variable in Swift: ```swift -guard let apiKey = ProcessInfo.processInfo.environment["OPENAI_API_KEY"] else { - fatalError("Missing OpenAI API key. Please set the OPENAI_API_KEY environment variable.") +guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else { + fatalError("Missing OpenAI API key. Please set the OPEN_AI_API_KEY environment variable.") } ``` From 461a9233d4cf9367c1f7be3114606d93520919fa Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 15:43:17 +0200 Subject: [PATCH 10/16] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 091fbb6..767842b 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ --- # Explore -Explore SwiftGPT's capabilities with Explorer app that showcases some of the most popular use cases and provides practical examples of how to utilize it in your projects.
-Whether you're interested in generating text, having conversations, or summarizing documents, Explorer demonstrates the power and versatility of SwiftGPT. +Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
+Explore popular use cases and practical examples for seamless integration into your projects. --- # Recommendations @@ -44,7 +44,7 @@ Every donation, no matter how small, makes a big difference. Thank you for consi --- # Note -The content of this README.md file has been automatically generated using SwiftyGPT. +The content of this file has been automatically generated using SwiftyGPT. --- # License From e6b89dbd3471ff0ad55372fc29c186b3fc0ea9b1 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 15:59:20 +0200 Subject: [PATCH 11/16] Update README.md --- README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 767842b..8c4bfd3 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,37 @@ - **Mockable**: SwiftyGPT employs protocol-oriented programming to guarantee testability and maintainability. It already exposes some mock objects that can be seamlessly utilized in testing or SwiftUI previews, eliminating the need for actual API calls. --- -# Explore +# Integration +Integrating SwiftyGPT into your Swift project is straightforward. Follow these steps to get started: + +1. **Install SwiftyGPT**: + - If you're using Swift Package Manager (SPM): + - Open your Xcode project. + - Navigate to "File" > "Swift Packages" > "Add Package Dependency...". + - Enter the SwiftyGPT repository URL: `https://github.com/antonio-war/SwiftyGPT`. + - Follow the prompts to select the version and add SwiftyGPT to your project. + - SwiftyGPT exposes multiple targets, import only the ones that you really need in your project. + - If you're using CocoaPods or Carthage, we're sorry, but they are not currently supported. +2. **Import SwiftyGPT**: + - In the files where you want to use SwiftyGPT features, import its modules at the top of the file: + ```swift + import SwiftyGPTChat + ``` +3. **Start Using SwiftyGPT**: + - Once SwiftyGPT is imported, you can start using its APIs to interact with GPT models. + - Refer to the documentation for guidance on how to use its features for text generation, conversation, summarization, and more. +5. **Run Your Project**: + - Build and run your project to ensure that SwiftyGPT has been integrated successfully. + - Test out the functionality you've implemented using SwiftyGPT to ensure everything works as expected. +That's it! You've successfully integrated SwiftyGPT into your project and can now leverage its powerful features. + +--- +# Exploration Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
Explore popular use cases and practical examples for seamless integration into your projects. --- -# Recommendations +# Recommendation To ensure security and flexibility, we recommend loading your OpenAI API key using environment variables instead of hardcoding it directly into your source code. This approach offers several advantages: - **Security**: Storing sensitive information like API keys in environment variables helps prevent accidental exposure of credentials, reducing the risk of unauthorized access to your API resources. @@ -32,7 +57,7 @@ guard let apiKey = ProcessInfo.processInfo.environment["OPEN_AI_API_KEY"] else { ``` --- -# Support the Project +# Support Your generous donations help sustain and improve this project. Here's why supporting us is important: 1. **Covering API Costs**: Accessing certain features or services may require a paid API key. Your donations help cover the cost of maintaining these subscriptions, ensuring uninterrupted access to essential functionalities. 2. **Development and Maintenance**: Donations enable us to dedicate more time and resources to developing new features, fixing bugs, and maintaining the project's overall health. Your support directly contributes to the project's ongoing improvement and sustainability. From bfa9c3966ffbef5eb12fb9c19812416566efe726 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:04:55 +0200 Subject: [PATCH 12/16] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8c4bfd3..13b98cb 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,11 @@ That's it! You've successfully integrated SwiftyGPT into your project and can no --- # Exploration -Discover the full potential of SwiftGPT through the [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
-Explore popular use cases and practical examples for seamless integration into your projects. +Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
+Delve deep into the realm of natural language processing and unleash the full potential of SwiftGPT with these captivating features: +- **Discover the Full Potential**: Immerse yourself in the boundless capabilities of SwiftGPT as you navigate through the intuitive interface of the Explorer app. Unveil the power of state-of-the-art GPT models and witness firsthand their transformative impact on text generation, conversation, summarization, and more. +- **Explore Popular Use Cases**: Embark on an exploration of the most popular use cases for SwiftGPT, meticulously curated within the Explorer app. From crafting compelling narratives to engaging in dynamic conversational interactions, delve into a myriad of scenarios where SwiftGPT shines brightest. +- **Practical Examples for Seamless Integration**: Seamlessly integrate SwiftGPT into your projects with practical examples and step-by-step guidance provided by the Explorer app. Unlock the secrets to effortless integration and harness the unparalleled versatility of SwiftGPT to elevate your applications to new heights of excellence. --- # Recommendation From 5ebf6038bbfff2a874496f3534ed2ea0e0e1da67 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:34:17 +0200 Subject: [PATCH 13/16] Update README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 13b98cb..d0069ad 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,32 @@ Integrating SwiftyGPT into your Swift project is straightforward. Follow these s - Test out the functionality you've implemented using SwiftyGPT to ensure everything works as expected. That's it! You've successfully integrated SwiftyGPT into your project and can now leverage its powerful features. +--- +# Usage +The main steps for using SwiftyGPTChat into your project are outlined below, guiding you through the process. + +### Service Definition +First, define a `SwiftyGPTChatService`. You have three options: +- Use `SwiftyGPTChatNetworkingService` if you want to execute API calls to OpenAI (requires an API Key). +- Use `SwiftyGPTChatMockService` for mocked responses, ideal for testing or SwiftUI previews. +- Create your custom instance to suit any of your purposes. + +```swift + import SwiftyGPTChat + + // Using SwiftyGPTChatNetworkingService + let service = SwiftyGPTChatNetworkingService(apiKey: "YOUR_API_KEY") + + // Using SwiftyGPTChatMockService + let service = SwiftyGPTChatMockService(responseBody: responseBody, duration: 0.5) +``` +### Manager creation +Create a SwiftyGPTChatManager instance using the defined service. + +```swift + let manager = SwiftyGPTChatManager(service: service) +``` + --- # Exploration Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
From c299f03dc7560eb77f126286ed9b4e79c41f2a0b Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:35:25 +0200 Subject: [PATCH 14/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0069ad..c27e2e0 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ That's it! You've successfully integrated SwiftyGPT into your project and can no # Usage The main steps for using SwiftyGPTChat into your project are outlined below, guiding you through the process. -### Service Definition +### Service definition First, define a `SwiftyGPTChatService`. You have three options: - Use `SwiftyGPTChatNetworkingService` if you want to execute API calls to OpenAI (requires an API Key). - Use `SwiftyGPTChatMockService` for mocked responses, ideal for testing or SwiftUI previews. From 1e93b2daa5ccac35c1bf0812a4d08307eb77c961 Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:48:38 +0200 Subject: [PATCH 15/16] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index c27e2e0..ff2d062 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,21 @@ Create a SwiftyGPTChatManager instance using the defined service. ```swift let manager = SwiftyGPTChatManager(service: service) ``` +### Prompt engineering +Create your prompt using the various message types available. +```swift + let messages: [any SwiftyGPTChatMessage] = [ + SwiftyGPTChatSystemMessage(content: "You are Victor from Fallout New Vegas"), + SwiftyGPTChatUserMessage(content: "What's your name ?") + ] +``` +### Request execution +Execute the request using the defined messages and a high degree of customization for all available parameters. + +```swift + let response = try await manager.send(messages: messages, model: .gpt3_5_turbo, frequencyPenalty: 0.5) +``` --- # Exploration Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.
From 57f9b653316303ec80c9bbc65cf36bd87723420c Mon Sep 17 00:00:00 2001 From: Antonio Guerra <59933379+antonio-war@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:55:36 +0200 Subject: [PATCH 16/16] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index ff2d062..5c00a99 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,22 @@ Execute the request using the defined messages and a high degree of customizatio ```swift let response = try await manager.send(messages: messages, model: .gpt3_5_turbo, frequencyPenalty: 0.5) ``` +### Response handling +If successful, by default the response message is found within the first choice received. But this may vary based on the type of request you make. +In case of failure, however, the response body always contains an error describing what went wrong. + +```swift + switch response { + case .success(let body): + if let receivedMessage = body.choices.first?.message { + messages.append(receivedMessage) + } else { + print("Oops, there are no available choices!") + } + case .failure(let body): + print(body.error) + } +``` --- # Exploration Uncover the limitless possibilities of SwiftGPT as you embark on a journey of discovery through the innovative [Explorer](https://github.com/antonio-war/SwiftyGPT/tree/develop/Explorer) app.