├── .gitignore ├── LICENSE ├── OpenChat.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── OpenChat ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── ConfigView.swift ├── ContentView.swift ├── Extensions │ └── View.swift ├── Messages │ ├── SystemMessageView.swift │ ├── TypingAnimationProgressView.swift │ └── UserMessageView.swift ├── Model API controllers │ ├── LocalaiController.swift │ ├── OllamaController.swift │ └── OpenaiController.swift ├── Model │ ├── ChatMessage.swift │ ├── ChatStyle.swift │ ├── CompletionResponse.swift │ ├── DataModel.swift │ ├── Host.swift │ ├── InputMessage.swift │ └── OllamaCompletionResponse.swift ├── OpenChat.entitlements ├── OpenChatApp.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── UserInputBarView.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | ## User settings 4 | xcuserdata/ 5 | 6 | # Swift Package Manager 7 | .build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Barnacle.ai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenChat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F2BDA68F2A3714AF00DB7317 /* OpenChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA68E2A3714AF00DB7317 /* OpenChatApp.swift */; }; 11 | F2BDA6912A3714AF00DB7317 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6902A3714AF00DB7317 /* ContentView.swift */; }; 12 | F2BDA6932A3714B000DB7317 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F2BDA6922A3714B000DB7317 /* Assets.xcassets */; }; 13 | F2BDA6972A3714B000DB7317 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F2BDA6962A3714B000DB7317 /* Preview Assets.xcassets */; }; 14 | F2BDA6BC2A37156700DB7317 /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6BB2A37156700DB7317 /* ChatMessage.swift */; }; 15 | F2BDA6BE2A39D71D00DB7317 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6BD2A39D71D00DB7317 /* DataModel.swift */; }; 16 | F2BDA6C02A39D76400DB7317 /* LocalaiController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6BF2A39D76400DB7317 /* LocalaiController.swift */; }; 17 | F2BDA6C32A39DA7F00DB7317 /* ChatStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6C22A39DA7F00DB7317 /* ChatStyle.swift */; }; 18 | F2BDA6C52A39DB4800DB7317 /* SystemMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6C42A39DB4800DB7317 /* SystemMessageView.swift */; }; 19 | F2BDA6C72A39DB5500DB7317 /* UserMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6C62A39DB5500DB7317 /* UserMessageView.swift */; }; 20 | F2BDA6CB2A39EB6400DB7317 /* CompletionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6CA2A39EB6400DB7317 /* CompletionResponse.swift */; }; 21 | F2BDA6CD2A39EEAA00DB7317 /* TypingAnimationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6CC2A39EEAA00DB7317 /* TypingAnimationProgressView.swift */; }; 22 | F2BDA6CF2A3A2AE900DB7317 /* UserInputBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6CE2A3A2AE900DB7317 /* UserInputBarView.swift */; }; 23 | F2BDA6D12A3A366D00DB7317 /* InputMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2BDA6D02A3A366D00DB7317 /* InputMessage.swift */; }; 24 | F2F7D3EC2A7ABEDB009308CF /* OllamaController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3EB2A7ABEDB009308CF /* OllamaController.swift */; }; 25 | F2F7D3EE2A7ABEFD009308CF /* OllamaCompletionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3ED2A7ABEFD009308CF /* OllamaCompletionResponse.swift */; }; 26 | F2F7D3F02A7BCFAF009308CF /* Host.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3EF2A7BCFAF009308CF /* Host.swift */; }; 27 | F2F7D3F22A7BDCB4009308CF /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3F12A7BDCB4009308CF /* ConfigView.swift */; }; 28 | F2F7D3F52A7BE0BD009308CF /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3F42A7BE0BD009308CF /* View.swift */; }; 29 | F2F7D3FB2A7BFCA1009308CF /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = F2F7D3FA2A7BFCA1009308CF /* OpenAI */; }; 30 | F2F7D3FD2A7C2A2C009308CF /* OpenaiController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2F7D3FC2A7C2A2C009308CF /* OpenaiController.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | F2BDA69D2A3714B000DB7317 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = F2BDA6832A3714AF00DB7317 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = F2BDA68A2A3714AF00DB7317; 39 | remoteInfo = OpenChat; 40 | }; 41 | F2BDA6A72A3714B000DB7317 /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = F2BDA6832A3714AF00DB7317 /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = F2BDA68A2A3714AF00DB7317; 46 | remoteInfo = OpenChat; 47 | }; 48 | /* End PBXContainerItemProxy section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | F2BDA68B2A3714AF00DB7317 /* OpenChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenChat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | F2BDA68E2A3714AF00DB7317 /* OpenChatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenChatApp.swift; sourceTree = ""; }; 53 | F2BDA6902A3714AF00DB7317 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 54 | F2BDA6922A3714B000DB7317 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | F2BDA6942A3714B000DB7317 /* OpenChat.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OpenChat.entitlements; sourceTree = ""; }; 56 | F2BDA6962A3714B000DB7317 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 57 | F2BDA69C2A3714B000DB7317 /* OpenChatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenChatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | F2BDA6A62A3714B000DB7317 /* OpenChatUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenChatUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | F2BDA6BB2A37156700DB7317 /* ChatMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; 60 | F2BDA6BD2A39D71D00DB7317 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; }; 61 | F2BDA6BF2A39D76400DB7317 /* LocalaiController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalaiController.swift; sourceTree = ""; }; 62 | F2BDA6C22A39DA7F00DB7317 /* ChatStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStyle.swift; sourceTree = ""; }; 63 | F2BDA6C42A39DB4800DB7317 /* SystemMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemMessageView.swift; sourceTree = ""; }; 64 | F2BDA6C62A39DB5500DB7317 /* UserMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageView.swift; sourceTree = ""; }; 65 | F2BDA6CA2A39EB6400DB7317 /* CompletionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionResponse.swift; sourceTree = ""; }; 66 | F2BDA6CC2A39EEAA00DB7317 /* TypingAnimationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingAnimationProgressView.swift; sourceTree = ""; }; 67 | F2BDA6CE2A3A2AE900DB7317 /* UserInputBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInputBarView.swift; sourceTree = ""; }; 68 | F2BDA6D02A3A366D00DB7317 /* InputMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMessage.swift; sourceTree = ""; }; 69 | F2F7D3EB2A7ABEDB009308CF /* OllamaController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OllamaController.swift; sourceTree = ""; }; 70 | F2F7D3ED2A7ABEFD009308CF /* OllamaCompletionResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OllamaCompletionResponse.swift; sourceTree = ""; }; 71 | F2F7D3EF2A7BCFAF009308CF /* Host.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Host.swift; sourceTree = ""; }; 72 | F2F7D3F12A7BDCB4009308CF /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = ""; }; 73 | F2F7D3F42A7BE0BD009308CF /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; 74 | F2F7D3FC2A7C2A2C009308CF /* OpenaiController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenaiController.swift; sourceTree = ""; }; 75 | F2F7D4002A7E6255009308CF /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | F2BDA6882A3714AF00DB7317 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | F2F7D3FB2A7BFCA1009308CF /* OpenAI in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | F2BDA6992A3714B000DB7317 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | F2BDA6A32A3714B000DB7317 /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | F2BDA6822A3714AF00DB7317 = { 105 | isa = PBXGroup; 106 | children = ( 107 | F2F7D4002A7E6255009308CF /* README.md */, 108 | F2BDA68D2A3714AF00DB7317 /* OpenChat */, 109 | F2BDA68C2A3714AF00DB7317 /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | F2BDA68C2A3714AF00DB7317 /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | F2BDA68B2A3714AF00DB7317 /* OpenChat.app */, 117 | F2BDA69C2A3714B000DB7317 /* OpenChatTests.xctest */, 118 | F2BDA6A62A3714B000DB7317 /* OpenChatUITests.xctest */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | F2BDA68D2A3714AF00DB7317 /* OpenChat */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | F2BDA68E2A3714AF00DB7317 /* OpenChatApp.swift */, 127 | F2BDA6902A3714AF00DB7317 /* ContentView.swift */, 128 | F2F7D3F12A7BDCB4009308CF /* ConfigView.swift */, 129 | F2BDA6CE2A3A2AE900DB7317 /* UserInputBarView.swift */, 130 | F2F7D3F32A7BE0AE009308CF /* Extensions */, 131 | F2F7D3FE2A7CD22F009308CF /* Model API controllers */, 132 | F2F7D3FF2A7CD3B9009308CF /* Messages */, 133 | F2BDA6C12A39D8E500DB7317 /* Model */, 134 | F2BDA6922A3714B000DB7317 /* Assets.xcassets */, 135 | F2BDA6942A3714B000DB7317 /* OpenChat.entitlements */, 136 | F2BDA6952A3714B000DB7317 /* Preview Content */, 137 | ); 138 | path = OpenChat; 139 | sourceTree = ""; 140 | }; 141 | F2BDA6952A3714B000DB7317 /* Preview Content */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | F2BDA6962A3714B000DB7317 /* Preview Assets.xcassets */, 145 | ); 146 | path = "Preview Content"; 147 | sourceTree = ""; 148 | }; 149 | F2BDA6C12A39D8E500DB7317 /* Model */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | F2BDA6C22A39DA7F00DB7317 /* ChatStyle.swift */, 153 | F2BDA6D02A3A366D00DB7317 /* InputMessage.swift */, 154 | F2BDA6BB2A37156700DB7317 /* ChatMessage.swift */, 155 | F2BDA6BD2A39D71D00DB7317 /* DataModel.swift */, 156 | F2BDA6CA2A39EB6400DB7317 /* CompletionResponse.swift */, 157 | F2F7D3ED2A7ABEFD009308CF /* OllamaCompletionResponse.swift */, 158 | F2F7D3EF2A7BCFAF009308CF /* Host.swift */, 159 | ); 160 | path = Model; 161 | sourceTree = ""; 162 | }; 163 | F2F7D3F32A7BE0AE009308CF /* Extensions */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | F2F7D3F42A7BE0BD009308CF /* View.swift */, 167 | ); 168 | path = Extensions; 169 | sourceTree = ""; 170 | }; 171 | F2F7D3FE2A7CD22F009308CF /* Model API controllers */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | F2BDA6BF2A39D76400DB7317 /* LocalaiController.swift */, 175 | F2F7D3EB2A7ABEDB009308CF /* OllamaController.swift */, 176 | F2F7D3FC2A7C2A2C009308CF /* OpenaiController.swift */, 177 | ); 178 | path = "Model API controllers"; 179 | sourceTree = ""; 180 | }; 181 | F2F7D3FF2A7CD3B9009308CF /* Messages */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | F2BDA6CC2A39EEAA00DB7317 /* TypingAnimationProgressView.swift */, 185 | F2BDA6C42A39DB4800DB7317 /* SystemMessageView.swift */, 186 | F2BDA6C62A39DB5500DB7317 /* UserMessageView.swift */, 187 | ); 188 | path = Messages; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXGroup section */ 192 | 193 | /* Begin PBXNativeTarget section */ 194 | F2BDA68A2A3714AF00DB7317 /* OpenChat */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = F2BDA6B02A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChat" */; 197 | buildPhases = ( 198 | F2BDA6872A3714AF00DB7317 /* Sources */, 199 | F2BDA6882A3714AF00DB7317 /* Frameworks */, 200 | F2BDA6892A3714AF00DB7317 /* Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | ); 206 | name = OpenChat; 207 | packageProductDependencies = ( 208 | F2F7D3FA2A7BFCA1009308CF /* OpenAI */, 209 | ); 210 | productName = OpenChat; 211 | productReference = F2BDA68B2A3714AF00DB7317 /* OpenChat.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | F2BDA69B2A3714B000DB7317 /* OpenChatTests */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = F2BDA6B32A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChatTests" */; 217 | buildPhases = ( 218 | F2BDA6982A3714B000DB7317 /* Sources */, 219 | F2BDA6992A3714B000DB7317 /* Frameworks */, 220 | F2BDA69A2A3714B000DB7317 /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | F2BDA69E2A3714B000DB7317 /* PBXTargetDependency */, 226 | ); 227 | name = OpenChatTests; 228 | productName = OpenChatTests; 229 | productReference = F2BDA69C2A3714B000DB7317 /* OpenChatTests.xctest */; 230 | productType = "com.apple.product-type.bundle.unit-test"; 231 | }; 232 | F2BDA6A52A3714B000DB7317 /* OpenChatUITests */ = { 233 | isa = PBXNativeTarget; 234 | buildConfigurationList = F2BDA6B62A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChatUITests" */; 235 | buildPhases = ( 236 | F2BDA6A22A3714B000DB7317 /* Sources */, 237 | F2BDA6A32A3714B000DB7317 /* Frameworks */, 238 | F2BDA6A42A3714B000DB7317 /* Resources */, 239 | ); 240 | buildRules = ( 241 | ); 242 | dependencies = ( 243 | F2BDA6A82A3714B000DB7317 /* PBXTargetDependency */, 244 | ); 245 | name = OpenChatUITests; 246 | productName = OpenChatUITests; 247 | productReference = F2BDA6A62A3714B000DB7317 /* OpenChatUITests.xctest */; 248 | productType = "com.apple.product-type.bundle.ui-testing"; 249 | }; 250 | /* End PBXNativeTarget section */ 251 | 252 | /* Begin PBXProject section */ 253 | F2BDA6832A3714AF00DB7317 /* Project object */ = { 254 | isa = PBXProject; 255 | attributes = { 256 | BuildIndependentTargetsInParallel = 1; 257 | LastSwiftUpdateCheck = 1430; 258 | LastUpgradeCheck = 1430; 259 | TargetAttributes = { 260 | F2BDA68A2A3714AF00DB7317 = { 261 | CreatedOnToolsVersion = 14.3.1; 262 | }; 263 | F2BDA69B2A3714B000DB7317 = { 264 | CreatedOnToolsVersion = 14.3.1; 265 | TestTargetID = F2BDA68A2A3714AF00DB7317; 266 | }; 267 | F2BDA6A52A3714B000DB7317 = { 268 | CreatedOnToolsVersion = 14.3.1; 269 | TestTargetID = F2BDA68A2A3714AF00DB7317; 270 | }; 271 | }; 272 | }; 273 | buildConfigurationList = F2BDA6862A3714AF00DB7317 /* Build configuration list for PBXProject "OpenChat" */; 274 | compatibilityVersion = "Xcode 14.0"; 275 | developmentRegion = en; 276 | hasScannedForEncodings = 0; 277 | knownRegions = ( 278 | en, 279 | Base, 280 | ); 281 | mainGroup = F2BDA6822A3714AF00DB7317; 282 | packageReferences = ( 283 | F2F7D3F92A7BFCA1009308CF /* XCRemoteSwiftPackageReference "OpenAI" */, 284 | ); 285 | productRefGroup = F2BDA68C2A3714AF00DB7317 /* Products */; 286 | projectDirPath = ""; 287 | projectRoot = ""; 288 | targets = ( 289 | F2BDA68A2A3714AF00DB7317 /* OpenChat */, 290 | F2BDA69B2A3714B000DB7317 /* OpenChatTests */, 291 | F2BDA6A52A3714B000DB7317 /* OpenChatUITests */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | F2BDA6892A3714AF00DB7317 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | F2BDA6972A3714B000DB7317 /* Preview Assets.xcassets in Resources */, 302 | F2BDA6932A3714B000DB7317 /* Assets.xcassets in Resources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | F2BDA69A2A3714B000DB7317 /* Resources */ = { 307 | isa = PBXResourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | F2BDA6A42A3714B000DB7317 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXResourcesBuildPhase section */ 321 | 322 | /* Begin PBXSourcesBuildPhase section */ 323 | F2BDA6872A3714AF00DB7317 /* Sources */ = { 324 | isa = PBXSourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | F2F7D3EE2A7ABEFD009308CF /* OllamaCompletionResponse.swift in Sources */, 328 | F2BDA6CD2A39EEAA00DB7317 /* TypingAnimationProgressView.swift in Sources */, 329 | F2BDA6CF2A3A2AE900DB7317 /* UserInputBarView.swift in Sources */, 330 | F2F7D3F02A7BCFAF009308CF /* Host.swift in Sources */, 331 | F2F7D3EC2A7ABEDB009308CF /* OllamaController.swift in Sources */, 332 | F2BDA6C72A39DB5500DB7317 /* UserMessageView.swift in Sources */, 333 | F2BDA6BE2A39D71D00DB7317 /* DataModel.swift in Sources */, 334 | F2BDA6912A3714AF00DB7317 /* ContentView.swift in Sources */, 335 | F2BDA6BC2A37156700DB7317 /* ChatMessage.swift in Sources */, 336 | F2BDA68F2A3714AF00DB7317 /* OpenChatApp.swift in Sources */, 337 | F2BDA6C02A39D76400DB7317 /* LocalaiController.swift in Sources */, 338 | F2BDA6C52A39DB4800DB7317 /* SystemMessageView.swift in Sources */, 339 | F2BDA6D12A3A366D00DB7317 /* InputMessage.swift in Sources */, 340 | F2F7D3F52A7BE0BD009308CF /* View.swift in Sources */, 341 | F2F7D3F22A7BDCB4009308CF /* ConfigView.swift in Sources */, 342 | F2BDA6CB2A39EB6400DB7317 /* CompletionResponse.swift in Sources */, 343 | F2BDA6C32A39DA7F00DB7317 /* ChatStyle.swift in Sources */, 344 | F2F7D3FD2A7C2A2C009308CF /* OpenaiController.swift in Sources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | F2BDA6982A3714B000DB7317 /* Sources */ = { 349 | isa = PBXSourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | F2BDA6A22A3714B000DB7317 /* Sources */ = { 356 | isa = PBXSourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | ); 360 | runOnlyForDeploymentPostprocessing = 0; 361 | }; 362 | /* End PBXSourcesBuildPhase section */ 363 | 364 | /* Begin PBXTargetDependency section */ 365 | F2BDA69E2A3714B000DB7317 /* PBXTargetDependency */ = { 366 | isa = PBXTargetDependency; 367 | target = F2BDA68A2A3714AF00DB7317 /* OpenChat */; 368 | targetProxy = F2BDA69D2A3714B000DB7317 /* PBXContainerItemProxy */; 369 | }; 370 | F2BDA6A82A3714B000DB7317 /* PBXTargetDependency */ = { 371 | isa = PBXTargetDependency; 372 | target = F2BDA68A2A3714AF00DB7317 /* OpenChat */; 373 | targetProxy = F2BDA6A72A3714B000DB7317 /* PBXContainerItemProxy */; 374 | }; 375 | /* End PBXTargetDependency section */ 376 | 377 | /* Begin XCBuildConfiguration section */ 378 | F2BDA6AE2A3714B000DB7317 /* Debug */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | ALWAYS_SEARCH_USER_PATHS = NO; 382 | CLANG_ANALYZER_NONNULL = YES; 383 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 385 | CLANG_ENABLE_MODULES = YES; 386 | CLANG_ENABLE_OBJC_ARC = YES; 387 | CLANG_ENABLE_OBJC_WEAK = YES; 388 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 389 | CLANG_WARN_BOOL_CONVERSION = YES; 390 | CLANG_WARN_COMMA = YES; 391 | CLANG_WARN_CONSTANT_CONVERSION = YES; 392 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 393 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 394 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 404 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 405 | CLANG_WARN_STRICT_PROTOTYPES = YES; 406 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 407 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | COPY_PHASE_STRIP = NO; 411 | DEBUG_INFORMATION_FORMAT = dwarf; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | ENABLE_TESTABILITY = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu11; 415 | GCC_DYNAMIC_NO_PIC = NO; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 424 | GCC_WARN_UNDECLARED_SELECTOR = YES; 425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 426 | GCC_WARN_UNUSED_FUNCTION = YES; 427 | GCC_WARN_UNUSED_VARIABLE = YES; 428 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 429 | MTL_FAST_MATH = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 432 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 433 | }; 434 | name = Debug; 435 | }; 436 | F2BDA6AF2A3714B000DB7317 /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ALWAYS_SEARCH_USER_PATHS = NO; 440 | CLANG_ANALYZER_NONNULL = YES; 441 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 442 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 443 | CLANG_ENABLE_MODULES = YES; 444 | CLANG_ENABLE_OBJC_ARC = YES; 445 | CLANG_ENABLE_OBJC_WEAK = YES; 446 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 447 | CLANG_WARN_BOOL_CONVERSION = YES; 448 | CLANG_WARN_COMMA = YES; 449 | CLANG_WARN_CONSTANT_CONVERSION = YES; 450 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 453 | CLANG_WARN_EMPTY_BODY = YES; 454 | CLANG_WARN_ENUM_CONVERSION = YES; 455 | CLANG_WARN_INFINITE_RECURSION = YES; 456 | CLANG_WARN_INT_CONVERSION = YES; 457 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 459 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 460 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 461 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 462 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 463 | CLANG_WARN_STRICT_PROTOTYPES = YES; 464 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 465 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 466 | CLANG_WARN_UNREACHABLE_CODE = YES; 467 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 468 | COPY_PHASE_STRIP = NO; 469 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 470 | ENABLE_NS_ASSERTIONS = NO; 471 | ENABLE_STRICT_OBJC_MSGSEND = YES; 472 | GCC_C_LANGUAGE_STANDARD = gnu11; 473 | GCC_NO_COMMON_BLOCKS = YES; 474 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 475 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 476 | GCC_WARN_UNDECLARED_SELECTOR = YES; 477 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 478 | GCC_WARN_UNUSED_FUNCTION = YES; 479 | GCC_WARN_UNUSED_VARIABLE = YES; 480 | MTL_ENABLE_DEBUG_INFO = NO; 481 | MTL_FAST_MATH = YES; 482 | SWIFT_COMPILATION_MODE = wholemodule; 483 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 484 | }; 485 | name = Release; 486 | }; 487 | F2BDA6B12A3714B000DB7317 /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 491 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 492 | CODE_SIGN_ENTITLEMENTS = OpenChat/OpenChat.entitlements; 493 | CODE_SIGN_STYLE = Automatic; 494 | CURRENT_PROJECT_VERSION = 1; 495 | DEVELOPMENT_ASSET_PATHS = "\"OpenChat/Preview Content\""; 496 | DEVELOPMENT_TEAM = 9BJC33K565; 497 | ENABLE_HARDENED_RUNTIME = YES; 498 | ENABLE_PREVIEWS = YES; 499 | GENERATE_INFOPLIST_FILE = YES; 500 | INFOPLIST_KEY_CFBundleDisplayName = "Open Chat"; 501 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 502 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 503 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 504 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 505 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 506 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 507 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 508 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 509 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 510 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 511 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 512 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 513 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 514 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 515 | MACOSX_DEPLOYMENT_TARGET = 13.3; 516 | MARKETING_VERSION = 1.0; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.barnacle.OpenChat; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SDKROOT = auto; 520 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 521 | SUPPORTS_MACCATALYST = YES; 522 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 523 | SWIFT_EMIT_LOC_STRINGS = YES; 524 | SWIFT_VERSION = 5.0; 525 | TARGETED_DEVICE_FAMILY = "1,2,6"; 526 | }; 527 | name = Debug; 528 | }; 529 | F2BDA6B22A3714B000DB7317 /* Release */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 533 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 534 | CODE_SIGN_ENTITLEMENTS = OpenChat/OpenChat.entitlements; 535 | CODE_SIGN_STYLE = Automatic; 536 | CURRENT_PROJECT_VERSION = 1; 537 | DEVELOPMENT_ASSET_PATHS = "\"OpenChat/Preview Content\""; 538 | DEVELOPMENT_TEAM = 9BJC33K565; 539 | ENABLE_HARDENED_RUNTIME = YES; 540 | ENABLE_PREVIEWS = YES; 541 | GENERATE_INFOPLIST_FILE = YES; 542 | INFOPLIST_KEY_CFBundleDisplayName = "Open Chat"; 543 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 544 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 545 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 546 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 547 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 548 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 549 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 550 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 551 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 552 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 553 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 554 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 555 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 556 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 557 | MACOSX_DEPLOYMENT_TARGET = 13.3; 558 | MARKETING_VERSION = 1.0; 559 | PRODUCT_BUNDLE_IDENTIFIER = com.barnacle.OpenChat; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | SDKROOT = auto; 562 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 563 | SUPPORTS_MACCATALYST = YES; 564 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 565 | SWIFT_EMIT_LOC_STRINGS = YES; 566 | SWIFT_VERSION = 5.0; 567 | TARGETED_DEVICE_FAMILY = "1,2,6"; 568 | }; 569 | name = Release; 570 | }; 571 | F2BDA6B42A3714B000DB7317 /* Debug */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 575 | BUNDLE_LOADER = "$(TEST_HOST)"; 576 | CODE_SIGN_STYLE = Automatic; 577 | CURRENT_PROJECT_VERSION = 1; 578 | DEVELOPMENT_TEAM = 9BJC33K565; 579 | GENERATE_INFOPLIST_FILE = YES; 580 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 581 | MACOSX_DEPLOYMENT_TARGET = 13.3; 582 | MARKETING_VERSION = 1.0; 583 | PRODUCT_BUNDLE_IDENTIFIER = "com.barnacle.test-import.OpenChatTests"; 584 | PRODUCT_NAME = "$(TARGET_NAME)"; 585 | SDKROOT = auto; 586 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 587 | SWIFT_EMIT_LOC_STRINGS = NO; 588 | SWIFT_VERSION = 5.0; 589 | TARGETED_DEVICE_FAMILY = "1,2"; 590 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenChat.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenChat"; 591 | }; 592 | name = Debug; 593 | }; 594 | F2BDA6B52A3714B000DB7317 /* Release */ = { 595 | isa = XCBuildConfiguration; 596 | buildSettings = { 597 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 598 | BUNDLE_LOADER = "$(TEST_HOST)"; 599 | CODE_SIGN_STYLE = Automatic; 600 | CURRENT_PROJECT_VERSION = 1; 601 | DEVELOPMENT_TEAM = 9BJC33K565; 602 | GENERATE_INFOPLIST_FILE = YES; 603 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 604 | MACOSX_DEPLOYMENT_TARGET = 13.3; 605 | MARKETING_VERSION = 1.0; 606 | PRODUCT_BUNDLE_IDENTIFIER = "com.barnacle.test-import.OpenChatTests"; 607 | PRODUCT_NAME = "$(TARGET_NAME)"; 608 | SDKROOT = auto; 609 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 610 | SWIFT_EMIT_LOC_STRINGS = NO; 611 | SWIFT_VERSION = 5.0; 612 | TARGETED_DEVICE_FAMILY = "1,2"; 613 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenChat.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenChat"; 614 | }; 615 | name = Release; 616 | }; 617 | F2BDA6B72A3714B000DB7317 /* Debug */ = { 618 | isa = XCBuildConfiguration; 619 | buildSettings = { 620 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 621 | CODE_SIGN_STYLE = Automatic; 622 | CURRENT_PROJECT_VERSION = 1; 623 | DEVELOPMENT_TEAM = 9BJC33K565; 624 | GENERATE_INFOPLIST_FILE = YES; 625 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 626 | MACOSX_DEPLOYMENT_TARGET = 13.3; 627 | MARKETING_VERSION = 1.0; 628 | PRODUCT_BUNDLE_IDENTIFIER = "com.barnacle.test-import.OpenChatUITests"; 629 | PRODUCT_NAME = "$(TARGET_NAME)"; 630 | SDKROOT = auto; 631 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 632 | SWIFT_EMIT_LOC_STRINGS = NO; 633 | SWIFT_VERSION = 5.0; 634 | TARGETED_DEVICE_FAMILY = "1,2"; 635 | TEST_TARGET_NAME = OpenChat; 636 | }; 637 | name = Debug; 638 | }; 639 | F2BDA6B82A3714B000DB7317 /* Release */ = { 640 | isa = XCBuildConfiguration; 641 | buildSettings = { 642 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 643 | CODE_SIGN_STYLE = Automatic; 644 | CURRENT_PROJECT_VERSION = 1; 645 | DEVELOPMENT_TEAM = 9BJC33K565; 646 | GENERATE_INFOPLIST_FILE = YES; 647 | IPHONEOS_DEPLOYMENT_TARGET = 16.4; 648 | MACOSX_DEPLOYMENT_TARGET = 13.3; 649 | MARKETING_VERSION = 1.0; 650 | PRODUCT_BUNDLE_IDENTIFIER = "com.barnacle.test-import.OpenChatUITests"; 651 | PRODUCT_NAME = "$(TARGET_NAME)"; 652 | SDKROOT = auto; 653 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 654 | SWIFT_EMIT_LOC_STRINGS = NO; 655 | SWIFT_VERSION = 5.0; 656 | TARGETED_DEVICE_FAMILY = "1,2"; 657 | TEST_TARGET_NAME = OpenChat; 658 | }; 659 | name = Release; 660 | }; 661 | /* End XCBuildConfiguration section */ 662 | 663 | /* Begin XCConfigurationList section */ 664 | F2BDA6862A3714AF00DB7317 /* Build configuration list for PBXProject "OpenChat" */ = { 665 | isa = XCConfigurationList; 666 | buildConfigurations = ( 667 | F2BDA6AE2A3714B000DB7317 /* Debug */, 668 | F2BDA6AF2A3714B000DB7317 /* Release */, 669 | ); 670 | defaultConfigurationIsVisible = 0; 671 | defaultConfigurationName = Release; 672 | }; 673 | F2BDA6B02A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChat" */ = { 674 | isa = XCConfigurationList; 675 | buildConfigurations = ( 676 | F2BDA6B12A3714B000DB7317 /* Debug */, 677 | F2BDA6B22A3714B000DB7317 /* Release */, 678 | ); 679 | defaultConfigurationIsVisible = 0; 680 | defaultConfigurationName = Release; 681 | }; 682 | F2BDA6B32A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChatTests" */ = { 683 | isa = XCConfigurationList; 684 | buildConfigurations = ( 685 | F2BDA6B42A3714B000DB7317 /* Debug */, 686 | F2BDA6B52A3714B000DB7317 /* Release */, 687 | ); 688 | defaultConfigurationIsVisible = 0; 689 | defaultConfigurationName = Release; 690 | }; 691 | F2BDA6B62A3714B000DB7317 /* Build configuration list for PBXNativeTarget "OpenChatUITests" */ = { 692 | isa = XCConfigurationList; 693 | buildConfigurations = ( 694 | F2BDA6B72A3714B000DB7317 /* Debug */, 695 | F2BDA6B82A3714B000DB7317 /* Release */, 696 | ); 697 | defaultConfigurationIsVisible = 0; 698 | defaultConfigurationName = Release; 699 | }; 700 | /* End XCConfigurationList section */ 701 | 702 | /* Begin XCRemoteSwiftPackageReference section */ 703 | F2F7D3F92A7BFCA1009308CF /* XCRemoteSwiftPackageReference "OpenAI" */ = { 704 | isa = XCRemoteSwiftPackageReference; 705 | repositoryURL = "https://github.com/MacPaw/OpenAI.git"; 706 | requirement = { 707 | kind = upToNextMajorVersion; 708 | minimumVersion = 0.2.3; 709 | }; 710 | }; 711 | /* End XCRemoteSwiftPackageReference section */ 712 | 713 | /* Begin XCSwiftPackageProductDependency section */ 714 | F2F7D3FA2A7BFCA1009308CF /* OpenAI */ = { 715 | isa = XCSwiftPackageProductDependency; 716 | package = F2F7D3F92A7BFCA1009308CF /* XCRemoteSwiftPackageReference "OpenAI" */; 717 | productName = OpenAI; 718 | }; 719 | /* End XCSwiftPackageProductDependency section */ 720 | }; 721 | rootObject = F2BDA6832A3714AF00DB7317 /* Project object */; 722 | } 723 | -------------------------------------------------------------------------------- /OpenChat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OpenChat.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OpenChat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "openai", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/MacPaw/OpenAI.git", 7 | "state" : { 8 | "revision" : "a51a7fde78173e57b9166d38d1f665d17a3c4383", 9 | "version" : "0.2.3" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /OpenChat/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /OpenChat/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "1x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "2x", 16 | "size" : "16x16" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "1x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "2x", 26 | "size" : "32x32" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "2x", 36 | "size" : "128x128" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "2x", 46 | "size" : "256x256" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "1x", 51 | "size" : "512x512" 52 | }, 53 | { 54 | "idiom" : "mac", 55 | "scale" : "2x", 56 | "size" : "512x512" 57 | } 58 | ], 59 | "info" : { 60 | "author" : "xcode", 61 | "version" : 1 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OpenChat/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /OpenChat/ConfigView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigView.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 03/08/2023. 6 | // 7 | 8 | import SwiftUI 9 | import OpenAI 10 | 11 | struct ConfigView: View { 12 | @Binding var showConfig: Bool 13 | @Binding var modelHost: Host 14 | @Binding var modelName: String 15 | @Binding var modelUrl: String 16 | @Binding var openaiKey: String 17 | @Binding var openaiAPI: OpenAI? 18 | var ollamaDefaultUrl = "http://127.0.0.1:11434/api/generate" 19 | var localaiDefaultUrl = "http://127.0.0.1:8080/v1/chat/completions" 20 | var localaiUrl = "https://localai.io" 21 | var ollamaUrl = "https://ollama.ai" 22 | var openaiUrl = "https://openai.com" 23 | 24 | var body: some View { 25 | NavigationStack { 26 | VStack { 27 | Picker("Model source:", selection: $modelHost) { 28 | ForEach(Host.allCases, id: \.self) { 29 | Text($0.rawValue) 30 | } 31 | } 32 | HStack { 33 | switch modelHost { 34 | case .ollama: 35 | Link(ollamaUrl, destination: URL(string: ollamaUrl)!) 36 | .font(.footnote) 37 | .buttonStyle(.borderless) 38 | case .localai: 39 | Link(localaiUrl, destination: URL(string: localaiUrl)!) 40 | .font(.footnote) 41 | .buttonStyle(.borderless) 42 | case .openai: 43 | Link(openaiUrl, destination: URL(string: openaiUrl)!) 44 | .font(.footnote) 45 | .buttonStyle(.borderless) 46 | } 47 | Spacer() 48 | } 49 | HStack { 50 | if modelHost == .ollama { 51 | Text("Model URL:").padding(.trailing) 52 | .padding(.top, 20) 53 | TextField("Model URL...", text: $modelUrl) 54 | .onAppear { 55 | modelUrl = ollamaDefaultUrl 56 | } 57 | .padding(.top, 20) 58 | } 59 | if modelHost == .localai { 60 | Text("Model URL:").padding(.trailing) 61 | .padding(.top, 20) 62 | TextField("Model URL...", text: $modelUrl) 63 | .onAppear { 64 | modelUrl = localaiDefaultUrl 65 | } 66 | .padding(.top, 20) 67 | } 68 | Spacer() 69 | } 70 | if [.ollama, .openai, .localai].contains(modelHost) { 71 | HStack { 72 | Text("Model name:").padding(.trailing) 73 | TextField("Model name...", text: $modelName) 74 | } 75 | .padding(.top, 20) 76 | } 77 | if modelHost == .openai { 78 | HStack { 79 | Text("OpenAI key:").padding(.trailing) 80 | TextField("OpenAI key...", text: $openaiKey) 81 | .onChange(of: openaiKey) { newOpenaiKey in 82 | openaiAPI = OpenAI(apiToken: newOpenaiKey) 83 | } 84 | } 85 | .padding(.top, 20) 86 | } 87 | } 88 | .padding() 89 | .padding(.top, 20) 90 | .navigationBarTitle("Configuration", displayMode: .inline) 91 | .toolbar { 92 | Button( 93 | action: { 94 | showConfig = false 95 | }, 96 | label: { 97 | Text("Done") 98 | } 99 | ) 100 | .padding(.leading) 101 | } 102 | } 103 | } 104 | } 105 | 106 | struct ConfigView_Previews: PreviewProvider { 107 | static var previews: some View { 108 | ConfigView( 109 | showConfig: .constant(false), 110 | modelHost: .constant(.ollama), 111 | modelName: .constant("llama2"), 112 | modelUrl: .constant(""), 113 | openaiKey: .constant(""), 114 | openaiAPI: .constant(nil) 115 | ) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /OpenChat/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 12/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | import OpenAI 10 | 11 | struct ContentView: View { 12 | @Environment(\.colorScheme) var colorScheme 13 | @StateObject var dataModel: DataModel = DataModel() 14 | @State var input: String = "" 15 | @Namespace var bottomID 16 | @State var showConfig = false 17 | @State var modelHost = Host.ollama 18 | @State var modelName = "llama2" 19 | @State var openaiKey = "" 20 | @State var openaiAPI: OpenAI? 21 | @State var modelUrl: String = "" 22 | @State var chatStyle = ChatStyle( 23 | name: .light, 24 | backgroundColor: .white, 25 | userMessageColor: .white, 26 | systemMessageColor: .black, 27 | userMessageFont: .system(.body, design: .default), 28 | systemMessageFont: .system(.body, design: .default), 29 | userMessageAlignment: .trailing, 30 | systemMessageAlignment: .leading, 31 | userMessageBackgroundColor: .clear, 32 | userMessageBubbleColor: Color(red:0.041, green:0.502, blue:1.000), 33 | systemMessageBackgroundColor: .clear, 34 | systemMessageBubbleColor: Color(red:0.914, green:0.914, blue:0.922), 35 | inputBoxOutlineColor: .black, 36 | inputBoxBackgroundColor: .white, 37 | inputTextColor: .black, 38 | inputTextFont: .system(.body, design: .default) 39 | ) 40 | var darkChatStyle = ChatStyle( 41 | name: .dark, 42 | backgroundColor: .black, 43 | userMessageColor: .white, 44 | systemMessageColor: .white, 45 | userMessageFont: .system(.body, design: .default), 46 | systemMessageFont: .system(.body, design: .default), 47 | userMessageAlignment: .trailing, 48 | systemMessageAlignment: .leading, 49 | userMessageBackgroundColor: .clear, 50 | userMessageBubbleColor: Color(red:0.041, green:0.502, blue:1.000), 51 | systemMessageBackgroundColor: .clear, 52 | systemMessageBubbleColor: Color(red:0.231, green:0.231, blue:0.240), 53 | inputBoxOutlineColor: .gray, 54 | inputBoxBackgroundColor: .white, 55 | inputTextColor: .white, 56 | inputTextFont: .system(.body, design: .default) 57 | ) 58 | 59 | var body: some View { 60 | NavigationStack { 61 | VStack(alignment: .leading) { 62 | ScrollView([.vertical]) { 63 | ScrollViewReader { value in 64 | LazyVStack(alignment: .leading) { 65 | if dataModel.messages.count > 0 { 66 | ForEach(0..(condition: Bool, transform: (Self) -> M) -> some View { 14 | if condition { 15 | transform(self) 16 | } else { 17 | self 18 | } 19 | } 20 | } 21 | 22 | extension View { 23 | func snapshot() -> UIImage { 24 | let controller = UIHostingController(rootView: self) 25 | let view = controller.view 26 | 27 | let targetSize = controller.view.intrinsicContentSize 28 | view?.bounds = CGRect(origin: .zero, size: targetSize) 29 | view?.backgroundColor = .clear 30 | 31 | let renderer = UIGraphicsImageRenderer(size: targetSize) 32 | 33 | return renderer.image { _ in 34 | view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /OpenChat/Messages/SystemMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserMessageView.swift 3 | // ChatApp 4 | // 5 | // Created by Duncan Anderson on 29/03/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SystemMessageView: View { 11 | @Environment(\.colorScheme) var colorScheme 12 | @State var chatStyle: ChatStyle 13 | @State var errorGettingInspiration = false 14 | @Binding var systemMessage: ChatMessage 15 | 16 | var body: some View { 17 | HStack { 18 | if chatStyle.systemMessageAlignment == .trailing { 19 | Spacer() 20 | } 21 | VStack(alignment: chatStyle.systemMessageAlignment) { 22 | if systemMessage.role == .assistant { 23 | VStack { 24 | HStack { 25 | Text(systemMessage.content) 26 | .font(chatStyle.systemMessageFont) 27 | .foregroundColor(chatStyle.systemMessageColor) 28 | .padding(.horizontal, 10) 29 | .padding(.vertical, 10) 30 | .background(RoundedRectangle(cornerRadius: 15).fill(chatStyle.systemMessageBubbleColor)) 31 | 32 | Spacer() 33 | } 34 | } 35 | } else { 36 | Text(systemMessage.content) 37 | .font(chatStyle.systemMessageFont) 38 | .foregroundColor(chatStyle.systemMessageColor) 39 | } 40 | } 41 | if chatStyle.systemMessageAlignment == .leading { 42 | Spacer() 43 | } 44 | } 45 | .background(chatStyle.systemMessageBackgroundColor) 46 | } 47 | } 48 | 49 | struct SystemMessageView_Previews: PreviewProvider { 50 | static var previews: some View { 51 | SystemMessageView( 52 | chatStyle: ChatStyle( 53 | name: .light, 54 | backgroundColor: .white, 55 | userMessageColor: .white, 56 | systemMessageColor: .black, 57 | userMessageFont: .system(.body, design: .default), 58 | systemMessageFont: .system(.body, design: .default), 59 | userMessageAlignment: .trailing, 60 | systemMessageAlignment: .leading, 61 | userMessageBackgroundColor: .clear, 62 | userMessageBubbleColor: Color(red:0.041, green:0.502, blue:1.000), 63 | systemMessageBackgroundColor: .clear, 64 | systemMessageBubbleColor: Color(red:0.914, green:0.914, blue:0.922), 65 | inputBoxOutlineColor: .black, 66 | inputBoxBackgroundColor: .white, 67 | inputTextColor: .black, 68 | inputTextFont: .system(.body, design: .default) 69 | ), 70 | systemMessage: .constant(ChatMessage( 71 | role: .assistant, 72 | content: "Hello there!" 73 | )) 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /OpenChat/Messages/TypingAnimationProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypingIndicatorProgressView.swift 3 | // ChatApp 4 | // 5 | // Created by Duncan Anderson on 30/03/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TypingAnimationProgressViewStyle: ProgressViewStyle { 11 | @State private var isTyping = false 12 | 13 | func makeBody(configuration: ProgressViewStyleConfiguration) -> some View { 14 | VStack { 15 | Spacer().frame(height: 10) 16 | HStack(spacing: 4) { 17 | Circle() 18 | .frame(width: 10, height: 15) 19 | .opacity(isTyping ? 1 : 0.1) 20 | .animation(.easeOut(duration: 1).repeatForever(autoreverses: true), value: isTyping) 21 | Circle() 22 | .frame(width: 10, height: 10) 23 | .opacity(isTyping ? 1 : 0.1) 24 | .animation(.easeInOut(duration: 1).repeatForever(autoreverses: true), value: isTyping) 25 | Circle() 26 | .frame(width: 10, height: 10) 27 | .opacity(isTyping ? 1 : 0.1) 28 | .animation(.easeIn(duration: 1).repeatForever(autoreverses: true), value: isTyping) 29 | } 30 | .padding(.top, 20) 31 | .padding(.leading, 10) 32 | Spacer().frame(height: 15) 33 | } 34 | .onAppear{ 35 | isTyping.toggle() 36 | } 37 | } 38 | } 39 | 40 | struct TypingAnimationProgressView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | ProgressView(value: 50, total: 100) 43 | .progressViewStyle(TypingAnimationProgressViewStyle()) 44 | .padding() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /OpenChat/Messages/UserMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserMessageView.swift 3 | // ChatApp 4 | // 5 | // Created by Duncan Anderson on 29/03/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct UserMessageView: View { 11 | @State var chatStyle: ChatStyle 12 | @Binding var userMessage: ChatMessage 13 | 14 | var body: some View { 15 | HStack { 16 | if chatStyle.userMessageAlignment == .trailing { 17 | Spacer() 18 | } 19 | VStack(alignment: chatStyle.userMessageAlignment) { 20 | Text(userMessage.content) 21 | .font(chatStyle.userMessageFont) 22 | .foregroundColor(chatStyle.userMessageColor) 23 | .padding(.horizontal, 10) 24 | .padding(.vertical, 10) 25 | .background(RoundedRectangle(cornerRadius: 15).fill(chatStyle.userMessageBubbleColor)) 26 | } 27 | if chatStyle.userMessageAlignment == .leading { 28 | Spacer() 29 | } 30 | } 31 | .background(chatStyle.userMessageBackgroundColor) 32 | } 33 | } 34 | 35 | struct UserMessageView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | UserMessageView( 38 | chatStyle: ChatStyle( 39 | name: .light, 40 | backgroundColor: .white, 41 | userMessageColor: .white, 42 | systemMessageColor: .black, 43 | userMessageFont: .system(.body, design: .default), 44 | systemMessageFont: .system(.body, design: .default), 45 | userMessageAlignment: .trailing, 46 | systemMessageAlignment: .leading, 47 | userMessageBackgroundColor: .clear, 48 | userMessageBubbleColor: Color(red:0.041, green:0.502, blue:1.000), 49 | systemMessageBackgroundColor: .clear, 50 | systemMessageBubbleColor: Color(red:0.914, green:0.914, blue:0.922), 51 | inputBoxOutlineColor: .black, 52 | inputBoxBackgroundColor: .white, 53 | inputTextColor: .black, 54 | inputTextFont: .system(.body, design: .default) 55 | ), 56 | userMessage: .constant( 57 | ChatMessage( 58 | role: .user, 59 | content: "Hi" 60 | ) 61 | ) 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /OpenChat/Model API controllers/LocalaiController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalaiController.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | class LocalaiController: NSObject, URLSessionDataDelegate { 12 | @ObservedObject var dataModel: DataModel 13 | 14 | init(dataModel: DataModel) { 15 | self.dataModel = dataModel 16 | } 17 | 18 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 19 | let jsonDecoder = JSONDecoder() 20 | let completionResponse = try! jsonDecoder.decode(CompletionResponse.self, from: data) 21 | let responseText = completionResponse.choices.first?.message.content ?? "" 22 | DispatchQueue.main.async { 23 | self.dataModel.awaitingResponse = false 24 | let responseMessage = ChatMessage( 25 | role: .assistant, 26 | content: responseText 27 | ) 28 | self.dataModel.messages.append(responseMessage) 29 | } 30 | } 31 | 32 | func sendLocalaiMessage(message: String, modelUrl: String, modelName: String) { 33 | dataModel.messages.append(ChatMessage(role: .user, content: message)) 34 | dataModel.awaitingResponse = true 35 | 36 | let sessionConfig = URLSessionConfiguration.default 37 | let responseHandler = LocalaiController(dataModel: dataModel) 38 | 39 | let session = URLSession(configuration: sessionConfig, delegate: responseHandler, delegateQueue: OperationQueue()) 40 | guard let URL = URL(string: modelUrl) else {return} 41 | var request = URLRequest(url: URL) 42 | request.httpMethod = "POST" 43 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 44 | 45 | let body = LocalaiInputMessage( 46 | temperature: 0.9, 47 | model: modelName, 48 | messages: dataModel.messages) 49 | let jsonEncoder = JSONEncoder() 50 | request.httpBody = try! jsonEncoder.encode(body) 51 | 52 | let task = session.dataTask(with: request) 53 | task.resume() 54 | session.finishTasksAndInvalidate() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /OpenChat/Model API controllers/OllamaController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OllamaController.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | class OllamaController: NSObject, URLSessionDataDelegate { 12 | @ObservedObject var dataModel: DataModel 13 | 14 | init(dataModel: DataModel) { 15 | self.dataModel = dataModel 16 | } 17 | 18 | func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { 19 | var arrayOfJsonResponse: [OllamaCompletionResponse] = [] 20 | let responseString = String(data: data, encoding: String.Encoding.utf8) 21 | let responses = responseString?.components(separatedBy: "\n") 22 | var responseText = "" 23 | if let responses = responses { 24 | for index in 0.. 0 { 26 | let responseData = responses[index].data(using: .utf8) 27 | if let responseData = responseData { 28 | let jsonDecoder = JSONDecoder() 29 | let completionResponse = try! jsonDecoder.decode(OllamaCompletionResponse.self, from: responseData) 30 | DispatchQueue.main.async { 31 | if let context = completionResponse.context { 32 | self.dataModel.ollamaContext = context 33 | } 34 | } 35 | arrayOfJsonResponse.append(completionResponse) 36 | } 37 | } 38 | } 39 | arrayOfJsonResponse = arrayOfJsonResponse.filter({ $0.done != true }) 40 | responseText = arrayOfJsonResponse.map { $0.response ?? "" }.joined() 41 | } 42 | if self.dataModel.messages.last?.role == .assistant { 43 | DispatchQueue.main.async { 44 | self.dataModel.messages[self.dataModel.messages.count-1].content = "\(self.dataModel.messages.last?.content ?? "")\(responseText)" 45 | } 46 | } else { 47 | DispatchQueue.main.async { 48 | let responseMessage = ChatMessage( 49 | role: .assistant, 50 | content: responseText 51 | ) 52 | self.dataModel.messages.append(responseMessage) 53 | } 54 | } 55 | DispatchQueue.main.async { 56 | self.dataModel.awaitingResponse = false 57 | } 58 | } 59 | 60 | func sendOllamaMessage(message: String, modelUrl: String, modelName: String) { 61 | dataModel.messages.append(ChatMessage(role: .user, content: message)) 62 | dataModel.awaitingResponse = true 63 | 64 | let sessionConfig = URLSessionConfiguration.default 65 | let responseHandler = OllamaController(dataModel: dataModel) 66 | 67 | let session = URLSession(configuration: sessionConfig, delegate: responseHandler, delegateQueue: OperationQueue()) 68 | guard let URL = URL(string: modelUrl) else {return} 69 | var request = URLRequest(url: URL) 70 | request.httpMethod = "POST" 71 | 72 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 73 | let body = OllamaInputMessage( 74 | model: modelName, 75 | prompt: message, 76 | context: dataModel.ollamaContext 77 | ) 78 | let jsonEncoder = JSONEncoder() 79 | request.httpBody = try! jsonEncoder.encode(body) 80 | 81 | let task = session.dataTask(with: request) 82 | task.resume() 83 | session.finishTasksAndInvalidate() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /OpenChat/Model API controllers/OpenaiController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenaiController.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 03/08/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import OpenAI 11 | 12 | class OpenaiController { 13 | @ObservedObject var dataModel: DataModel 14 | 15 | init(dataModel: DataModel) { 16 | self.dataModel = dataModel 17 | } 18 | 19 | func openaiMessages(model: String) -> [Chat] { 20 | var openaiMessages: [Chat] = [] 21 | for message in dataModel.messages { 22 | var role: Chat.Role = Chat.Role.assistant 23 | switch message.role { 24 | case .assistant: 25 | role = .assistant 26 | case .user: 27 | role = .user 28 | } 29 | let openaiMessage = Chat( 30 | role: role, 31 | content: message.content 32 | ) 33 | openaiMessages.append(openaiMessage) 34 | } 35 | return openaiMessages 36 | } 37 | 38 | func sendOpenaiMessage(message: String, openaiAPI: OpenAI?, modelName: String) async { 39 | if let openaiAPI = openaiAPI { 40 | dataModel.messages.append(ChatMessage(role: .user, content: message)) 41 | dataModel.awaitingResponse = true 42 | 43 | var messages = OpenaiController(dataModel: dataModel).openaiMessages(model: modelName) 44 | messages.append( 45 | Chat(role: .user, content: message) 46 | ) 47 | var gptModel = Model() 48 | switch modelName { 49 | case "gpt-4": 50 | gptModel = Model.gpt4 51 | case "gpt-3.5-turbo": 52 | gptModel = Model.gpt3_5Turbo0613 53 | default: 54 | () 55 | } 56 | let query = ChatQuery( 57 | model: gptModel, 58 | messages: messages 59 | ) 60 | 61 | openaiAPI.chatsStream(query: query) { partialResult in 62 | switch partialResult { 63 | case .success(let result): 64 | print(result.choices) 65 | for chunk in result.choices { 66 | DispatchQueue.main.async { 67 | self.dataModel.awaitingResponse = false 68 | if let word = chunk.delta.content { 69 | if self.dataModel.messages.last?.role == .assistant { 70 | self.dataModel.messages[self.dataModel.messages.count-1].content = "\(self.dataModel.messages.last?.content ?? "")\(word)" 71 | } else { 72 | let responseMessage = ChatMessage( 73 | role: .assistant, 74 | content: word 75 | ) 76 | self.dataModel.messages.append(responseMessage) 77 | } 78 | } 79 | } 80 | } 81 | 82 | case .failure(let error): 83 | print(error.localizedDescription) 84 | } 85 | } completion: { error in 86 | if let error = error { 87 | print(error.localizedDescription) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /OpenChat/Model/ChatMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatMessage.swift 3 | // ChatApp 4 | // 5 | // Created by Duncan Anderson on 29/03/2023. 6 | // 7 | 8 | enum Role: String, Codable { 9 | case assistant = "assistant" 10 | case user = "user" 11 | } 12 | 13 | struct ChatMessage: Codable { 14 | var role: Role 15 | var content: String 16 | } 17 | -------------------------------------------------------------------------------- /OpenChat/Model/ChatStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatStyle.swift 3 | // ChatApp 4 | // 5 | // Created by Duncan Anderson on 29/03/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | enum ChatUIStyle: String, CaseIterable { 12 | case light = "light" 13 | case dark = "dark" 14 | } 15 | 16 | struct ChatStyle { 17 | enum TypingIndicator { 18 | case typing 19 | case terminal 20 | case none 21 | } 22 | 23 | var name: ChatUIStyle 24 | var backgroundColor: Color 25 | var userMessageColor: Color 26 | var systemMessageColor: Color 27 | var userMessageFont: Font 28 | var systemMessageFont: Font 29 | var userMessageAlignment: HorizontalAlignment 30 | var systemMessageAlignment: HorizontalAlignment 31 | var userMessageBackgroundColor: Color 32 | var userMessageBubbleColor: Color 33 | var systemMessageBackgroundColor: Color 34 | var systemMessageBubbleColor: Color 35 | var inputBoxOutlineColor: Color 36 | var inputBoxBackgroundColor: Color 37 | var inputTextColor: Color 38 | var inputTextFont: Font 39 | } 40 | -------------------------------------------------------------------------------- /OpenChat/Model/CompletionResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionResponse.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CompletionResponse: Decodable { 11 | let choices: [Choice] 12 | let usage: Usage? 13 | } 14 | 15 | struct Choice: Decodable { 16 | let finishReason: String? 17 | let message: ChatMessage 18 | } 19 | 20 | struct Usage: Decodable { 21 | let promptTokens: Int? 22 | let completionTokens: Int? 23 | let totalTokens: Int? 24 | } 25 | -------------------------------------------------------------------------------- /OpenChat/Model/DataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataModel.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | class DataModel: ObservableObject { 11 | @Published var messages: [ChatMessage] = [] 12 | @Published var awaitingResponse = false 13 | @Published var ollamaContext: [Int]? 14 | } 15 | -------------------------------------------------------------------------------- /OpenChat/Model/Host.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Host.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 03/08/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Host: String, CaseIterable { 11 | case ollama = "Ollama" 12 | case localai = "LocalAI" 13 | case openai = "OpenAI" 14 | } 15 | -------------------------------------------------------------------------------- /OpenChat/Model/InputMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputMessage.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct LocalaiInputMessage: Codable { 11 | var temperature: Decimal 12 | var model: String 13 | var messages: [ChatMessage] 14 | } 15 | 16 | struct OllamaInputMessage: Codable { 17 | var model: String 18 | var prompt: String 19 | var context: [Int]? 20 | } 21 | -------------------------------------------------------------------------------- /OpenChat/Model/OllamaCompletionResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionResponse.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct OllamaCompletionResponse: Decodable { 11 | let model: String 12 | let created_at: String 13 | let response: String? 14 | let done: Bool 15 | let context: [Int]? 16 | let total_duration: Int? 17 | let load_duration: Int? 18 | let sample_count: Int? 19 | let sample_duration: Int? 20 | let prompt_eval_count: Int? 21 | let prompt_eval_duration: Int? 22 | let eval_count: Int? 23 | let eval_duration: Int? 24 | } 25 | -------------------------------------------------------------------------------- /OpenChat/OpenChat.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /OpenChat/OpenChatApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenChatApp.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 12/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct OpenChatApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /OpenChat/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /OpenChat/UserInputBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInputBarView.swift 3 | // OpenChat 4 | // 5 | // Created by Duncan Anderson on 14/06/2023. 6 | // 7 | 8 | import SwiftUI 9 | import OpenAI 10 | 11 | struct UserInputBarView: View { 12 | @StateObject var dataModel: DataModel = DataModel() 13 | @Binding var input: String 14 | @Binding var chatStyle: ChatStyle 15 | @Binding var modelHost: Host 16 | @Binding var modelName: String 17 | @Binding var modelUrl: String 18 | @Binding var openaiAPI: OpenAI? 19 | @FocusState private var inputInFocus: Bool 20 | 21 | var body: some View { 22 | ZStack { 23 | RoundedRectangle(cornerRadius: 5) 24 | .stroke(chatStyle.inputBoxOutlineColor, lineWidth: 0.5) 25 | .frame(height: 44) 26 | .foregroundColor(chatStyle.inputBoxBackgroundColor) 27 | HStack { 28 | TextField( 29 | text: $input, 30 | label: { 31 | Text("") 32 | .font(.system(.body, design: .monospaced)) 33 | } 34 | ) 35 | .padding(.leading, 10) 36 | .foregroundColor(chatStyle.inputTextColor) 37 | .font(chatStyle.inputTextFont) 38 | .disabled(dataModel.awaitingResponse) 39 | .foregroundColor(.white) 40 | .textFieldStyle(PlainTextFieldStyle()) 41 | .disableAutocorrection(true) 42 | .focused($inputInFocus) 43 | .onSubmit { 44 | Task { 45 | await sendMessage(message: input) 46 | } 47 | } 48 | Button( 49 | action: { 50 | Task { 51 | await sendMessage(message: input) 52 | } 53 | }, 54 | label: { 55 | if input.count > 0 { 56 | ZStack { 57 | RoundedRectangle(cornerRadius: 5) 58 | .foregroundColor(Color(red:0.041, green:0.502, blue:1.000)) 59 | Image(systemName: "paperplane.fill") 60 | .foregroundColor(Color.white) 61 | } 62 | .frame(width: 35, height: 35) 63 | } else { 64 | ZStack { 65 | RoundedRectangle(cornerRadius: 5) 66 | .foregroundColor(Color.gray) 67 | Image(systemName: "paperplane.fill") 68 | .foregroundColor(Color.white) 69 | } 70 | .frame(width: 35, height: 35) 71 | } 72 | } 73 | ) 74 | .disabled(input.count == 0) 75 | .padding(.trailing, 5) 76 | .buttonStyle(.borderless) 77 | } 78 | } 79 | } 80 | 81 | private func sendMessage(message: String) async { 82 | switch modelHost { 83 | case .localai: 84 | LocalaiController(dataModel: dataModel).sendLocalaiMessage( 85 | message: input, 86 | modelUrl: modelUrl, 87 | modelName: modelName 88 | ) 89 | case .ollama: 90 | OllamaController(dataModel: dataModel).sendOllamaMessage( 91 | message: input, 92 | modelUrl: modelUrl, 93 | modelName: modelName 94 | ) 95 | case .openai: 96 | await OpenaiController(dataModel: dataModel).sendOpenaiMessage( 97 | message: input, 98 | openaiAPI: openaiAPI, 99 | modelName: modelName 100 | ) 101 | } 102 | input = "" 103 | } 104 | } 105 | 106 | struct UserInputBarView_Previews: PreviewProvider { 107 | static var previews: some View { 108 | UserInputBarView( 109 | input: .constant("Hi!"), 110 | chatStyle: .constant(ChatStyle( 111 | name: .light, 112 | backgroundColor: .white, 113 | userMessageColor: .white, 114 | systemMessageColor: .black, 115 | userMessageFont: .system(.body, design: .default), 116 | systemMessageFont: .system(.body, design: .default), 117 | userMessageAlignment: .trailing, 118 | systemMessageAlignment: .leading, 119 | userMessageBackgroundColor: .clear, 120 | userMessageBubbleColor: Color(red:0.041, green:0.502, blue:1.000), 121 | systemMessageBackgroundColor: .clear, 122 | systemMessageBubbleColor: Color(red:0.914, green:0.914, blue:0.922), 123 | inputBoxOutlineColor: .black, 124 | inputBoxBackgroundColor: .white, 125 | inputTextColor: .black, 126 | inputTextFont: .system(.body, design: .default) 127 | )), 128 | modelHost: .constant(Host.ollama), 129 | modelName: .constant("llama2"), 130 | modelUrl: .constant(""), 131 | openaiAPI: .constant(nil) 132 | ) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenChat 2 | A native SwiftUI app for chatting with Large Language Models. 3 | 4 | This app is mainly meant to run on Apple mac, alongside a locally installed LLM. However, it's a mac catalyst app, so should also run on iPhone and iPad. Perhaps you don't have an LLM running there, but it also supports OpenAI so you could use it to interact with that. 5 | 6 | To run, open the project in Xcode and hit run. 7 | 8 | Use the settings menu (gear icon in top right) to choose your model provider and model name. 9 | 10 | Currently supported LLMs are: 11 | - [Ollama](https://ollama.ai) 12 | - [LocalAI](https://localai.io) 13 | - [OpenAI](https://openai.com) 14 | --------------------------------------------------------------------------------