├── .gitignore ├── Destinex.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── swiftpm │ └── Package.resolved ├── Destinex ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── ContentView.swift ├── Destinex.entitlements └── DestinexApp.swift ├── LICENSE ├── MLXDestinex ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── BasicLLMView.swift ├── ContentView.swift ├── MLXDestinex.entitlements ├── MLXDestinexApp.swift ├── ModelComponentsView.swift └── TextEmbeddingsView.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## Obj-C/Swift specific 9 | *.hmap 10 | 11 | ## App packaging 12 | *.ipa 13 | *.dSYM.zip 14 | *.dSYM 15 | 16 | ## Playgrounds 17 | timeline.xctimeline 18 | playground.xcworkspace 19 | 20 | # Swift Package Manager 21 | # 22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 23 | # Packages/ 24 | # Package.pins 25 | # Package.resolved 26 | # *.xcodeproj 27 | # 28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 29 | # hence it is not needed unless you have added a package configuration file to your project 30 | # .swiftpm 31 | 32 | .build/ 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | # 42 | # Add this line if you want to avoid checking in source code from the Xcode workspace 43 | # *.xcworkspace 44 | 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build/ 51 | 52 | # fastlane 53 | # 54 | # It is recommended to not store the screenshots in the git repo. 55 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 56 | # For more information about the recommended setup visit: 57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 58 | 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots/**/*.png 62 | fastlane/test_output 63 | 64 | # Xcode Build Output 65 | build/ 66 | DerivedData/ 67 | -------------------------------------------------------------------------------- /Destinex.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A487C552DC79B7C00562832 /* MLXEmbedders in Frameworks */ = {isa = PBXBuildFile; productRef = 0A487C542DC79B7C00562832 /* MLXEmbedders */; }; 11 | 0A487C572DC79B7C00562832 /* MLXLLM in Frameworks */ = {isa = PBXBuildFile; productRef = 0A487C562DC79B7C00562832 /* MLXLLM */; }; 12 | 0A487C592DC79B7C00562832 /* MLXLMCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 0A487C582DC79B7C00562832 /* MLXLMCommon */; }; 13 | 0A487C5B2DC79B7C00562832 /* MLXVLM in Frameworks */ = {isa = PBXBuildFile; productRef = 0A487C5A2DC79B7C00562832 /* MLXVLM */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | 0A487C2D2DC79A9200562832 /* Destinex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Destinex.app; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 0A487C472DC79AE100562832 /* MLXDestinex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MLXDestinex.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 22 | 0A487C2F2DC79A9200562832 /* Destinex */ = { 23 | isa = PBXFileSystemSynchronizedRootGroup; 24 | path = Destinex; 25 | sourceTree = ""; 26 | }; 27 | 0A487C482DC79AE100562832 /* MLXDestinex */ = { 28 | isa = PBXFileSystemSynchronizedRootGroup; 29 | path = MLXDestinex; 30 | sourceTree = ""; 31 | }; 32 | /* End PBXFileSystemSynchronizedRootGroup section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 0A487C2A2DC79A9200562832 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | 0A487C442DC79AE100562832 /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | 0A487C572DC79B7C00562832 /* MLXLLM in Frameworks */, 47 | 0A487C592DC79B7C00562832 /* MLXLMCommon in Frameworks */, 48 | 0A487C5B2DC79B7C00562832 /* MLXVLM in Frameworks */, 49 | 0A487C552DC79B7C00562832 /* MLXEmbedders in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 0A487C242DC79A9200562832 = { 57 | isa = PBXGroup; 58 | children = ( 59 | 0A487C2F2DC79A9200562832 /* Destinex */, 60 | 0A487C482DC79AE100562832 /* MLXDestinex */, 61 | 0A487C2E2DC79A9200562832 /* Products */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | 0A487C2E2DC79A9200562832 /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 0A487C2D2DC79A9200562832 /* Destinex.app */, 69 | 0A487C472DC79AE100562832 /* MLXDestinex.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | 0A487C2C2DC79A9200562832 /* Destinex */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = 0A487C392DC79A9400562832 /* Build configuration list for PBXNativeTarget "Destinex" */; 80 | buildPhases = ( 81 | 0A487C292DC79A9200562832 /* Sources */, 82 | 0A487C2A2DC79A9200562832 /* Frameworks */, 83 | 0A487C2B2DC79A9200562832 /* Resources */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | fileSystemSynchronizedGroups = ( 90 | 0A487C2F2DC79A9200562832 /* Destinex */, 91 | ); 92 | name = Destinex; 93 | packageProductDependencies = ( 94 | ); 95 | productName = Destinex; 96 | productReference = 0A487C2D2DC79A9200562832 /* Destinex.app */; 97 | productType = "com.apple.product-type.application"; 98 | }; 99 | 0A487C462DC79AE100562832 /* MLXDestinex */ = { 100 | isa = PBXNativeTarget; 101 | buildConfigurationList = 0A487C522DC79AE500562832 /* Build configuration list for PBXNativeTarget "MLXDestinex" */; 102 | buildPhases = ( 103 | 0A487C432DC79AE100562832 /* Sources */, 104 | 0A487C442DC79AE100562832 /* Frameworks */, 105 | 0A487C452DC79AE100562832 /* Resources */, 106 | ); 107 | buildRules = ( 108 | ); 109 | dependencies = ( 110 | ); 111 | fileSystemSynchronizedGroups = ( 112 | 0A487C482DC79AE100562832 /* MLXDestinex */, 113 | ); 114 | name = MLXDestinex; 115 | packageProductDependencies = ( 116 | 0A487C542DC79B7C00562832 /* MLXEmbedders */, 117 | 0A487C562DC79B7C00562832 /* MLXLLM */, 118 | 0A487C582DC79B7C00562832 /* MLXLMCommon */, 119 | 0A487C5A2DC79B7C00562832 /* MLXVLM */, 120 | ); 121 | productName = MLXDestinex; 122 | productReference = 0A487C472DC79AE100562832 /* MLXDestinex.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 0A487C252DC79A9200562832 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | BuildIndependentTargetsInParallel = 1; 132 | LastSwiftUpdateCheck = 1630; 133 | LastUpgradeCheck = 1630; 134 | TargetAttributes = { 135 | 0A487C2C2DC79A9200562832 = { 136 | CreatedOnToolsVersion = 16.3; 137 | }; 138 | 0A487C462DC79AE100562832 = { 139 | CreatedOnToolsVersion = 16.3; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = 0A487C282DC79A9200562832 /* Build configuration list for PBXProject "Destinex" */; 144 | developmentRegion = en; 145 | hasScannedForEncodings = 0; 146 | knownRegions = ( 147 | en, 148 | Base, 149 | ); 150 | mainGroup = 0A487C242DC79A9200562832; 151 | minimizedProjectReferenceProxies = 1; 152 | packageReferences = ( 153 | 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */, 154 | ); 155 | preferredProjectObjectVersion = 77; 156 | productRefGroup = 0A487C2E2DC79A9200562832 /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 0A487C2C2DC79A9200562832 /* Destinex */, 161 | 0A487C462DC79AE100562832 /* MLXDestinex */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | 0A487C2B2DC79A9200562832 /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | 0A487C452DC79AE100562832 /* Resources */ = { 175 | isa = PBXResourcesBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXResourcesBuildPhase section */ 182 | 183 | /* Begin PBXSourcesBuildPhase section */ 184 | 0A487C292DC79A9200562832 /* Sources */ = { 185 | isa = PBXSourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | 0A487C432DC79AE100562832 /* Sources */ = { 192 | isa = PBXSourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXSourcesBuildPhase section */ 199 | 200 | /* Begin XCBuildConfiguration section */ 201 | 0A487C372DC79A9400562832 /* Debug */ = { 202 | isa = XCBuildConfiguration; 203 | buildSettings = { 204 | ALWAYS_SEARCH_USER_PATHS = NO; 205 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 206 | CLANG_ANALYZER_NONNULL = YES; 207 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 208 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_ENABLE_OBJC_WEAK = YES; 212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_COMMA = YES; 215 | CLANG_WARN_CONSTANT_CONVERSION = YES; 216 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 224 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 225 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 227 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | COPY_PHASE_STRIP = NO; 235 | DEBUG_INFORMATION_FORMAT = dwarf; 236 | DEVELOPMENT_TEAM = YQZQG7N4WG; 237 | ENABLE_STRICT_OBJC_MSGSEND = YES; 238 | ENABLE_TESTABILITY = YES; 239 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 240 | GCC_C_LANGUAGE_STANDARD = gnu17; 241 | GCC_DYNAMIC_NO_PIC = NO; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 255 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 256 | MTL_FAST_MATH = YES; 257 | ONLY_ACTIVE_ARCH = YES; 258 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 259 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 260 | }; 261 | name = Debug; 262 | }; 263 | 0A487C382DC79A9400562832 /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 268 | CLANG_ANALYZER_NONNULL = YES; 269 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 290 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 291 | CLANG_WARN_STRICT_PROTOTYPES = YES; 292 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 293 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 294 | CLANG_WARN_UNREACHABLE_CODE = YES; 295 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 298 | DEVELOPMENT_TEAM = YQZQG7N4WG; 299 | ENABLE_NS_ASSERTIONS = NO; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu17; 303 | GCC_NO_COMMON_BLOCKS = YES; 304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 306 | GCC_WARN_UNDECLARED_SELECTOR = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 308 | GCC_WARN_UNUSED_FUNCTION = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 311 | MTL_ENABLE_DEBUG_INFO = NO; 312 | MTL_FAST_MATH = YES; 313 | SWIFT_COMPILATION_MODE = wholemodule; 314 | }; 315 | name = Release; 316 | }; 317 | 0A487C3A2DC79A9400562832 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 321 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 322 | CODE_SIGN_ENTITLEMENTS = Destinex/Destinex.entitlements; 323 | CODE_SIGN_STYLE = Automatic; 324 | CURRENT_PROJECT_VERSION = 1; 325 | DEVELOPMENT_TEAM = YQZQG7N4WG; 326 | ENABLE_HARDENED_RUNTIME = YES; 327 | ENABLE_PREVIEWS = YES; 328 | GENERATE_INFOPLIST_FILE = YES; 329 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 330 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 331 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 332 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 333 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 334 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 335 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 336 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 337 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 338 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 339 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 340 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 341 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 342 | MACOSX_DEPLOYMENT_TARGET = 15.4; 343 | MARKETING_VERSION = 1.0; 344 | PRODUCT_BUNDLE_IDENTIFIER = com.rudrankriyam.Destinex; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | REGISTER_APP_GROUPS = YES; 347 | SDKROOT = auto; 348 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 349 | SWIFT_EMIT_LOC_STRINGS = YES; 350 | SWIFT_VERSION = 5.0; 351 | TARGETED_DEVICE_FAMILY = "1,2,7"; 352 | XROS_DEPLOYMENT_TARGET = 2.4; 353 | }; 354 | name = Debug; 355 | }; 356 | 0A487C3B2DC79A9400562832 /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 360 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 361 | CODE_SIGN_ENTITLEMENTS = Destinex/Destinex.entitlements; 362 | CODE_SIGN_STYLE = Automatic; 363 | CURRENT_PROJECT_VERSION = 1; 364 | DEVELOPMENT_TEAM = YQZQG7N4WG; 365 | ENABLE_HARDENED_RUNTIME = YES; 366 | ENABLE_PREVIEWS = YES; 367 | GENERATE_INFOPLIST_FILE = YES; 368 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 369 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 370 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 371 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 372 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 373 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 374 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 375 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 376 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 377 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 378 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 379 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 380 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 381 | MACOSX_DEPLOYMENT_TARGET = 15.4; 382 | MARKETING_VERSION = 1.0; 383 | PRODUCT_BUNDLE_IDENTIFIER = com.rudrankriyam.Destinex; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | REGISTER_APP_GROUPS = YES; 386 | SDKROOT = auto; 387 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 388 | SWIFT_EMIT_LOC_STRINGS = YES; 389 | SWIFT_VERSION = 5.0; 390 | TARGETED_DEVICE_FAMILY = "1,2,7"; 391 | XROS_DEPLOYMENT_TARGET = 2.4; 392 | }; 393 | name = Release; 394 | }; 395 | 0A487C502DC79AE500562832 /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 399 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 400 | CODE_SIGN_ENTITLEMENTS = MLXDestinex/MLXDestinex.entitlements; 401 | CODE_SIGN_STYLE = Automatic; 402 | CURRENT_PROJECT_VERSION = 1; 403 | DEVELOPMENT_TEAM = YQZQG7N4WG; 404 | ENABLE_HARDENED_RUNTIME = YES; 405 | ENABLE_PREVIEWS = YES; 406 | GENERATE_INFOPLIST_FILE = YES; 407 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 408 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 409 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 410 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 411 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 412 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 413 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 414 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 415 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 416 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 417 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 418 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 419 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 420 | MACOSX_DEPLOYMENT_TARGET = 15.4; 421 | MARKETING_VERSION = 1.0; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.rudrankriyam.MLXDestinex; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | REGISTER_APP_GROUPS = YES; 425 | SDKROOT = auto; 426 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 427 | SWIFT_EMIT_LOC_STRINGS = YES; 428 | SWIFT_VERSION = 5.0; 429 | TARGETED_DEVICE_FAMILY = "1,2,7"; 430 | XROS_DEPLOYMENT_TARGET = 2.4; 431 | }; 432 | name = Debug; 433 | }; 434 | 0A487C512DC79AE500562832 /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 438 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 439 | CODE_SIGN_ENTITLEMENTS = MLXDestinex/MLXDestinex.entitlements; 440 | CODE_SIGN_STYLE = Automatic; 441 | CURRENT_PROJECT_VERSION = 1; 442 | DEVELOPMENT_TEAM = YQZQG7N4WG; 443 | ENABLE_HARDENED_RUNTIME = YES; 444 | ENABLE_PREVIEWS = YES; 445 | GENERATE_INFOPLIST_FILE = YES; 446 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 447 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 448 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 449 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 450 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 451 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 452 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; 453 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; 454 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 455 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 456 | IPHONEOS_DEPLOYMENT_TARGET = 18.4; 457 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 458 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 459 | MACOSX_DEPLOYMENT_TARGET = 15.4; 460 | MARKETING_VERSION = 1.0; 461 | PRODUCT_BUNDLE_IDENTIFIER = com.rudrankriyam.MLXDestinex; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | REGISTER_APP_GROUPS = YES; 464 | SDKROOT = auto; 465 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 466 | SWIFT_EMIT_LOC_STRINGS = YES; 467 | SWIFT_VERSION = 5.0; 468 | TARGETED_DEVICE_FAMILY = "1,2,7"; 469 | XROS_DEPLOYMENT_TARGET = 2.4; 470 | }; 471 | name = Release; 472 | }; 473 | /* End XCBuildConfiguration section */ 474 | 475 | /* Begin XCConfigurationList section */ 476 | 0A487C282DC79A9200562832 /* Build configuration list for PBXProject "Destinex" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 0A487C372DC79A9400562832 /* Debug */, 480 | 0A487C382DC79A9400562832 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 0A487C392DC79A9400562832 /* Build configuration list for PBXNativeTarget "Destinex" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 0A487C3A2DC79A9400562832 /* Debug */, 489 | 0A487C3B2DC79A9400562832 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | 0A487C522DC79AE500562832 /* Build configuration list for PBXNativeTarget "MLXDestinex" */ = { 495 | isa = XCConfigurationList; 496 | buildConfigurations = ( 497 | 0A487C502DC79AE500562832 /* Debug */, 498 | 0A487C512DC79AE500562832 /* Release */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | 505 | /* Begin XCRemoteSwiftPackageReference section */ 506 | 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */ = { 507 | isa = XCRemoteSwiftPackageReference; 508 | repositoryURL = "https://github.com/johnmai-dev/mlx-swift-examples"; 509 | requirement = { 510 | branch = main; 511 | kind = branch; 512 | }; 513 | }; 514 | /* End XCRemoteSwiftPackageReference section */ 515 | 516 | /* Begin XCSwiftPackageProductDependency section */ 517 | 0A487C542DC79B7C00562832 /* MLXEmbedders */ = { 518 | isa = XCSwiftPackageProductDependency; 519 | package = 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; 520 | productName = MLXEmbedders; 521 | }; 522 | 0A487C562DC79B7C00562832 /* MLXLLM */ = { 523 | isa = XCSwiftPackageProductDependency; 524 | package = 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; 525 | productName = MLXLLM; 526 | }; 527 | 0A487C582DC79B7C00562832 /* MLXLMCommon */ = { 528 | isa = XCSwiftPackageProductDependency; 529 | package = 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; 530 | productName = MLXLMCommon; 531 | }; 532 | 0A487C5A2DC79B7C00562832 /* MLXVLM */ = { 533 | isa = XCSwiftPackageProductDependency; 534 | package = 0A487C532DC79B7C00562832 /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; 535 | productName = MLXVLM; 536 | }; 537 | /* End XCSwiftPackageProductDependency section */ 538 | }; 539 | rootObject = 0A487C252DC79A9200562832 /* Project object */; 540 | } 541 | -------------------------------------------------------------------------------- /Destinex.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Destinex.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "e001df3f0c15f7090adcdbebd0c0f26cad321e5fb7d2113fb2d599ccc207507e", 3 | "pins" : [ 4 | { 5 | "identity" : "gzipswift", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/1024jp/GzipSwift", 8 | "state" : { 9 | "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", 10 | "version" : "6.0.1" 11 | } 12 | }, 13 | { 14 | "identity" : "jinja", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/johnmai-dev/Jinja", 17 | "state" : { 18 | "revision" : "31c4dd39bcdc07eaa42a384bdc88ea599022b800", 19 | "version" : "1.1.2" 20 | } 21 | }, 22 | { 23 | "identity" : "mlx-swift", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/ml-explore/mlx-swift", 26 | "state" : { 27 | "revision" : "b990c58153af70eb0914bca7dd74401d341fa9ae", 28 | "version" : "0.21.3" 29 | } 30 | }, 31 | { 32 | "identity" : "mlx-swift-examples", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/johnmai-dev/mlx-swift-examples", 35 | "state" : { 36 | "branch" : "main", 37 | "revision" : "f17047c107a62d17a4ce91c62bed764a70f9a688" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-argument-parser", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/apple/swift-argument-parser.git", 44 | "state" : { 45 | "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", 46 | "version" : "1.4.0" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-collections", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/apple/swift-collections.git", 53 | "state" : { 54 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", 55 | "version" : "1.1.4" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-numerics", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/swift-numerics", 62 | "state" : { 63 | "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", 64 | "version" : "1.0.3" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-transformers", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/huggingface/swift-transformers", 71 | "state" : { 72 | "revision" : "940a4fac109d7d68817b22382de4894a8ea945c3", 73 | "version" : "0.1.20" 74 | } 75 | } 76 | ], 77 | "version" : 3 78 | } 79 | -------------------------------------------------------------------------------- /Destinex/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 | -------------------------------------------------------------------------------- /Destinex/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Destinex/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Destinex/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Destinex 4 | // 5 | // Created by Rudrank Riyam on 5/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | #Preview { 23 | ContentView() 24 | } 25 | -------------------------------------------------------------------------------- /Destinex/Destinex.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Destinex/DestinexApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DestinexApp.swift 3 | // Destinex 4 | // 5 | // Created by Rudrank Riyam on 5/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DestinexApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Rudrank Riyam 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 | -------------------------------------------------------------------------------- /MLXDestinex/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 | -------------------------------------------------------------------------------- /MLXDestinex/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "mac", 32 | "scale" : "1x", 33 | "size" : "16x16" 34 | }, 35 | { 36 | "idiom" : "mac", 37 | "scale" : "2x", 38 | "size" : "16x16" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "32x32" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "2x", 48 | "size" : "32x32" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "128x128" 54 | }, 55 | { 56 | "idiom" : "mac", 57 | "scale" : "2x", 58 | "size" : "128x128" 59 | }, 60 | { 61 | "idiom" : "mac", 62 | "scale" : "1x", 63 | "size" : "256x256" 64 | }, 65 | { 66 | "idiom" : "mac", 67 | "scale" : "2x", 68 | "size" : "256x256" 69 | }, 70 | { 71 | "idiom" : "mac", 72 | "scale" : "1x", 73 | "size" : "512x512" 74 | }, 75 | { 76 | "idiom" : "mac", 77 | "scale" : "2x", 78 | "size" : "512x512" 79 | } 80 | ], 81 | "info" : { 82 | "author" : "xcode", 83 | "version" : 1 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MLXDestinex/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MLXDestinex/BasicLLMView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import MLX 3 | import MLXLLM 4 | import MLXLMCommon 5 | 6 | // Define a basic LoadState enum (adapt as needed based on MLXLLM details) 7 | enum LoadState: Equatable { 8 | case idle 9 | case loading 10 | case downloading(Progress) 11 | case ready 12 | case error(Error) 13 | 14 | // CHANGE: Make Equatable conformance compare associated values for .downloading and .error 15 | static func == (lhs: LoadState, rhs: LoadState) -> Bool { 16 | switch (lhs, rhs) { 17 | case (.idle, .idle): 18 | return true 19 | case (.loading, .loading): 20 | return true 21 | case (.downloading(let lhsProgress), .downloading(let rhsProgress)): 22 | // Compare relevant properties of Progress, e.g., fractionCompleted 23 | return lhsProgress.fractionCompleted == rhsProgress.fractionCompleted && 24 | lhsProgress.localizedDescription == rhsProgress.localizedDescription // Optional: Compare description too 25 | case (.ready, .ready): 26 | return true 27 | case (.error(let lhsError), .error(let rhsError)): 28 | // Compare errors based on their description or domain/code 29 | return lhsError.localizedDescription == rhsError.localizedDescription 30 | default: 31 | // Different enum cases are not equal 32 | return false 33 | } 34 | } 35 | } 36 | 37 | struct MessageBubble: View { 38 | let text: String 39 | let isUser: Bool 40 | 41 | var body: some View { 42 | Text(text) 43 | .padding(.horizontal, 16) 44 | .padding(.vertical, 12) 45 | .background(isUser ? Color.indigo : Color(.systemGray6)) 46 | .foregroundColor(isUser ? .white : .primary) 47 | .clipShape(RoundedRectangle(cornerRadius: 20)) 48 | .frame(maxWidth: .infinity, alignment: isUser ? .trailing : .leading) 49 | } 50 | } 51 | 52 | struct BasicLLMView: View { 53 | // Model Configuration 54 | // NOTE: Using a smaller model initially might be faster for testing. 55 | // You can change this later, e.g., LLMRegistry.mistral_7b_instruct_q4_0 56 | let modelConfiguration = LLMRegistry.qwen3_1_7b_4bit 57 | 58 | // State Variables 59 | @State private var modelContainer: ModelContainer? = nil 60 | @State private var loadState: LoadState = .idle 61 | @State private var prompt: String = "What is the meaning of destiny?" 62 | @State private var generatedText: String = "" 63 | @State private var isGenerating: Bool = false 64 | 65 | var body: some View { 66 | VStack(spacing: 0) { 67 | // Navigation bar 68 | ZStack { 69 | Color(.systemGray6) 70 | .ignoresSafeArea() 71 | 72 | Text("MLX Destinex") 73 | .font(.headline) 74 | .padding() 75 | } 76 | .frame(height: 44) 77 | 78 | // Chat area 79 | ScrollView { 80 | VStack(spacing: 16) { 81 | // Status messages 82 | Group { 83 | switch loadState { 84 | case .idle: 85 | Text("Model not loaded yet") 86 | .foregroundColor(.secondary) 87 | case .loading: 88 | HStack { 89 | ProgressView() 90 | Text("Loading Model...") 91 | .foregroundColor(.secondary) 92 | } 93 | case .downloading(let progress): 94 | VStack(spacing: 4) { 95 | ProgressView(value: progress.fractionCompleted) 96 | .progressViewStyle(.linear) 97 | .frame(maxWidth: 200) 98 | Text("Downloading: \(Int(progress.fractionCompleted * 100))%") 99 | .foregroundColor(.secondary) 100 | } 101 | case .ready: 102 | Text("Model Ready") 103 | .foregroundColor(.green) 104 | case .error(let error): 105 | Text("Error: \(error.localizedDescription)") 106 | .foregroundColor(.red) 107 | } 108 | } 109 | .frame(maxWidth: .infinity) 110 | .padding(.vertical, 8) 111 | 112 | // Messages 113 | if !prompt.isEmpty { 114 | MessageBubble(text: prompt, isUser: true) 115 | } 116 | 117 | if !generatedText.isEmpty { 118 | MessageBubble(text: generatedText, isUser: false) 119 | } 120 | } 121 | .padding() 122 | } 123 | .background(Color(.systemGroupedBackground)) 124 | 125 | // Input area 126 | VStack(spacing: 0) { 127 | Divider() 128 | HStack(spacing: 12) { 129 | TextField("Type your message...", text: $prompt) 130 | .padding(12) 131 | .background(Color(.systemGray6)) 132 | .cornerRadius(20) 133 | 134 | Button(action: { 135 | Task { await generate() } 136 | }) { 137 | Image(systemName: isGenerating ? "stop.circle.fill" : "arrow.up.circle.fill") 138 | .font(.system(size: 32)) 139 | .foregroundColor(prompt.isEmpty ? .gray : .indigo) 140 | } 141 | .disabled(loadState != .ready || isGenerating || prompt.isEmpty) 142 | } 143 | .padding(.horizontal, 12) 144 | .padding(.vertical, 8) 145 | .background(Color(.systemBackground)) 146 | } 147 | } 148 | .task { await loadModel() } 149 | } 150 | 151 | // Function to load the model asynchronously 152 | func loadModel() async { 153 | guard loadState == .idle else { return } // Prevent multiple loads 154 | loadState = .loading 155 | do { 156 | // Use the shared factory to load the container 157 | let loadedContainer = try await LLMModelFactory.shared.loadContainer(configuration: modelConfiguration) { progress in 158 | // Update state on main actor asynchronously without awaiting 159 | Task { @MainActor in 160 | loadState = .downloading(progress) 161 | } 162 | } 163 | // Update state on main actor 164 | await MainActor.run { 165 | modelContainer = loadedContainer 166 | loadState = .ready 167 | } 168 | } catch { 169 | // Update state on main actor 170 | await MainActor.run { 171 | loadState = .error(error) 172 | print("Failed to load model: \(error)") 173 | } 174 | } 175 | } 176 | 177 | // Function to generate text based on the prompt 178 | func generate() async { 179 | guard let container = modelContainer else { 180 | await MainActor.run { generatedText = "Model not loaded." } 181 | return 182 | } 183 | guard !isGenerating else { return } // Prevent concurrent generations 184 | 185 | await MainActor.run { 186 | isGenerating = true 187 | } 188 | 189 | do { 190 | // Use perform to get the ModelContext for generation 191 | let stream = try await container.perform { (context: ModelContext) in 192 | // Prepare input 193 | let chat = await [Chat.Message.user(prompt)] 194 | let userInput = UserInput(chat: chat) 195 | let lmInput = try await context.processor.prepare(input: userInput) 196 | 197 | // Set generation parameters 198 | let parameters = GenerateParameters(temperature: 0.7) 199 | 200 | // Generate text stream 201 | return try MLXLMCommon.generate(input: lmInput, parameters: parameters, context: context) 202 | } 203 | 204 | // Process the stream of generated tokens 205 | var preparingFirstToken = true 206 | for try await generation in stream { 207 | await MainActor.run { 208 | // Clear "Generating..." on first token 209 | if preparingFirstToken { 210 | generatedText = "" 211 | preparingFirstToken = false 212 | } 213 | generatedText += generation.chunk ?? "" 214 | } 215 | } 216 | } catch { 217 | await MainActor.run { 218 | generatedText = "Error during generation: \(error.localizedDescription)" 219 | print("Generation failed: \(error)") 220 | } 221 | } 222 | 223 | await MainActor.run { 224 | isGenerating = false // Reset generation state 225 | prompt = "" // Clear the input after sending 226 | } 227 | } 228 | } 229 | 230 | #Preview { 231 | BasicLLMView() 232 | } 233 | -------------------------------------------------------------------------------- /MLXDestinex/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // MLXDestinex 4 | // 5 | // Created by Rudrank Riyam on 5/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image(systemName: "globe") 14 | .imageScale(.large) 15 | .foregroundStyle(.tint) 16 | Text("Hello, world!") 17 | } 18 | .padding() 19 | } 20 | } 21 | 22 | #Preview { 23 | ContentView() 24 | } 25 | -------------------------------------------------------------------------------- /MLXDestinex/MLXDestinex.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.kernel.increased-memory-limit 6 | 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.files.downloads.read-write 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | com.apple.security.network.client 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /MLXDestinex/MLXDestinexApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MLXDestinexApp.swift 3 | // MLXDestinex 4 | // 5 | // Created by Rudrank Riyam on 5/4/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct MLXDestinexApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | TabView { 15 | TextEmbeddingsView() 16 | .tabItem { 17 | Label("Embeddings", systemImage: "brain.head.profile") 18 | } 19 | 20 | BasicLLMView() 21 | .tabItem { 22 | Label("LLM", systemImage: "text.bubble") 23 | } 24 | 25 | ModelComponentsView(modelId: "mlx-community/Qwen3-1.7B-4bit") 26 | .tabItem { 27 | Label("Components", systemImage: "square.and.arrow.up") 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MLXDestinex/ModelComponentsView.swift: -------------------------------------------------------------------------------- 1 | import Hub // Import the Hub module 2 | import OSLog // Use Apple's unified logging 3 | import SwiftUI 4 | 5 | // Represents a file fetched/downloaded from the Hub, categorized for display 6 | struct HubFileDisplay: Identifiable, Hashable { 7 | let id = UUID() 8 | let name: String 9 | let category: FileCategory 10 | let size: String 11 | let url: URL? // Local URL after download 12 | 13 | enum FileCategory: String, CaseIterable, Comparable { 14 | case Config 15 | case Tokenizer 16 | case Weights 17 | case Other 18 | 19 | // Define comparison logic for sorting 20 | static func < (lhs: HubFileDisplay.FileCategory, rhs: HubFileDisplay.FileCategory) -> Bool { 21 | func order(_ category: FileCategory) -> Int { 22 | switch category { 23 | case .Config: return 1 24 | case .Tokenizer: return 2 25 | case .Weights: return 3 26 | case .Other: return 4 27 | } 28 | } 29 | return order(lhs) < order(rhs) 30 | } 31 | } 32 | 33 | // Helper to format bytes into readable string 34 | static func formatBytes(_ bytes: Int64?) -> String { 35 | guard let bytes = bytes else { return "N/A" } 36 | let formatter = ByteCountFormatter() 37 | formatter.allowedUnits = [.useAll] 38 | formatter.countStyle = .file 39 | return formatter.string(fromByteCount: bytes) 40 | } 41 | 42 | // Determine category based on filename - refined based on screenshot 43 | static func categorize(filename: String) -> FileCategory { 44 | if filename == "config.json" || filename == "generation_config.json" { 45 | return .Config 46 | } else if filename == "tokenizer.json" || filename == "tokenizer_config.json" 47 | || filename == "special_tokens_map.json" || filename == "vocab.json" 48 | || filename == "merges.txt" || filename == "added_tokens.json" 49 | { 50 | return .Tokenizer 51 | } else if filename.hasSuffix(".safetensors") || filename.hasSuffix(".safetensors.index.json") { 52 | return .Weights 53 | } else { 54 | return .Other // README.md, .gitattributes, etc. 55 | } 56 | } 57 | 58 | // Implement Hashable 59 | func hash(into hasher: inout Hasher) { 60 | hasher.combine(id) 61 | } 62 | 63 | static func == (lhs: HubFileDisplay, rhs: HubFileDisplay) -> Bool { 64 | lhs.id == rhs.id 65 | } 66 | } 67 | 68 | struct ModelComponentsView: View { 69 | let modelId: String 70 | 71 | @State private var filesByCategory: [HubFileDisplay.FileCategory: [HubFileDisplay]] = [:] 72 | @State private var downloadState: LoadState = .idle // Uses the modified LoadState enum 73 | 74 | private static let logger = Logger( 75 | subsystem: Bundle.main.bundleIdentifier ?? "ModelComponentsView", category: "HubLoading") 76 | 77 | var body: some View { 78 | NavigationStack { 79 | List { 80 | Section { 81 | Text("Components for model: **\(modelId)**") 82 | .font(.headline) 83 | .padding(.bottom, 5) 84 | 85 | .font(.callout) 86 | } header: { 87 | Text("Model Anatomy") 88 | } 89 | 90 | Section { 91 | switch downloadState { 92 | case .idle: 93 | Button("Download Model Files") { 94 | Task { await downloadAndListModelFiles() } 95 | } 96 | .frame(maxWidth: .infinity, alignment: .center) 97 | case .loading: 98 | HStack { 99 | Spacer() 100 | ProgressView("Preparing Download...") 101 | Spacer() 102 | } 103 | case .downloading(let progress): 104 | VStack(spacing: 4) { 105 | ProgressView(value: progress.fractionCompleted) 106 | .progressViewStyle(.linear) 107 | .frame(maxWidth: 200) 108 | Text("Downloading: \(Int(progress.fractionCompleted * 100))%") 109 | .foregroundColor(.secondary) 110 | } 111 | case .ready: 112 | Text("Files Downloaded/Verified in Cache") 113 | .foregroundColor(.green) 114 | .frame(maxWidth: .infinity, alignment: .center) 115 | case .error(let error): 116 | Text("Error: \(error.localizedDescription)") 117 | .foregroundColor(.red) 118 | } 119 | } 120 | 121 | if case .ready = downloadState { 122 | ForEach( 123 | HubFileDisplay.FileCategory.allCases.filter { filesByCategory.keys.contains($0) }, 124 | id: \.self 125 | ) { category in 126 | displaySection(category: category) 127 | } 128 | } 129 | 130 | } 131 | .listStyle(InsetGroupedListStyle()) 132 | .navigationTitle("Model Files") 133 | .refreshable { 134 | await downloadAndListModelFiles() 135 | } 136 | } 137 | } 138 | 139 | @ViewBuilder 140 | private func displaySection(category: HubFileDisplay.FileCategory) -> some View { 141 | if let categoryFiles = filesByCategory[category], !categoryFiles.isEmpty { 142 | Section { 143 | FileGroupView( 144 | icon: icon(for: category), 145 | title: title(for: category), 146 | files: categoryFiles 147 | ) 148 | } header: { 149 | Text("\(title(for: category))") 150 | } 151 | } 152 | } 153 | 154 | private func downloadAndListModelFiles() async { 155 | guard downloadState != .loading && !isDownloading() else { return } 156 | 157 | await MainActor.run { downloadState = .loading } 158 | filesByCategory = [:] 159 | 160 | let hubApi = HubApi() 161 | let repo = Hub.Repo(id: modelId) 162 | let essentialGlobs = ["*.json", "*.safetensors", "*.txt"] 163 | Self.logger.info("Starting snapshot download for model: \(self.modelId)") 164 | 165 | do { 166 | let snapshotURL = try await hubApi.snapshot(from: repo, matching: essentialGlobs) { 167 | progress in 168 | Task { @MainActor in 169 | let fraction = progress.fractionCompleted 170 | let description = progress.localizedDescription // Can be nil 171 | self.downloadState = .downloading(progress) 172 | Self.logger.debug( 173 | "Download progress: \(fraction * 100)% (\(description ?? "No description"))") 174 | } 175 | } 176 | Self.logger.info("Snapshot download/verification complete. Local path: \(snapshotURL.path)") 177 | 178 | let fileManager = FileManager.default 179 | guard 180 | let enumerator = fileManager.enumerator( 181 | at: snapshotURL, includingPropertiesForKeys: [.isRegularFileKey, .fileSizeKey], 182 | options: [.skipsHiddenFiles, .skipsPackageDescendants]) 183 | else { 184 | throw NSError( 185 | domain: "ModelComponentsView", code: 1, 186 | userInfo: [NSLocalizedDescriptionKey: "Failed to enumerate files in snapshot directory."]) 187 | } 188 | 189 | var groupedFiles = [HubFileDisplay.FileCategory: [HubFileDisplay]]() 190 | for case let fileURL as URL in enumerator { 191 | let resourceKeys: Set = [.isRegularFileKey, .fileSizeKey] 192 | let resourceValues = try fileURL.resourceValues(forKeys: resourceKeys) 193 | if resourceValues.isRegularFile ?? false { 194 | let filename = fileURL.lastPathComponent 195 | let category = HubFileDisplay.categorize(filename: filename) 196 | let fileSize = resourceValues.fileSize.map { Int64($0) } 197 | 198 | let displayFile = HubFileDisplay( 199 | name: filename, 200 | category: category, 201 | size: HubFileDisplay.formatBytes(fileSize), 202 | url: fileURL 203 | ) 204 | groupedFiles[category, default: []].append(displayFile) 205 | } 206 | } 207 | 208 | for category in groupedFiles.keys { 209 | groupedFiles[category]?.sort { $0.name < $1.name } 210 | } 211 | 212 | await MainActor.run { 213 | self.filesByCategory = groupedFiles 214 | self.downloadState = .ready 215 | } 216 | } catch let error as Hub.HubClientError { 217 | await MainActor.run { 218 | downloadState = .error(error) 219 | } 220 | Self.logger.error( 221 | "Hub Error during snapshot for \(self.modelId): \(error.localizedDescription)") 222 | } catch { 223 | await MainActor.run { 224 | downloadState = .error(error) 225 | } 226 | Self.logger.error( 227 | "Unexpected Error during snapshot for \(self.modelId): \(error.localizedDescription)") 228 | } 229 | } 230 | 231 | private func isDownloading() -> Bool { 232 | if case .downloading = downloadState { 233 | return true 234 | } 235 | return false 236 | } 237 | 238 | private func icon(for category: HubFileDisplay.FileCategory) -> String { 239 | switch category { 240 | case .Config: return "doc.text.fill" 241 | case .Tokenizer: return "textformat.abc.dottedunderline" 242 | case .Weights: return "cpu.fill" // Or 'memorychip.fill' 243 | case .Other: return "questionmark.folder.fill" 244 | } 245 | } 246 | 247 | private func title(for category: HubFileDisplay.FileCategory) -> String { 248 | switch category { 249 | case .Config: return "1. Configuration" 250 | case .Tokenizer: return "2. Tokenizer" 251 | case .Weights: return "3. Weights" 252 | case .Other: return "4. Other Files (if present)" // Clarify 'Other' 253 | } 254 | } 255 | } 256 | 257 | struct FileGroupView: View { 258 | let icon: String 259 | let title: String 260 | let files: [HubFileDisplay] 261 | 262 | var body: some View { 263 | VStack(alignment: .leading, spacing: 8) { 264 | VStack(alignment: .leading) { 265 | ForEach(files) { file in 266 | HStack { 267 | Image(systemName: icon(for: file.category)) 268 | .foregroundColor(.secondary) 269 | .frame(width: 20, alignment: .center) 270 | Text(file.name) 271 | .font(.caption.monospaced()) 272 | Spacer() 273 | Text(file.size) 274 | .font(.caption) 275 | .foregroundColor(.gray) 276 | } 277 | .padding(.leading, 8) 278 | } 279 | } 280 | } 281 | .padding(.vertical, 5) 282 | } 283 | 284 | private func icon(for category: HubFileDisplay.FileCategory) -> String { 285 | switch category { 286 | case .Config: return "doc.text" 287 | case .Tokenizer: return "textformat.abc" 288 | case .Weights: return "memorychip" 289 | case .Other: return "questionmark.folder" 290 | } 291 | } 292 | } 293 | 294 | struct ModelComponentsView_Previews: PreviewProvider { 295 | static var previews: some View { 296 | ModelComponentsView(modelId: "mlx-community/Qwen1.5-0.5B-Chat-4bit") 297 | .previewDisplayName("Qwen 1.5 0.5B Chat 4bit") 298 | 299 | ModelComponentsView(modelId: "google/paligemma-3b-mix-448") 300 | .previewDisplayName("PaliGemma 3B") 301 | 302 | ModelComponentsView(modelId: "non-existent-model-id-12345") 303 | .previewDisplayName("Error State") 304 | } 305 | } 306 | 307 | extension Substring { 308 | var string: String { String(self) } 309 | } 310 | -------------------------------------------------------------------------------- /MLXDestinex/TextEmbeddingsView.swift: -------------------------------------------------------------------------------- 1 | import MLX 2 | import MLXEmbedders 3 | import SwiftUI 4 | import Tokenizers 5 | 6 | struct DocumentResult: Identifiable, Hashable { 7 | let id = UUID() 8 | let text: String 9 | let similarity: Float 10 | } 11 | 12 | struct ModelStatusView: View { 13 | let loadState: LoadState 14 | 15 | var body: some View { 16 | HStack(spacing: 12) { 17 | Text("Model Status:") 18 | .font(.headline) 19 | .foregroundStyle(.secondary) 20 | 21 | switch loadState { 22 | case .idle: 23 | Label("Idle", systemImage: "circle.dashed") 24 | .foregroundStyle(.gray) 25 | case .loading: 26 | HStack(spacing: 8) { 27 | ProgressView() 28 | .controlSize(.small) 29 | Text("Loading...") 30 | .fontWeight(.medium) 31 | } 32 | .foregroundStyle(.orange) 33 | case .downloading(let progress): 34 | HStack(spacing: 8) { 35 | ProgressView(value: progress.fractionCompleted) 36 | .frame(width: 80) 37 | Text("\(Int(progress.fractionCompleted * 100))%") 38 | .monospacedDigit() 39 | .fontWeight(.medium) 40 | } 41 | .foregroundStyle(.blue) 42 | case .ready: 43 | Label("Ready", systemImage: "checkmark.circle.fill") 44 | .fontWeight(.medium) 45 | .foregroundStyle(.green) 46 | case .error(let error): 47 | Label("Error", systemImage: "exclamationmark.triangle.fill") 48 | .fontWeight(.medium) 49 | .foregroundStyle(.red) 50 | .help(error.localizedDescription) 51 | } 52 | Spacer() 53 | } 54 | .padding() 55 | .background(Color(.systemBackground)) 56 | .clipShape(RoundedRectangle(cornerRadius: 12)) 57 | .overlay( 58 | RoundedRectangle(cornerRadius: 12) 59 | .stroke(Color.secondary.opacity(0.2), lineWidth: 1) 60 | ) 61 | .padding(.horizontal) 62 | .padding(.bottom, 8) 63 | } 64 | } 65 | 66 | struct InputSectionView: View { 67 | @Binding var queryText: String 68 | let documentTexts: [String] 69 | let isProcessing: Bool 70 | let isModelReady: Bool 71 | var queryFieldFocus: FocusState.Binding 72 | let findAction: () -> Void 73 | 74 | var body: some View { 75 | VStack(alignment: .leading, spacing: 20) { 76 | VStack(alignment: .leading, spacing: 8) { 77 | Label("Search Query", systemImage: "magnifyingglass") 78 | .font(.headline) 79 | 80 | TextField("Enter your search query", text: $queryText, axis: .vertical) 81 | .textFieldStyle(.roundedBorder) 82 | .lineLimit(2...) 83 | .padding(.horizontal, 4) 84 | .focused(queryFieldFocus) 85 | } 86 | 87 | VStack(alignment: .leading, spacing: 8) { 88 | Label("Documents", systemImage: "doc.text") 89 | .font(.headline) 90 | 91 | VStack(alignment: .leading, spacing: 8) { 92 | ForEach(documentTexts.indices, id: \.self) { index in 93 | HStack(alignment: .top, spacing: 8) { 94 | Text("•") 95 | .foregroundStyle(.secondary) 96 | Text(documentTexts[index]) 97 | .font(.subheadline) 98 | .lineLimit(1) 99 | .foregroundStyle(.secondary) 100 | } 101 | } 102 | } 103 | .padding(.leading, 4) 104 | } 105 | 106 | Button(action: findAction) { 107 | HStack { 108 | if isProcessing { 109 | ProgressView() 110 | .controlSize(.small) 111 | Text("Processing...") 112 | } else { 113 | Image(systemName: "sparkle.magnifyingglass") 114 | Text("Find Similar Documents") 115 | } 116 | } 117 | .frame(maxWidth: .infinity) 118 | } 119 | .buttonStyle(.borderedProminent) 120 | .controlSize(.large) 121 | .disabled(!isModelReady || isProcessing || queryText.isEmpty) 122 | .animation(.spring(duration: 0.3), value: isProcessing) 123 | } 124 | .padding() 125 | .background(Color(.systemBackground)) 126 | .clipShape(RoundedRectangle(cornerRadius: 12)) 127 | .overlay( 128 | RoundedRectangle(cornerRadius: 12) 129 | .stroke(Color.secondary.opacity(0.2), lineWidth: 1) 130 | ) 131 | .padding(.horizontal) 132 | } 133 | } 134 | 135 | struct ResultsSectionView: View { 136 | let rankedResults: [DocumentResult] 137 | let isProcessing: Bool 138 | let processingError: String? 139 | 140 | var body: some View { 141 | VStack(alignment: .leading, spacing: 16) { 142 | Label("Results", systemImage: "list.bullet.rectangle") 143 | .font(.headline) 144 | .padding(.horizontal) 145 | 146 | if let errorMsg = processingError { 147 | HStack { 148 | Image(systemName: "exclamationmark.triangle.fill") 149 | .foregroundStyle(.red) 150 | Text(errorMsg) 151 | .foregroundStyle(.red) 152 | } 153 | .padding() 154 | .frame(maxWidth: .infinity, alignment: .leading) 155 | .background(Color.red.opacity(0.1)) 156 | .clipShape(RoundedRectangle(cornerRadius: 8)) 157 | .padding(.horizontal) 158 | } else if rankedResults.isEmpty && !isProcessing { 159 | VStack(spacing: 16) { 160 | Image(systemName: "text.magnifyingglass") 161 | .font(.system(size: 32)) 162 | .foregroundStyle(.secondary) 163 | .padding() 164 | 165 | Text("Enter a query and press 'Find Similar' to see results.") 166 | .foregroundStyle(.secondary) 167 | .multilineTextAlignment(.center) 168 | } 169 | .frame(maxWidth: .infinity) 170 | .padding(.vertical, 40) 171 | } else if isProcessing && rankedResults.isEmpty { 172 | VStack { 173 | ProgressView("Analyzing documents...") 174 | .padding() 175 | } 176 | .frame(maxWidth: .infinity) 177 | .padding(.vertical, 40) 178 | } else { 179 | ScrollView { 180 | LazyVStack(spacing: 12) { 181 | ForEach(rankedResults) { result in 182 | HStack(alignment: .top) { 183 | VStack(alignment: .leading, spacing: 4) { 184 | Text(result.text) 185 | .lineLimit(2) 186 | 187 | // Similarity bar indicator 188 | GeometryReader { geo in 189 | ZStack(alignment: .leading) { 190 | RoundedRectangle(cornerRadius: 4) 191 | .fill(Color.secondary.opacity(0.2)) 192 | .frame(height: 8) 193 | 194 | RoundedRectangle(cornerRadius: 4) 195 | .fill(similarityColor(result.similarity)) 196 | .frame( 197 | width: max(10, geo.size.width * CGFloat(result.similarity)), height: 8) 198 | } 199 | } 200 | .frame(height: 8) 201 | .padding(.top, 4) 202 | } 203 | 204 | Spacer() 205 | 206 | Text(String(format: "%.2f", result.similarity)) 207 | .font(.system(.body, design: .monospaced)) 208 | .padding(.horizontal, 8) 209 | .padding(.vertical, 4) 210 | .background(similarityColor(result.similarity).opacity(0.2)) 211 | .foregroundStyle(similarityColor(result.similarity)) 212 | .clipShape(RoundedRectangle(cornerRadius: 6)) 213 | } 214 | .padding() 215 | .background(Color(.secondarySystemBackground)) 216 | .clipShape(RoundedRectangle(cornerRadius: 12)) 217 | } 218 | } 219 | .padding(.horizontal) 220 | .animation(.spring(duration: 0.3), value: rankedResults) 221 | } 222 | } 223 | } 224 | .padding(.top, 8) 225 | } 226 | 227 | private func similarityColor(_ similarity: Float) -> Color { 228 | if similarity > 0.7 { 229 | return .green 230 | } else if similarity > 0.4 { 231 | return .orange 232 | } else { 233 | return .red 234 | } 235 | } 236 | } 237 | 238 | struct TextEmbeddingsView: View { 239 | // --- State Variables --- 240 | @State private var modelContainer: ModelContainer? 241 | @State private var loadState: LoadState = .idle 242 | @State private var isProcessing: Bool = false 243 | @State private var queryText: String = "Best hikes near Mount Rainier" 244 | @State private var documentTexts: [String] = [ 245 | "The Skyline Trail offers stunning views of glaciers and wildflowers.", 246 | "Remember to bring sunscreen and water for your hike.", 247 | "Consider the Paradise area for easy access to scenic trails.", 248 | "Baking sourdough bread requires a mature starter.", 249 | "Tips for optimizing SwiftUI app performance.", 250 | ] 251 | @State private var rankedResults: [DocumentResult] = [] 252 | @State private var processingError: String? 253 | @FocusState private var isQueryFieldFocused: Bool 254 | 255 | // --- Model Configuration --- 256 | let modelConfiguration = ModelConfiguration.minilm_l12 257 | 258 | // Computed property for button disabling 259 | private var isModelReady: Bool { 260 | loadState == .ready 261 | } 262 | 263 | var body: some View { 264 | ScrollView { 265 | VStack(alignment: .leading, spacing: 16) { 266 | // Header with animation 267 | HStack { 268 | VStack(alignment: .leading, spacing: 4) { 269 | Text("Text Embeddings") 270 | .font(.largeTitle) 271 | .fontWeight(.bold) 272 | 273 | Text("Semantic search powered by ML") 274 | .font(.subheadline) 275 | .foregroundStyle(.secondary) 276 | } 277 | 278 | Spacer() 279 | 280 | Image(systemName: "brain.head.profile") 281 | .font(.system(size: 44)) 282 | .symbolRenderingMode(.hierarchical) 283 | .foregroundStyle(.blue) 284 | } 285 | .padding() 286 | 287 | // Model Status Section 288 | ModelStatusView(loadState: loadState) 289 | 290 | // Input Section 291 | InputSectionView( 292 | queryText: $queryText, 293 | documentTexts: documentTexts, 294 | isProcessing: isProcessing, 295 | isModelReady: isModelReady, 296 | queryFieldFocus: $isQueryFieldFocused, 297 | findAction: { Task { await generateEmbeddingsAndCompare() } } 298 | ) 299 | 300 | // Results Section 301 | ResultsSectionView( 302 | rankedResults: rankedResults, 303 | isProcessing: isProcessing, 304 | processingError: processingError 305 | ) 306 | 307 | Spacer(minLength: 20) 308 | } 309 | } 310 | .background(Color(.systemGroupedBackground)) 311 | .task { await loadEmbeddingModel() } 312 | .navigationTitle("Embeddings") 313 | } 314 | 315 | func loadEmbeddingModel() async { 316 | guard loadState == .idle else { return } 317 | await MainActor.run { loadState = .loading } 318 | do { 319 | let loadedContainer = try await MLXEmbedders.loadModelContainer( 320 | configuration: modelConfiguration 321 | ) { progress in 322 | Task { @MainActor in 323 | loadState = .downloading(progress) 324 | } 325 | } 326 | await MainActor.run { 327 | self.modelContainer = loadedContainer 328 | self.loadState = .ready 329 | print("Embedding model loaded successfully.") 330 | } 331 | } catch { 332 | await MainActor.run { 333 | self.loadState = .error(error) 334 | print("Failed to load embedding model: \(error)") 335 | } 336 | } 337 | } 338 | 339 | func generateEmbeddingsAndCompare() async { 340 | isQueryFieldFocused = false 341 | 342 | guard let container = modelContainer, loadState == .ready else { 343 | await MainActor.run { processingError = "Model not ready." } 344 | return 345 | } 346 | guard !queryText.isEmpty else { 347 | await MainActor.run { processingError = "Query cannot be empty." } 348 | return 349 | } 350 | 351 | await MainActor.run { 352 | isProcessing = true 353 | processingError = nil 354 | rankedResults = [] // Clear previous results immediately 355 | } 356 | 357 | let allTexts = [queryText] + documentTexts 358 | 359 | do { 360 | // Generate embeddings for all texts in one batch 361 | let embeddingsFloats: [[Float]] = try await container.perform { 362 | (model: EmbeddingModel, tokenizer: Tokenizer, pooling: Pooling) -> [[Float]] in 363 | 364 | let tokenizedInputs = allTexts.map { 365 | tokenizer.encode(text: $0, addSpecialTokens: true) 366 | } 367 | 368 | // Pad to longest, with a minimum length of 16 369 | let maxLength = tokenizedInputs.reduce(into: 16) { acc, elem in 370 | acc = max(acc, elem.count) 371 | } 372 | 373 | let paddingTokenId = tokenizer.eosTokenId ?? 0 374 | 375 | // Create the padded batch using stacked 376 | let paddedInputIDs = stacked( 377 | tokenizedInputs.map { ids in 378 | MLXArray( 379 | ids + Array(repeating: paddingTokenId, count: maxLength - ids.count) 380 | ) 381 | } 382 | ) 383 | 384 | // Create mask based on padding token 385 | let attentionMask = (paddedInputIDs .!= paddingTokenId) 386 | // Create token type IDs (usually zeros for sentence pair tasks, but just zeros here) 387 | let tokenTypeIDs = MLXArray.zeros(like: paddedInputIDs) 388 | 389 | // Pass nil for positionIds as the model can often infer them 390 | let modelOutput = model( 391 | paddedInputIDs, positionIds: nil, tokenTypeIds: tokenTypeIDs, 392 | attentionMask: attentionMask 393 | ) 394 | 395 | // Pool the embeddings, passing normalize and applyLayerNorm flags 396 | // Remove explicit mask parameter if pooling handles it via modelOutput 397 | let finalEmbeddings = pooling( 398 | modelOutput, 399 | normalize: true, 400 | applyLayerNorm: true 401 | ) 402 | 403 | finalEmbeddings.eval() 404 | 405 | // Convert to [[Float]] before returning from the closure 406 | guard finalEmbeddings.dtype == .float32 else { 407 | // Throwing inside the closure will propagate out 408 | throw NSError( 409 | domain: "EmbeddingError", code: 3, 410 | userInfo: [ 411 | NSLocalizedDescriptionKey: 412 | "Unexpected embedding dtype from pooling: \(finalEmbeddings.dtype)" 413 | ] 414 | ) 415 | } 416 | return finalEmbeddings.map { $0.asArray(Float.self) } 417 | } 418 | 419 | guard let queryEmbedding = embeddingsFloats.first else { 420 | throw NSError( 421 | domain: "EmbeddingError", code: 1, 422 | userInfo: [NSLocalizedDescriptionKey: "Failed to get query embedding."]) 423 | } 424 | let documentEmbeddings = Array(embeddingsFloats.dropFirst()) 425 | 426 | let queryEmbeddingMLX = MLXArray(queryEmbedding) 427 | var results: [DocumentResult] = [] 428 | 429 | for (index, docEmbeddingFloats) in documentEmbeddings.enumerated() { 430 | let docEmbeddingMLX = MLXArray(docEmbeddingFloats) 431 | let similarityMLX = cosineSimilarity(queryEmbeddingMLX, docEmbeddingMLX) 432 | similarityMLX.eval() 433 | let score = getScalarFloat(similarityMLX) 434 | results.append(DocumentResult(text: documentTexts[index], similarity: score)) 435 | } 436 | 437 | await MainActor.run { 438 | self.rankedResults = results.sorted { $0.similarity > $1.similarity } 439 | self.isProcessing = false // Set processing to false *after* results are set 440 | } 441 | 442 | } catch { 443 | await MainActor.run { 444 | processingError = "Processing failed: \(error.localizedDescription)" 445 | isProcessing = false 446 | print("Embedding/Comparison failed: \(error)") 447 | } 448 | } 449 | } 450 | 451 | func cosineSimilarity(_ vectorA: MLXArray, _ vectorB: MLXArray, stream: StreamOrDevice = .default) 452 | -> MLXArray 453 | { 454 | let vectorAProcessed = vectorA.squeezed().asType(.float32) 455 | let vectorBProcessed = vectorB.squeezed().asType(.float32) 456 | 457 | let dotProduct = sum(vectorAProcessed * vectorBProcessed, stream: stream) 458 | let normA = sqrt(sum(vectorAProcessed * vectorAProcessed, stream: stream)) 459 | let normB = sqrt(sum(vectorBProcessed * vectorBProcessed, stream: stream)) 460 | 461 | let magnitude = normA * normB 462 | let similarity = dotProduct / (magnitude + 1e-8) 463 | 464 | return similarity 465 | } 466 | 467 | func getScalarFloat(_ array: MLXArray) -> Float { 468 | // Check dtype before attempting to get item 469 | guard array.dtype == .float32 else { 470 | print("Warning: Trying to get Float scalar from non-float32 MLXArray. DType: \(array.dtype)") 471 | return Float.nan 472 | } 473 | 474 | // Directly call item(Float.self) for clarity and type safety 475 | let floatValue = array.item(Float.self) 476 | return floatValue 477 | } 478 | } 479 | 480 | #Preview { 481 | NavigationView { 482 | TextEmbeddingsView() 483 | } 484 | } 485 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Destinex 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | **Destinex** is the sample iOS app for the book: **[Exploring AI for iOS Development](https://academy.rudrank.com/product/ai)**. 6 | 7 | Currently, Destinex focuses on implementationing using Apple's **MLX Swift** framework, showcasing how to use on-device machine learning for various tasks. Follow along with the book to see how Destinex evolves and how different AI concepts are brought to life! 8 | --------------------------------------------------------------------------------