Skip to content
Merged
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
177 changes: 177 additions & 0 deletions Sources/SwiftApiAdapter/Loader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,185 @@ import Foundation
import SwiftyJSON
import SwiftSoup

#if canImport(ImagePlayground)
import ImagePlayground
#endif

#if canImport(FoundationModels)
import FoundationModels
#endif

#if canImport(UIKit)
import UIKit
#endif

#if canImport(AppKit) && !targetEnvironment(macCatalyst)
import AppKit
#endif

#if canImport(ImagePlayground)
@available(iOS 18.4, macOS 15.4, visionOS 2.4, *)
extension ImagePlaygroundStyle {
init?(string: String) {
switch string.lowercased() {
case "animation": self = .animation
case "illustration": self = .illustration
case "sketch": self = .sketch
default: return nil
}
}
}
#endif

private func ipgPNGData(from cgImage: CGImage) -> Data? {
#if canImport(UIKit)
return UIImage(cgImage: cgImage).pngData()
#elseif canImport(AppKit) && !targetEnvironment(macCatalyst)
let rep = NSBitmapImageRep(cgImage: cgImage)
return rep.representation(using: .png, properties: [:])
#else
return nil
#endif
}

enum ImagePlaygroundGeneratorError: Error {
case osTooOld
case generationFailed
}

struct ImagePlaygroundGenerator {
static func generate(prompt: String,
styleString: String,
limit: Int) async throws -> String {
#if canImport(ImagePlayground)
guard #available(iOS 18.4, macOS 15.4, visionOS 2.4, *) else {
throw ImagePlaygroundGeneratorError.osTooOld
}

let style = ImagePlaygroundStyle(string: styleString) ?? .animation
let creator = try await ImageCreator()
let seq = creator.images(for: [.text(prompt)], style: style, limit: limit)

if let first = try await seq.first(where: { _ in true }),
let png = ipgPNGData(from: first.cgImage) {
return png.base64EncodedString()
}
throw ImagePlaygroundGeneratorError.generationFailed
#else
throw ImagePlaygroundGeneratorError.osTooOld
#endif
}
}

private struct ImagePlaygroundRequestBody: Decodable {
var prompt: String
var style: String
var limit: Int?
}

enum FoundationModelsGeneratorError: Error {
case osTooOld
case generationFailed
}

@available(iOS 26.0, macOS 26.0, visionOS 26.0, *)
struct FoundationModelsGenerator {
static func generate(prompt: String,
instructions: String? = nil,
temperature: Double? = nil,
maxTokens: Int? = nil) async throws -> String {

#if canImport(FoundationModels)
// Create or reuse a session
let session: LanguageModelSession = {
if let instructions, !instructions.isEmpty {
return LanguageModelSession(instructions: instructions)
} else {
return LanguageModelSession()
}
}()

// Prepare generation options (all are optional)
var opts = GenerationOptions()
if let temperature { opts.temperature = temperature }
if let maxTokens { opts.maximumResponseTokens = maxTokens }

// Ask the model
let response = try await session.respond(to: prompt, options: opts)
return response.content
#else
throw FoundationModelsGeneratorError.osTooOld
#endif
}
}

private struct FoundationModelsRequestBody: Decodable {
let prompt: String
let instructions: String?
let temperature: Double?
let maxTokens: Int?
}

public class ApiContentLoader {
public static func load(contextId: UUID, apiContent: ApiContent) async throws -> ApiContentRack? {
if apiContent.endpoint.hasPrefix("imageplayground://") {
if let data = apiContent.body.data(using: .utf8) {
do {
let body = try JSONDecoder().decode(ImagePlaygroundRequestBody.self, from: data)
let base64 = try await ImagePlaygroundGenerator.generate(
prompt: body.prompt,
styleString: body.style,
limit: body.limit ?? 1
)
return ApiContentRack(id: apiContent.id, arguments: ["base64image": base64])
} catch {
#if DEBUG
print("[ApiContentLoader] ImagePlayground generation failed: \(error)")
#endif
return nil
}
} else {
return nil
}
}

if apiContent.endpoint.hasPrefix("foundationmodels://") {
guard let data = apiContent.body.data(using: .utf8) else { return nil }

do {
let body = try JSONDecoder().decode(FoundationModelsRequestBody.self, from: data)

#if canImport(FoundationModels)
if #available(iOS 26.0, macOS 26.0, visionOS 26.0, *) {
let generated = try await FoundationModelsGenerator.generate(
prompt: body.prompt,
instructions: body.instructions,
temperature: body.temperature,
maxTokens: body.maxTokens
)

return ApiContentRack(
id: apiContent.id,
arguments: ["text": generated]
)
} else {
#if DEBUG
print("[ApiContentLoader] FoundationModels not supported on this OS version")
#endif
return nil
}
#else
return nil
#endif

} catch {
#if DEBUG
print("[ApiContentLoader] FoundationModels generation failed: \(error)")
#endif
return nil
}
}

let endpoint = apiContent.endpoint

guard let url = URL(string: endpoint) else {
Expand Down