Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playground access for unsupported devices #156

Merged
merged 12 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions OLMoE.swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
CODE_SIGN_ENTITLEMENTS = OLMoE.swift/OLMoE_swift.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "${BUNDLE_VERSION}";
DEVELOPMENT_ASSET_PATHS = "\"OLMoE.swift/Preview Content\"";
Expand Down Expand Up @@ -417,7 +418,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand All @@ -434,6 +436,7 @@
CODE_SIGN_ENTITLEMENTS = OLMoE.swift/OLMoE_swift.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "${BUNDLE_VERSION}";
DEVELOPMENT_ASSET_PATHS = "\"OLMoE.swift/Preview Content\"";
Expand Down Expand Up @@ -463,7 +466,8 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
20 changes: 20 additions & 0 deletions OLMoE.swift/Assets.xcassets/Warning.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"red" : "0xFF",
"green" : "0xA3",
"blue" : "0x1C"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
20 changes: 16 additions & 4 deletions OLMoE.swift/Constants/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
},
"%lld%%" : {

},
"⚠️ Running on AI2's servers - not on your device" : {

},
"allenai.org" : {

Expand All @@ -28,9 +31,6 @@
},
"Delete history?" : {

},
"Device Not Supported" : {

},
"Download Model" : {

Expand Down Expand Up @@ -62,17 +62,29 @@
},
"Flush Model" : {

},
"However, you can try using OLMoE at the Ai2 Playground. This option does not download the model file to your device, but instead submits user input to a hosted version of OLMoE to remotely generate responses." : {

},
"Model is ready to use!" : {

},
"OLMoE can run locally on iPhone 15 Pro/Max, iPhone 16 models, iPad Pro 4th Gen and newer, or iPad Air 5th Gen and newer." : {

},
"On-Device OLMoE Not Available" : {

},
"Proceed Anyway" : {

},
"Proceed With Mocked Model" : {

},
"This app requires a device with at least 8GB of RAM." : {
"This device does not have the 8GB physical RAM required to run OLMoE locally." : {

},
"Try OLMoE at the Ai2 Playground" : {

},
"Welcome" : {
Expand Down
1 change: 1 addition & 0 deletions OLMoE.swift/Extensions/DeviceSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func isDeviceSupported() -> Bool {
"iPad14,5", "iPad14,6", // iPad Pro 12.9" 6th Gen
"iPad16,3", "iPad16,4", // iPad Pro 11" 5th Gen
"iPad16,5", "iPad16,6", // iPad Pro 12.9" 7th Gen
"iPad13,16", "iPad13,17", // iPad Air 5th Gen
"iPad14,8", "iPad14,9", // iPad Air 6th Gen
"iPad15,1", "iPad15,2", // Hypothetical future iPad models with 8GB RAM
]
Expand Down
121 changes: 76 additions & 45 deletions OLMoE.swift/Views/UnsupportedDeviceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import os
import UIKit

struct UnsupportedDeviceView: View {
@State private var showWebView = false
let proceedAnyway: () -> Void
let proceedMocked: () -> Void

Expand All @@ -21,58 +22,88 @@ struct UnsupportedDeviceView: View {
let availableMemoryInGB = Double(os_proc_available_memory()) / (1024 * 1024 * 1024)
let formattedMemory = String(format: "%.2f", availableMemoryInGB)

VStack {
Image("Exclamation")
.foregroundColor(Color("AccentColor"))

Text("Device Not Supported")
.id(UUID()) // Force unique ID so onAppear gets updated width
.font(.title())
.foregroundColor(Color("AccentColor"))
.background(GeometryReader { geometry in
Color.clear.onAppear {
notSupportedWidth = geometry.size.width + 24
}
})

Text("This app requires a device with at least 8GB of RAM.")
.frame(width: notSupportedWidth)
.multilineTextAlignment(.center)
.padding([.horizontal], 32)
.padding([.vertical], 2)
.font(.body())

if FeatureFlags.allowDeviceBypass {
if availableMemoryInGB > 0 {
Text("(The model requires ~6 GB and this device has: \(formattedMemory) GB available.)")
.frame(width: notSupportedWidth)
GeometryReader { geometry in
ScrollView {
VStack(spacing: 20) {
Image("Exclamation")
.foregroundColor(Color("AccentColor"))

Text("On-Device OLMoE Not Available")
.id(UUID())
.font(.title())
.foregroundColor(Color("AccentColor"))
.background(GeometryReader { geometry in
Color.clear.onAppear {
notSupportedWidth = geometry.size.width + 24
}
})
.multilineTextAlignment(.center)

Text("This device does not have the 8GB physical RAM required to run OLMoE locally.")
.multilineTextAlignment(.center)
.padding()
.font(.body())
}

Button("Proceed Anyway") {
proceedAnyway()
}
.buttonStyle(PrimaryButton(minWidth: mockedModelButtonWidth))
.padding(.vertical, 5)
}
Text("OLMoE can run locally on iPhone 15 Pro/Max, iPhone 16 models, iPad Pro 4th Gen and newer, or iPad Air 5th Gen and newer.")
.multilineTextAlignment(.center)
.font(.body())

if FeatureFlags.allowMockedModel {
Button("Proceed With Mocked Model") {
proceedMocked()
}
.id(UUID()) // Force unique ID so onAppear gets updated width
.buttonStyle(.PrimaryButton)
.padding(.vertical, 5)
.background(GeometryReader { geometry in
Color.clear.onAppear {
mockedModelButtonWidth = geometry.size.width - 24
Text("However, you can try using OLMoE at the Ai2 Playground. This option does not download the model file to your device, but instead submits user input to a hosted version of OLMoE to remotely generate responses.")
.multilineTextAlignment(.center)
.font(.body())

Button("Try OLMoE at the Ai2 Playground") {
showWebView = true
}
.buttonStyle(PrimaryButton(minWidth: mockedModelButtonWidth))
.padding(.top, 12)
.sheet(isPresented: $showWebView, onDismiss: nil) {
SheetWrapper {
WebViewWithBanner(
url: URL(string: "https://playground.allenai.org/?model=olmoe-0125")!,
onDismiss: { showWebView = false }
)
}
.interactiveDismissDisabled(false)
}

if FeatureFlags.allowDeviceBypass {
if availableMemoryInGB > 0 {
Text("(The model requires ~6 GB and this device has: \(formattedMemory) GB available.)")
.frame(width: notSupportedWidth)
.multilineTextAlignment(.center)
.padding()
.font(.body())
}

Button("Proceed Anyway") {
proceedAnyway()
}
.buttonStyle(PrimaryButton(minWidth: mockedModelButtonWidth))
.padding(.vertical, 5)
}

if FeatureFlags.allowMockedModel {
Button("Proceed With Mocked Model") {
proceedMocked()
}
.id(UUID())
.buttonStyle(.PrimaryButton)
.padding(.vertical, 5)
.background(GeometryReader { geometry in
Color.clear.onAppear {
mockedModelButtonWidth = geometry.size.width - 24
}
})
}
})

}
.frame(minHeight: geometry.size.height)
.frame(maxWidth: 512)
.padding(.horizontal, 24)
}
.frame(maxWidth: .infinity)
.frame(height: geometry.size.height)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color("BackgroundColor"))
}
}
Expand Down
87 changes: 87 additions & 0 deletions OLMoE.swift/Views/WebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
let url: URL

func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
return webView
}

func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebView>) {
let request = URLRequest(url: url)
uiView.load(request)
}

class Coordinator: NSObject, WKNavigationDelegate {
var parent: WebView

init(_ parent: WebView) {
self.parent = parent
}

// MARK: - WKNavigationDelegate

/// Intercepts navigation actions in the WKWebView.
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

print("Navigation action type: \(navigationAction.navigationType)")

// Log the destination URL if available.
if let url = navigationAction.request.url {
print("Destination URL: \(url.absoluteString)")
}

// Check if the navigation action is triggered by a link click or form submission,
// and the URL contains "http"
if let url = navigationAction.request.url,
url.absoluteString.contains("http") ||
navigationAction.navigationType == .linkActivated || navigationAction.navigationType == .formSubmitted,
!url.absoluteString.contains("https://playground.allenai.org") {
// Open the URL in Safari.
UIApplication.shared.open(url, options: [:], completionHandler: nil)
// Cancel the navigation within the web view.
decisionHandler(.cancel)
return
}

// Allow all other types of navigation.
decisionHandler(.allow)
}
}

func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}

struct WebViewWithBanner: View {
let url: URL
let onDismiss: () -> Void

var body: some View {
VStack(spacing: 0) {
HStack {
Text("⚠️ Running on AI2's servers - not on your device")
.font(.footnote)

Spacer()

Button(action: onDismiss) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.black)
}
}
.padding(8)
.frame(maxWidth: .infinity)
.background(Color("Warning"))
.foregroundColor(.black)

WebView(url: url)
}
}
}