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

feat: new app path + realtime url support #11

Merged
merged 2 commits into from
Feb 21, 2024
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
35 changes: 31 additions & 4 deletions Sources/FalClient/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public protocol Client {

func run(_ id: String, input: Payload?, options: RunOptions) async throws -> Payload

@available(*, deprecated, message: "Pass the path as part of the app identifier instead")
func subscribe(
to app: String,
path: String?,
Expand All @@ -50,6 +51,15 @@ public protocol Client {
includeLogs: Bool,
onQueueUpdate: OnQueueUpdate?
) async throws -> Payload

func subscribe(
to app: String,
input: Payload?,
pollInterval: DispatchTimeInterval,
timeout: DispatchTimeInterval,
includeLogs: Bool,
onQueueUpdate: OnQueueUpdate?
) async throws -> Payload
}

public extension Client {
Expand All @@ -68,8 +78,8 @@ public extension Client {
try await run(app, input: input, options: options)
}

/// Submits a request to the given [app], an optional [path]. This method
/// uses the [queue] API to submit the request and poll for the result.
/// Submits a request to the given [app]. This method uses the [queue] API
/// to submit the request and poll for the result.
///
/// This is useful for long running requests, and it's the preffered way
/// to interact with the model APIs.
Expand All @@ -83,19 +93,36 @@ public extension Client {
/// - onQueueUpdate: A callback to be called when the queue status is updated.
func subscribe(
to app: String,
path: String? = nil,
input: Payload? = nil,
pollInterval: DispatchTimeInterval = .seconds(1),
timeout: DispatchTimeInterval = .minutes(3),
includeLogs: Bool = false,
onQueueUpdate: OnQueueUpdate? = nil
) async throws -> Payload {
try await subscribe(to: app,
path: path,
input: input,
pollInterval: pollInterval,
timeout: timeout,
includeLogs: includeLogs,
onQueueUpdate: onQueueUpdate)
}

@available(*, deprecated, message: "Pass the path as part of the app identifier instead")
func subscribe(
to app: String,
path: String? = nil,
input: Payload? = nil,
pollInterval: DispatchTimeInterval = .seconds(1),
timeout: DispatchTimeInterval = .minutes(3),
includeLogs: Bool = false,
onQueueUpdate: OnQueueUpdate? = nil
) async throws -> Payload {
let appId = path.map { "\(app)\($0)" } ?? app
return try await subscribe(to: appId,
input: input,
pollInterval: pollInterval,
timeout: timeout,
includeLogs: includeLogs,
onQueueUpdate: onQueueUpdate)
}
}
22 changes: 19 additions & 3 deletions Sources/FalClient/Realtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ func hasBinaryField(_ type: Encodable) -> Bool {
if child.value is Data {
return true
}
if case FalImageContent.raw = child.value {
return true
}
}
return false
}
Expand Down Expand Up @@ -112,17 +115,30 @@ public class RealtimeConnection: BaseRealtimeConnection<Payload> {}
/// Connection implementation that can be used to send messages using a custom `Encodable` type.
public class TypedRealtimeConnection<Input: Encodable>: BaseRealtimeConnection<Input> {}

/// This is a list of apps deployed before formal realtime support. Their URLs follow
/// a different pattern and will be kept here until we fully sunset them.
let LegacyApps = [
"lcm-sd15-i2i",
"lcm",
"sdxl-turbo-realtime",
"sd-turbo-real-time-high-fps-msgpack-a10g",
"lcm-plexed-sd15-i2i",
"sd-turbo-real-time-high-fps-msgpack",
]

func buildRealtimeUrl(forApp app: String, token: String? = nil) -> URL {
guard var components = URLComponents(string: buildUrl(fromId: app, path: "/ws")) else {
// Some basic support for old ids, this should be removed during 1.0.0 release
// For full-support of old ids, users can point to version 0.4.x
let appAlias = (try? appAlias(fromId: app)) ?? app
let path = LegacyApps.contains(appAlias) || !app.contains("/") ? "/ws" : "/realtime"
guard var components = URLComponents(string: buildUrl(fromId: app, path: path)) else {
preconditionFailure("Invalid URL. This is unexpected and likely a problem in the client library.")
}
components.scheme = "wss"

if let token {
components.queryItems = [URLQueryItem(name: "fal_jwt_token", value: token)]
}

print(components.url!)
// swiftlint:disable:next force_unwrapping
return components.url!
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/FalClient/Utility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ func buildUrl(fromId id: String, path: String? = nil, subdomain: String? = nil)

func ensureAppIdFormat(_ id: String) throws -> String {
let parts = id.split(separator: "/")
if parts.count == 2 {
if parts.count > 1 {
return id
}
let regex = try NSRegularExpression(pattern: "^([0-9]+)-([a-zA-Z0-9-]+)$")
Expand All @@ -26,7 +26,7 @@ func ensureAppIdFormat(_ id: String) throws -> String {

func appAlias(fromId id: String) throws -> String {
let appId = try ensureAppIdFormat(id)
guard let alias = appId.split(separator: "/").last else {
guard let alias = appId.split(separator: "/").dropFirst().first else {
throw FalError.invalidUrl(url: id)
}
return String(describing: alias)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import FalClient
import SwiftUI

// See https://www.fal.ai/models/latent-consistency-sd/api for API documentation

let OptimizedLatentConsistency = "fal-ai/lcm-sd15-i2i"

struct LcmInput: Encodable {
let prompt: String
let image: FalImageContent
Expand All @@ -28,12 +24,13 @@ class LiveImage: ObservableObject {

// This example demonstrates the support to Codable types, but
// RealtimeConnection can also be used for untyped input / output
// using dictionary-like ObjectValue
// using dictionary-like Payload
private var connection: TypedRealtimeConnection<LcmInput>?

init() {
connection = try? fal.realtime.connect(
to: OptimizedLatentConsistency,
// See https://fal.ai/models/latent-consistency-sd/api
to: "fal-ai/lcm-sd15-i2i",
connectionKey: "PencilKitDemo",
throttleInterval: .milliseconds(128)
) { (result: Result<LcmResponse, Error>) in
Expand Down
Loading