Repository: lzell/AIProxyBootstrap Branch: main Commit: 1126c9e54d7c Files: 244 Total size: 588.8 KB Directory structure: gitextract_weqk_mf7/ ├── .gitignore ├── AIProxyAnthropic/ │ ├── AIProxyAnthropic/ │ │ ├── AIProxyAnthropicApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── anthropic.imageset/ │ │ │ │ └── Contents.json │ │ │ └── climber.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── MessageRequestView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ ├── ToolsView.swift │ │ └── VisionView.swift │ └── AIProxyAnthropic.xcodeproj/ │ └── project.pbxproj ├── AIProxyDeepL/ │ ├── AIProxyDeepL/ │ │ ├── AIProxyDeepLApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── deepl.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── TranslationView.swift │ └── AIProxyDeepL.xcodeproj/ │ └── project.pbxproj ├── AIProxyFal/ │ ├── AIProxyFal/ │ │ ├── AIProxyFalApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── fal.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── TextToImageView.swift │ └── AIProxyFal.xcodeproj/ │ └── project.pbxproj ├── AIProxyGemini/ │ ├── AIProxyGemini/ │ │ ├── AIProxyGeminiApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── icon.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── TextGenerationView.swift │ └── AIProxyGemini.xcodeproj/ │ └── project.pbxproj ├── AIProxyGroq/ │ ├── AIProxyGroq/ │ │ ├── AIProxyGroqApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── groq.imageset/ │ │ │ └── Contents.json │ │ ├── ChatView.swift │ │ ├── ContentView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── StreamingChatView.swift │ └── AIProxyGroq.xcodeproj/ │ └── project.pbxproj ├── AIProxyOpenAI/ │ ├── AIProxyOpenAI/ │ │ ├── AIProxyOpenAIApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── openai.imageset/ │ │ │ │ └── Contents.json │ │ │ └── surfer.imageset/ │ │ │ └── Contents.json │ │ ├── ChatView.swift │ │ ├── ContentView.swift │ │ ├── DalleView.swift │ │ ├── MultiModalChatView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ ├── StreamingChatView.swift │ │ └── TextToSpeechView.swift │ └── AIProxyOpenAI.xcodeproj/ │ └── project.pbxproj ├── AIProxyReplicate/ │ ├── AIProxyReplicate/ │ │ ├── AIProxyReplicateApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── replicate.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── ImageGenView.swift │ │ └── Preview Content/ │ │ └── Preview Assets.xcassets/ │ │ └── Contents.json │ └── AIProxyReplicate.xcodeproj/ │ └── project.pbxproj ├── AIProxyStabilityAI/ │ ├── AIProxyStabilityAI/ │ │ ├── AIProxyStabilityAIApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── stability.imageset/ │ │ │ └── Contents.json │ │ ├── ContentView.swift │ │ ├── ImageGenView.swift │ │ └── Preview Content/ │ │ └── Preview Assets.xcassets/ │ │ └── Contents.json │ └── AIProxyStabilityAI.xcodeproj/ │ └── project.pbxproj ├── AIProxyTogetherAI/ │ ├── AIProxyTogetherAI/ │ │ ├── AIProxyTogetherAIApp.swift │ │ ├── AppConstants.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── togetherai.imageset/ │ │ │ └── Contents.json │ │ ├── ChatView.swift │ │ ├── ContentView.swift │ │ ├── JSONResponseView.swift │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── StreamingChatView.swift │ └── AIProxyTogetherAI.xcodeproj/ │ └── project.pbxproj ├── Demos/ │ ├── AIColorPalette/ │ │ ├── AIColorPalette/ │ │ │ ├── AIColorPaletteApp.swift │ │ │ ├── AIProxyIntegration.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── palm.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ColorData.swift │ │ │ ├── ColorDetailView.swift │ │ │ ├── ContentView.swift │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── Ripple.metal │ │ │ └── Ripple.swift │ │ ├── AIColorPalette.xcodeproj/ │ │ │ └── project.pbxproj │ │ └── README.md │ ├── Chat/ │ │ ├── Chat/ │ │ │ ├── AppConstants.swift │ │ │ ├── AppLogger.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ChatApp.swift │ │ │ ├── ChatBubble.swift │ │ │ ├── ChatDataLoader.swift │ │ │ ├── ChatInputView.swift │ │ │ ├── ChatManager.swift │ │ │ ├── ChatMessage.swift │ │ │ ├── ChatView.swift │ │ │ └── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── Chat.xcodeproj/ │ │ └── project.pbxproj │ ├── Classifier/ │ │ ├── Classifier/ │ │ │ ├── AppConstants.swift │ │ │ ├── AppLogger.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── CameraControlsView.swift │ │ │ ├── CameraDataLoader.swift │ │ │ ├── CameraFrameManager.swift │ │ │ ├── CameraView.swift │ │ │ ├── ClassifierApp.swift │ │ │ ├── ClassifierDataLoader.swift │ │ │ ├── ClassifierManager.swift │ │ │ ├── ClassifierView.swift │ │ │ └── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── Classifier.xcodeproj/ │ │ └── project.pbxproj │ ├── EmojiPuzzleMaker/ │ │ ├── EmojiPuzzleMaker/ │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── EmojiPuzzleMakerApp.swift │ │ │ └── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ └── EmojiPuzzleMaker.xcodeproj/ │ │ └── project.pbxproj │ ├── FilmFinder/ │ │ ├── FilmFinder/ │ │ │ ├── AppConstants.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── FilmFinderApp.swift │ │ │ ├── GenreSelectorView.swift │ │ │ ├── GetStartedTip.swift │ │ │ ├── Info.plist │ │ │ ├── Movie.swift │ │ │ ├── MovieDetailsView.swift │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── Ripple.metal │ │ │ └── Ripple.swift │ │ └── FilmFinder.xcodeproj/ │ │ └── project.pbxproj │ ├── PuLIDDemo/ │ │ ├── PuLIDDemo/ │ │ │ ├── AppConstants.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── pulid.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── ContentView.swift │ │ │ ├── Info.plist │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── PuLIDDemoApp.swift │ │ │ ├── Ripple.metal │ │ │ └── Ripple.swift │ │ └── PuLIDDemo.xcodeproj/ │ │ └── project.pbxproj │ ├── Stickers/ │ │ ├── Stickers/ │ │ │ ├── AppConstants.swift │ │ │ ├── AppLogger.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── ButtonStyles.swift │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── StickerDataLoader.swift │ │ │ ├── StickerImageView.swift │ │ │ ├── StickerInputView.swift │ │ │ ├── StickerLoadingView.swift │ │ │ ├── StickerManager.swift │ │ │ ├── StickerView.swift │ │ │ └── StickersApp.swift │ │ └── Stickers.xcodeproj/ │ │ └── project.pbxproj │ ├── Transcriber/ │ │ ├── Transcriber/ │ │ │ ├── AppConstants.swift │ │ │ ├── AppLogger.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── AudioFileWriter.swift │ │ │ ├── AudioRecorder.swift │ │ │ ├── AudioRecording.swift │ │ │ ├── ButtonStyles.swift │ │ │ ├── FileUtils.swift │ │ │ ├── MicrophoneSampleVendor.swift │ │ │ ├── ModelContext+Extensions.swift │ │ │ ├── NoRecordingsView.swift │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── RecordingRowView.swift │ │ │ ├── TranscribedAudioRecording.swift │ │ │ ├── TranscriberApp.swift │ │ │ ├── TranscriberDataLoader.swift │ │ │ ├── TranscriberManager.swift │ │ │ └── TranscriberView.swift │ │ └── Transcriber.xcodeproj/ │ │ └── project.pbxproj │ ├── Translator/ │ │ ├── Translator/ │ │ │ ├── AppConstants.swift │ │ │ ├── AppLogger.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AccentColor.colorset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── BottomTranslateView.swift │ │ │ ├── ButtonStyles.swift │ │ │ ├── Preview Content/ │ │ │ │ └── Preview Assets.xcassets/ │ │ │ │ └── Contents.json │ │ │ ├── TopTranslateView.swift │ │ │ ├── TranslateView.swift │ │ │ ├── TranslationDataLoader.swift │ │ │ └── TranslatorApp.swift │ │ └── Translator.xcodeproj/ │ │ └── project.pbxproj │ └── Trivia/ │ ├── Trivia/ │ │ ├── AppConstants.swift │ │ ├── AppLogger.swift │ │ ├── Assets.xcassets/ │ │ │ ├── AccentColor.colorset/ │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Preview Content/ │ │ │ └── Preview Assets.xcassets/ │ │ │ └── Contents.json │ │ ├── TriviaAnswerPicker.swift │ │ ├── TriviaApp.swift │ │ ├── TriviaCardData.swift │ │ ├── TriviaCardView.swift │ │ ├── TriviaDataLoader.swift │ │ ├── TriviaFormView.swift │ │ ├── TriviaManager.swift │ │ └── TriviaView.swift │ └── Trivia.xcodeproj/ │ └── project.pbxproj └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store *.swp tags xcshareddata project.xcworkspace .ackrc *.codekit3 # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## User settings xcuserdata/ ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint *.xccheckout ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) build/ DerivedData/ *.moved-aside *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 ## Obj-C/Swift specific *.hmap ## App packaging *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins # Package.resolved # *.xcodeproj # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project # .swiftpm .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace # *.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build/ # Accio dependency management Dependencies/ .accio/ # fastlane # # It is recommended to not store the screenshots in the git repo. # Instead, use fastlane to re-generate the screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/#source-control fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output # Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/AIProxyAnthropicApp.swift ================================================ // // AIProxyAnthropicApp.swift // AIProxyAnthropic // // Created by Todd Hamilton on 6/17/24. // import SwiftUI @main struct AIProxyAnthropicApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyAnthropic // // Created by Todd Hamilton on 8/14/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let anthropicService = AIProxy.anthropicDirectService( unprotectedAPIKey: "your-anthropic-key" ) /* Uncomment for all other production use cases */ //let anthropicService = AIProxy.anthropicService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Assets.xcassets/anthropic.imageset/Contents.json ================================================ { "images" : [ { "filename" : "anthropic.jpeg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Assets.xcassets/climber.imageset/Contents.json ================================================ { "images" : [ { "filename" : "climber.jpg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/ContentView.swift ================================================ // // ContentView.swift // AIProxyAnthropic // // Created by Todd Hamilton on 6/17/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:24){ VStack{ Image("anthropic") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Anthropic") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Message Request Example",destination: MessageRequestView()) .bold() .controlSize(.large) .tint(.brown) .buttonStyle(.bordered) NavigationLink("Vision Example",destination: VisionView()) .bold() .controlSize(.large) .tint(.brown) .buttonStyle(.bordered) NavigationLink("Tools Example",destination: ToolsView()) .bold() .controlSize(.large) .tint(.brown) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/MessageRequestView.swift ================================================ // // MessageRequestView.swift // AIProxyAnthropic // // Created by Todd Hamilton on 8/14/24. // import SwiftUI import AIProxy struct MessageRequestView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let response = try await anthropicService.messageRequest(body: AnthropicMessageRequestBody( maxTokens: 1024, messages: [ AnthropicInputMessage(content: [.text(prompt)], role: .user) ], model: "claude-3-5-sonnet-20240620" )) for content in response.content { switch content { case .text(let message): print("Claude sent a message: \(message)") result = message showingAlert = true case .toolUse(id: _, name: let toolName, input: let toolInput): print("Claude used a tool \(toolName) with input: \(toolInput)") } } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Message", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Message") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Message Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { MessageRequestView() } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/ToolsView.swift ================================================ // // ToolsView.swift // AIProxyAnthropic // // Created by Todd Hamilton on 8/14/24. // import SwiftUI import AIProxy struct ToolsView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false var body: some View { VStack { VStack{ ContentUnavailableView( "Stock Symbol Lookup", systemImage: "chart.line.uptrend.xyaxis", description: Text("Type the name of the company you want the symbol for.") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a company", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Look Up") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Tools Example") .navigationBarTitleDisplayMode(.inline) } func generate() async throws { isLoading = true defer { isLoading = false } do { let requestBody = AnthropicMessageRequestBody( maxTokens: 1024, messages: [ .init( content: [.text(prompt)], role: .user ) ], model: "claude-3-5-sonnet-20240620", tools: [ .init( description: "Call this function when the user wants a stock symbol", inputSchema: [ "type": "object", "properties": [ "ticker": [ "type": "string", "description": "The stock ticker symbol, e.g. AAPL for Apple Inc." ] ], "required": ["ticker"] ], name: "get_stock_symbol" ) ] ) let response = try await anthropicService.messageRequest(body: requestBody) for content in response.content { switch content { case .text(let message): print("Claude sent a message: \(message)") case .toolUse(id: _, name: let toolName, input: let toolInput): print("Claude used a tool \(toolName) with input: \(toolInput)") result = toolInput.description showingAlert = true } } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } } #Preview { ToolsView() } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic/VisionView.swift ================================================ // // VisionView.swift // AIProxyAnthropic // // Created by Todd Hamilton on 8/14/24. // import SwiftUI import AIProxy struct VisionView: View { @State private var prompt:String = "" @State private var result:String = "" @State private var showingAlert = false @State private var isLoading = false func generate() async throws { guard let image = UIImage(named: "climber") else { print("Could not find an image named 'climber' in your app assets") return } guard let jpegData = AIProxy.encodeImageAsJpeg(image: image, compressionQuality: 0.8) else { print("Could not convert image to jpeg") return } isLoading = true defer { isLoading = false } do { let response = try await anthropicService.messageRequest(body: AnthropicMessageRequestBody( maxTokens: 1024, messages: [ AnthropicInputMessage(content: [ .text("Provide a very short description of this image"), .image(mediaType: .jpeg, data: jpegData.base64EncodedString()) ], role: .user) ], model: "claude-3-5-sonnet-20240620" )) for content in response.content { switch content { case .text(let message): print("Claude sent a message: \(message)") result = message showingAlert = true case .toolUse(id: _, name: let toolName, input: let toolInput): print("Claude used a tool \(toolName) with input: \(toolInput)") } } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack{ VStack{ Image("climber") .resizable() .scaledToFit() .frame(maxWidth: .infinity) } .frame(maxHeight: .infinity) .alert(isPresented: $showingAlert){ Alert( title: Text("Result"), message: Text("\(result)"), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ Button{ Task { try await generate() } }label: { if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Describe Image") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Vision Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { VisionView() } ================================================ FILE: AIProxyAnthropic/AIProxyAnthropic.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C06E1442C20DFDB0024133C /* AIProxyAnthropicApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C06E1432C20DFDB0024133C /* AIProxyAnthropicApp.swift */; }; 2C06E1462C20DFDB0024133C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C06E1452C20DFDB0024133C /* ContentView.swift */; }; 2C06E1482C20DFDD0024133C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C06E1472C20DFDD0024133C /* Assets.xcassets */; }; 2C06E14B2C20DFDD0024133C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C06E14A2C20DFDD0024133C /* Preview Assets.xcassets */; }; 2CCB20382C6D13EE003A8B25 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20372C6D13EE003A8B25 /* AppConstants.swift */; }; 2CCB203B2C6D13F4003A8B25 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CCB203A2C6D13F4003A8B25 /* AIProxy */; }; 2CCB203D2C6D1464003A8B25 /* MessageRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB203C2C6D1464003A8B25 /* MessageRequestView.swift */; }; 2CCB203F2C6D27D9003A8B25 /* VisionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB203E2C6D27D9003A8B25 /* VisionView.swift */; }; 2CCB20412C6D3072003A8B25 /* ToolsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20402C6D3072003A8B25 /* ToolsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C06E1402C20DFDB0024133C /* AIProxyAnthropic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyAnthropic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2C06E1432C20DFDB0024133C /* AIProxyAnthropicApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyAnthropicApp.swift; sourceTree = ""; }; 2C06E1452C20DFDB0024133C /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2C06E1472C20DFDD0024133C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2C06E14A2C20DFDD0024133C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CCB20372C6D13EE003A8B25 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 2CCB203C2C6D1464003A8B25 /* MessageRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestView.swift; sourceTree = ""; }; 2CCB203E2C6D27D9003A8B25 /* VisionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisionView.swift; sourceTree = ""; }; 2CCB20402C6D3072003A8B25 /* ToolsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2C06E13D2C20DFDB0024133C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CCB203B2C6D13F4003A8B25 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C06E1372C20DFDB0024133C = { isa = PBXGroup; children = ( 2C06E1422C20DFDB0024133C /* AIProxyAnthropic */, 2C06E1412C20DFDB0024133C /* Products */, ); sourceTree = ""; }; 2C06E1412C20DFDB0024133C /* Products */ = { isa = PBXGroup; children = ( 2C06E1402C20DFDB0024133C /* AIProxyAnthropic.app */, ); name = Products; sourceTree = ""; }; 2C06E1422C20DFDB0024133C /* AIProxyAnthropic */ = { isa = PBXGroup; children = ( 2C06E1432C20DFDB0024133C /* AIProxyAnthropicApp.swift */, 2C06E1452C20DFDB0024133C /* ContentView.swift */, 2CCB203C2C6D1464003A8B25 /* MessageRequestView.swift */, 2CCB203E2C6D27D9003A8B25 /* VisionView.swift */, 2CCB20402C6D3072003A8B25 /* ToolsView.swift */, 2CCB20372C6D13EE003A8B25 /* AppConstants.swift */, 2C06E1472C20DFDD0024133C /* Assets.xcassets */, 2C06E1492C20DFDD0024133C /* Preview Content */, ); path = AIProxyAnthropic; sourceTree = ""; }; 2C06E1492C20DFDD0024133C /* Preview Content */ = { isa = PBXGroup; children = ( 2C06E14A2C20DFDD0024133C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C06E13F2C20DFDB0024133C /* AIProxyAnthropic */ = { isa = PBXNativeTarget; buildConfigurationList = 2C06E14E2C20DFDD0024133C /* Build configuration list for PBXNativeTarget "AIProxyAnthropic" */; buildPhases = ( 2C06E13C2C20DFDB0024133C /* Sources */, 2C06E13D2C20DFDB0024133C /* Frameworks */, 2C06E13E2C20DFDB0024133C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyAnthropic; packageProductDependencies = ( 2CCB203A2C6D13F4003A8B25 /* AIProxy */, ); productName = AIProxyAnthropic; productReference = 2C06E1402C20DFDB0024133C /* AIProxyAnthropic.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C06E1382C20DFDB0024133C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2C06E13F2C20DFDB0024133C = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2C06E13B2C20DFDB0024133C /* Build configuration list for PBXProject "AIProxyAnthropic" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C06E1372C20DFDB0024133C; packageReferences = ( 2CCB20392C6D13F4003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2C06E1412C20DFDB0024133C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C06E13F2C20DFDB0024133C /* AIProxyAnthropic */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C06E13E2C20DFDB0024133C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2C06E14B2C20DFDD0024133C /* Preview Assets.xcassets in Resources */, 2C06E1482C20DFDD0024133C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C06E13C2C20DFDB0024133C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CCB20412C6D3072003A8B25 /* ToolsView.swift in Sources */, 2CCB203D2C6D1464003A8B25 /* MessageRequestView.swift in Sources */, 2C06E1462C20DFDB0024133C /* ContentView.swift in Sources */, 2CCB20382C6D13EE003A8B25 /* AppConstants.swift in Sources */, 2CCB203F2C6D27D9003A8B25 /* VisionView.swift in Sources */, 2C06E1442C20DFDB0024133C /* AIProxyAnthropicApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C06E14C2C20DFDD0024133C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C06E14D2C20DFDD0024133C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C06E14F2C20DFDD0024133C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyAnthropic/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C06E1502C20DFDD0024133C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyAnthropic/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C06E13B2C20DFDB0024133C /* Build configuration list for PBXProject "AIProxyAnthropic" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C06E14C2C20DFDD0024133C /* Debug */, 2C06E14D2C20DFDD0024133C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C06E14E2C20DFDD0024133C /* Build configuration list for PBXNativeTarget "AIProxyAnthropic" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C06E14F2C20DFDD0024133C /* Debug */, 2C06E1502C20DFDD0024133C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CCB20392C6D13F4003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CCB203A2C6D13F4003A8B25 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CCB20392C6D13F4003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C06E1382C20DFDB0024133C /* Project object */; } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/AIProxyDeepLApp.swift ================================================ // // AIProxyDeepLApp.swift // AIProxyDeepL // // Created by Todd Hamilton on 8/14/24. // import SwiftUI @main struct AIProxyDeepLApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyDeepL // // Created by Todd Hamilton on 8/14/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let deepLService = AIProxy.deepLDirectService( unprotectedAPIKey: "your-deepL-key", accountType: .free ) /* Uncomment for all other production use cases */ //let deepLService = AIProxy.deepLService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyDeepL/AIProxyDeepL/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/Assets.xcassets/deepl.imageset/Contents.json ================================================ { "images" : [ { "filename" : "deepl.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/ContentView.swift ================================================ // // ContentView.swift // AIProxyDeepL // // Created by Todd Hamilton on 8/14/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:48){ VStack{ Image("deepl") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("DeepL") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Translation Example",destination: TranslationView()) .bold() .controlSize(.large) .tint(.blue) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyDeepL/AIProxyDeepL/TranslationView.swift ================================================ // // TranslationView.swift // AIProxyDeepL // // Created by Todd Hamilton on 8/14/24. // import SwiftUI import AIProxy struct TranslationView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let body = DeepLTranslateRequestBody(targetLang: "ES", text: [prompt]) let response = try await deepLService.translateRequest(body: body) // Do something with `response.translations` result = response.translations.first?.text ?? "" showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create translation: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Translate to Spanish", systemImage: "captions.bubble.fill", description: Text("Write text you want to translate below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type your text here", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Translate") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Translate Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { TranslationView() } ================================================ FILE: AIProxyDeepL/AIProxyDeepL.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2CCB204F2C6D5FB8003A8B25 /* AIProxyDeepLApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB204E2C6D5FB8003A8B25 /* AIProxyDeepLApp.swift */; }; 2CCB20512C6D5FB8003A8B25 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20502C6D5FB8003A8B25 /* ContentView.swift */; }; 2CCB20532C6D5FBA003A8B25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CCB20522C6D5FBA003A8B25 /* Assets.xcassets */; }; 2CCB20562C6D5FBA003A8B25 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CCB20552C6D5FBA003A8B25 /* Preview Assets.xcassets */; }; 2CCB205E2C6D5FC1003A8B25 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CCB205D2C6D5FC1003A8B25 /* AIProxy */; }; 2CCB20602C6D6012003A8B25 /* TranslationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB205F2C6D6012003A8B25 /* TranslationView.swift */; }; 2CCB20622C6D60B1003A8B25 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20612C6D60B1003A8B25 /* AppConstants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2CCB204B2C6D5FB8003A8B25 /* AIProxyDeepL.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyDeepL.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CCB204E2C6D5FB8003A8B25 /* AIProxyDeepLApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyDeepLApp.swift; sourceTree = ""; }; 2CCB20502C6D5FB8003A8B25 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CCB20522C6D5FBA003A8B25 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CCB20552C6D5FBA003A8B25 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CCB205F2C6D6012003A8B25 /* TranslationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationView.swift; sourceTree = ""; }; 2CCB20612C6D60B1003A8B25 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CCB20482C6D5FB8003A8B25 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CCB205E2C6D5FC1003A8B25 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CCB20422C6D5FB8003A8B25 = { isa = PBXGroup; children = ( 2CCB204D2C6D5FB8003A8B25 /* AIProxyDeepL */, 2CCB204C2C6D5FB8003A8B25 /* Products */, ); sourceTree = ""; }; 2CCB204C2C6D5FB8003A8B25 /* Products */ = { isa = PBXGroup; children = ( 2CCB204B2C6D5FB8003A8B25 /* AIProxyDeepL.app */, ); name = Products; sourceTree = ""; }; 2CCB204D2C6D5FB8003A8B25 /* AIProxyDeepL */ = { isa = PBXGroup; children = ( 2CCB204E2C6D5FB8003A8B25 /* AIProxyDeepLApp.swift */, 2CCB20502C6D5FB8003A8B25 /* ContentView.swift */, 2CCB205F2C6D6012003A8B25 /* TranslationView.swift */, 2CCB20612C6D60B1003A8B25 /* AppConstants.swift */, 2CCB20522C6D5FBA003A8B25 /* Assets.xcassets */, 2CCB20542C6D5FBA003A8B25 /* Preview Content */, ); path = AIProxyDeepL; sourceTree = ""; }; 2CCB20542C6D5FBA003A8B25 /* Preview Content */ = { isa = PBXGroup; children = ( 2CCB20552C6D5FBA003A8B25 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CCB204A2C6D5FB8003A8B25 /* AIProxyDeepL */ = { isa = PBXNativeTarget; buildConfigurationList = 2CCB20592C6D5FBA003A8B25 /* Build configuration list for PBXNativeTarget "AIProxyDeepL" */; buildPhases = ( 2CCB20472C6D5FB8003A8B25 /* Sources */, 2CCB20482C6D5FB8003A8B25 /* Frameworks */, 2CCB20492C6D5FB8003A8B25 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyDeepL; packageProductDependencies = ( 2CCB205D2C6D5FC1003A8B25 /* AIProxy */, ); productName = AIProxyDeepL; productReference = 2CCB204B2C6D5FB8003A8B25 /* AIProxyDeepL.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CCB20432C6D5FB8003A8B25 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CCB204A2C6D5FB8003A8B25 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CCB20462C6D5FB8003A8B25 /* Build configuration list for PBXProject "AIProxyDeepL" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CCB20422C6D5FB8003A8B25; packageReferences = ( 2CCB205C2C6D5FC1003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CCB204C2C6D5FB8003A8B25 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CCB204A2C6D5FB8003A8B25 /* AIProxyDeepL */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CCB20492C6D5FB8003A8B25 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CCB20562C6D5FBA003A8B25 /* Preview Assets.xcassets in Resources */, 2CCB20532C6D5FBA003A8B25 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CCB20472C6D5FB8003A8B25 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CCB20622C6D60B1003A8B25 /* AppConstants.swift in Sources */, 2CCB20602C6D6012003A8B25 /* TranslationView.swift in Sources */, 2CCB20512C6D5FB8003A8B25 /* ContentView.swift in Sources */, 2CCB204F2C6D5FB8003A8B25 /* AIProxyDeepLApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CCB20572C6D5FBA003A8B25 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CCB20582C6D5FBA003A8B25 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CCB205A2C6D5FBA003A8B25 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyDeepL/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CCB205B2C6D5FBA003A8B25 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyDeepL/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CCB20462C6D5FB8003A8B25 /* Build configuration list for PBXProject "AIProxyDeepL" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CCB20572C6D5FBA003A8B25 /* Debug */, 2CCB20582C6D5FBA003A8B25 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CCB20592C6D5FBA003A8B25 /* Build configuration list for PBXNativeTarget "AIProxyDeepL" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CCB205A2C6D5FBA003A8B25 /* Debug */, 2CCB205B2C6D5FBA003A8B25 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CCB205C2C6D5FC1003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CCB205D2C6D5FC1003A8B25 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CCB205C2C6D5FC1003A8B25 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CCB20432C6D5FB8003A8B25 /* Project object */; } ================================================ FILE: AIProxyFal/AIProxyFal/AIProxyFalApp.swift ================================================ // // AIProxyFalApp.swift // AIProxyFal // // Created by Todd Hamilton on 6/13/24. // import SwiftUI @main struct AIProxyFalApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyFal/AIProxyFal/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyFal // // Created by Todd Hamilton on 9/17/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let falService = AIProxy.falDirectService( unprotectedAPIKey: "your-fal-key" ) /* Uncomment for all other production use cases */ //let falService = AIProxy.falService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyFal/AIProxyFal/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyFal/AIProxyFal/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyFal/AIProxyFal/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyFal/AIProxyFal/Assets.xcassets/fal.imageset/Contents.json ================================================ { "images" : [ { "filename" : "fal.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyFal/AIProxyFal/ContentView.swift ================================================ // // ContentView.swift // AIProxyFal // // Created by Todd Hamilton on 6/13/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:24){ VStack{ Image("fal") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Fal") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Text to Image with FastSDXL",destination: TextToImageView()) .bold() .controlSize(.large) .tint(.indigo) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyFal/AIProxyFal/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyFal/AIProxyFal/TextToImageView.swift ================================================ // // ImageGenView.swift // AIProxyFal // // Created by Todd Hamilton on 6/13/24. // import SwiftUI import AIProxy struct TextToImageView: View { @State private var prompt: String = "" @State private var imageUrl: String? @State private var isLoading: Bool = false private func generate() async throws { let input = FalFastSDXLInputSchema( prompt: prompt, enableSafetyChecker: false ) isLoading = true // Start loading defer { isLoading = false } do { let output = try await falService.createFastSDXLImage(input: input) print(""" The first output image is at \(output.images?.first?.url?.absoluteString ?? "") It took \(output.timings?.inference ?? Double.nan) seconds to generate. """) imageUrl = output.images?.first?.url?.absoluteString } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create Fal SDXL image: \(error.localizedDescription)") } } var body: some View { VStack{ VStack{ if (imageUrl != nil) { AsyncImage(url: URL(string: imageUrl!)) { phase in if let image = phase.image { image .resizable() .aspectRatio(contentMode: .fit) } else if phase.error != nil { Text("Failed to load image") .foregroundColor(.red) } else { ProgressView() } } } else{ ContentUnavailableView( "Generate an image", systemImage: "photo.fill", description: Text("Write a prompt below") ) } } .frame(maxHeight: .infinity) Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Image") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Generate Image") .navigationBarTitleDisplayMode(.inline) } } #Preview { TextToImageView() } ================================================ FILE: AIProxyFal/AIProxyFal.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C20F29C2CB0EB31001ECE32 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C20F29B2CB0EB31001ECE32 /* AIProxy */; }; 2C63D7772D232C03008CFE5B /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C63D7762D232C03008CFE5B /* AIProxy */; }; 2C885B432CB1CD0A00C23BAD /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C885B422CB1CD0A00C23BAD /* AIProxy */; }; 2CD964C92C1BD144006DAD57 /* AIProxyFalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964C82C1BD144006DAD57 /* AIProxyFalApp.swift */; }; 2CD964CB2C1BD144006DAD57 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964CA2C1BD144006DAD57 /* ContentView.swift */; }; 2CD964CD2C1BD145006DAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD964CC2C1BD145006DAD57 /* Assets.xcassets */; }; 2CD964D02C1BD145006DAD57 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD964CF2C1BD145006DAD57 /* Preview Assets.xcassets */; }; 2CD964D72C1BD186006DAD57 /* TextToImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964D62C1BD186006DAD57 /* TextToImageView.swift */; }; 2CDD52932C9A2A940052700C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDD52922C9A2A940052700C /* AppConstants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2CD964C52C1BD144006DAD57 /* AIProxyFal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyFal.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CD964C82C1BD144006DAD57 /* AIProxyFalApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyFalApp.swift; sourceTree = ""; }; 2CD964CA2C1BD144006DAD57 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CD964CC2C1BD145006DAD57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CD964CF2C1BD145006DAD57 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CD964D62C1BD186006DAD57 /* TextToImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextToImageView.swift; sourceTree = ""; }; 2CDD52922C9A2A940052700C /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CD964C22C1BD144006DAD57 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C63D7772D232C03008CFE5B /* AIProxy in Frameworks */, 2C20F29C2CB0EB31001ECE32 /* AIProxy in Frameworks */, 2C885B432CB1CD0A00C23BAD /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CD964BC2C1BD144006DAD57 = { isa = PBXGroup; children = ( 2CD964C72C1BD144006DAD57 /* AIProxyFal */, 2CD964C62C1BD144006DAD57 /* Products */, ); sourceTree = ""; }; 2CD964C62C1BD144006DAD57 /* Products */ = { isa = PBXGroup; children = ( 2CD964C52C1BD144006DAD57 /* AIProxyFal.app */, ); name = Products; sourceTree = ""; }; 2CD964C72C1BD144006DAD57 /* AIProxyFal */ = { isa = PBXGroup; children = ( 2CD964C82C1BD144006DAD57 /* AIProxyFalApp.swift */, 2CD964CA2C1BD144006DAD57 /* ContentView.swift */, 2CD964D62C1BD186006DAD57 /* TextToImageView.swift */, 2CDD52922C9A2A940052700C /* AppConstants.swift */, 2CD964CC2C1BD145006DAD57 /* Assets.xcassets */, 2CD964CE2C1BD145006DAD57 /* Preview Content */, ); path = AIProxyFal; sourceTree = ""; }; 2CD964CE2C1BD145006DAD57 /* Preview Content */ = { isa = PBXGroup; children = ( 2CD964CF2C1BD145006DAD57 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CD964C42C1BD144006DAD57 /* AIProxyFal */ = { isa = PBXNativeTarget; buildConfigurationList = 2CD964D32C1BD145006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyFal" */; buildPhases = ( 2CD964C12C1BD144006DAD57 /* Sources */, 2CD964C22C1BD144006DAD57 /* Frameworks */, 2CD964C32C1BD144006DAD57 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyFal; packageProductDependencies = ( 2C20F29B2CB0EB31001ECE32 /* AIProxy */, 2C885B422CB1CD0A00C23BAD /* AIProxy */, 2C63D7762D232C03008CFE5B /* AIProxy */, ); productName = AIProxyFal; productReference = 2CD964C52C1BD144006DAD57 /* AIProxyFal.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CD964BD2C1BD144006DAD57 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CD964C42C1BD144006DAD57 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CD964C02C1BD144006DAD57 /* Build configuration list for PBXProject "AIProxyFal" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CD964BC2C1BD144006DAD57; packageReferences = ( 2C63D7752D232C03008CFE5B /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CD964C62C1BD144006DAD57 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CD964C42C1BD144006DAD57 /* AIProxyFal */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CD964C32C1BD144006DAD57 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD964D02C1BD145006DAD57 /* Preview Assets.xcassets in Resources */, 2CD964CD2C1BD145006DAD57 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CD964C12C1BD144006DAD57 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD964D72C1BD186006DAD57 /* TextToImageView.swift in Sources */, 2CD964CB2C1BD144006DAD57 /* ContentView.swift in Sources */, 2CD964C92C1BD144006DAD57 /* AIProxyFalApp.swift in Sources */, 2CDD52932C9A2A940052700C /* AppConstants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CD964D12C1BD145006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CD964D22C1BD145006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CD964D42C1BD145006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyFal/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxyFal; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CD964D52C1BD145006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyFal/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxyFal; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CD964C02C1BD144006DAD57 /* Build configuration list for PBXProject "AIProxyFal" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD964D12C1BD145006DAD57 /* Debug */, 2CD964D22C1BD145006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CD964D32C1BD145006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyFal" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD964D42C1BD145006DAD57 /* Debug */, 2CD964D52C1BD145006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C63D7752D232C03008CFE5B /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C20F29B2CB0EB31001ECE32 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; productName = AIProxy; }; 2C63D7762D232C03008CFE5B /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C63D7752D232C03008CFE5B /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; 2C885B422CB1CD0A00C23BAD /* AIProxy */ = { isa = XCSwiftPackageProductDependency; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CD964BD2C1BD144006DAD57 /* Project object */; } ================================================ FILE: AIProxyGemini/AIProxyGemini/AIProxyGeminiApp.swift ================================================ // // AIProxyGeminiApp.swift // AIProxyGemini // // Created by Todd Hamilton on 10/18/24. // import SwiftUI @main struct AIProxyGeminiApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyGemini/AIProxyGemini/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyGemini // // Created by Todd Hamilton on 10/18/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let geminiService = AIProxy.geminiDirectService( unprotectedAPIKey: "your-gemini-key" ) /* Uncomment for all other production use cases */ //let geminiService = AIProxy.geminiService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyGemini/AIProxyGemini/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGemini/AIProxyGemini/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "tinted" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGemini/AIProxyGemini/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGemini/AIProxyGemini/Assets.xcassets/icon.imageset/Contents.json ================================================ { "images" : [ { "filename" : "gemini.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGemini/AIProxyGemini/ContentView.swift ================================================ // // ContentView.swift // AIProxyGemini // // Created by Todd Hamilton on 10/18/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:24){ VStack{ Image("icon") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Gemini") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Text Generation",destination: TextGenerationView()) } .bold() .controlSize(.large) .tint(.teal) .buttonStyle(.bordered) } } } } #Preview { ContentView() } ================================================ FILE: AIProxyGemini/AIProxyGemini/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGemini/AIProxyGemini/TextGenerationView.swift ================================================ // // TextGenerationView.swift // AIProxyGemini // // Created by Todd Hamilton on 10/18/24. // import SwiftUI import AIProxy struct TextGenerationView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let requestBody = GeminiGenerateContentRequestBody( model: "gemini-1.5-flash", contents: [ .init( parts: [.text("Tell me a joke")] ) ] ) let response = try await geminiService.generateContentRequest(body: requestBody) for part in response.candidates?.first?.content?.parts ?? [] { switch part { case .text(let text): print("Gemini sent: \(text)") result = text } } if let usage = response.usageMetadata { print( """ Used: \(usage.promptTokenCount ?? 0) prompt tokens \(usage.cachedContentTokenCount ?? 0) cached tokens \(usage.candidatesTokenCount ?? 0) candidate tokens \(usage.totalTokenCount ?? 0) total tokens """ ) } showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received \(statusCode) status code with response body: \(responseBody)") } catch { print("Could not create Gemini generate content request: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Chat Completion") .navigationBarTitleDisplayMode(.inline) } } #Preview { TextGenerationView() } ================================================ FILE: AIProxyGemini/AIProxyGemini.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 2C7496D32CC2C9720069337D /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C7496D22CC2C9720069337D /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C7496A42CC2C82B0069337D /* AIProxyGemini.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyGemini.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ 2C7496A62CC2C82B0069337D /* AIProxyGemini */ = { isa = PBXFileSystemSynchronizedRootGroup; path = AIProxyGemini; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ 2C7496A12CC2C82B0069337D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C7496D32CC2C9720069337D /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C74969B2CC2C82B0069337D = { isa = PBXGroup; children = ( 2C7496A62CC2C82B0069337D /* AIProxyGemini */, 2C7496A52CC2C82B0069337D /* Products */, ); sourceTree = ""; }; 2C7496A52CC2C82B0069337D /* Products */ = { isa = PBXGroup; children = ( 2C7496A42CC2C82B0069337D /* AIProxyGemini.app */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C7496A32CC2C82B0069337D /* AIProxyGemini */ = { isa = PBXNativeTarget; buildConfigurationList = 2C7496B22CC2C82D0069337D /* Build configuration list for PBXNativeTarget "AIProxyGemini" */; buildPhases = ( 2C7496A02CC2C82B0069337D /* Sources */, 2C7496A12CC2C82B0069337D /* Frameworks */, 2C7496A22CC2C82B0069337D /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( 2C7496A62CC2C82B0069337D /* AIProxyGemini */, ); name = AIProxyGemini; packageProductDependencies = ( 2C7496D22CC2C9720069337D /* AIProxy */, ); productName = AIProxyGemini; productReference = 2C7496A42CC2C82B0069337D /* AIProxyGemini.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C74969C2CC2C82B0069337D /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; TargetAttributes = { 2C7496A32CC2C82B0069337D = { CreatedOnToolsVersion = 16.0; }; }; }; buildConfigurationList = 2C74969F2CC2C82B0069337D /* Build configuration list for PBXProject "AIProxyGemini" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C74969B2CC2C82B0069337D; minimizedProjectReferenceProxies = 1; packageReferences = ( 2C7496D12CC2C9720069337D /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); preferredProjectObjectVersion = 77; productRefGroup = 2C7496A52CC2C82B0069337D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C7496A32CC2C82B0069337D /* AIProxyGemini */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C7496A22CC2C82B0069337D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C7496A02CC2C82B0069337D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C7496B02CC2C82D0069337D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C7496B12CC2C82D0069337D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C7496B32CC2C82D0069337D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyGemini/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C7496B42CC2C82D0069337D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyGemini/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C74969F2CC2C82B0069337D /* Build configuration list for PBXProject "AIProxyGemini" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C7496B02CC2C82D0069337D /* Debug */, 2C7496B12CC2C82D0069337D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C7496B22CC2C82D0069337D /* Build configuration list for PBXNativeTarget "AIProxyGemini" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C7496B32CC2C82D0069337D /* Debug */, 2C7496B42CC2C82D0069337D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C7496D12CC2C9720069337D /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C7496D22CC2C9720069337D /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C7496D12CC2C9720069337D /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C74969C2CC2C82B0069337D /* Project object */; } ================================================ FILE: AIProxyGroq/AIProxyGroq/AIProxyGroqApp.swift ================================================ // // AIProxyGroqApp.swift // AIProxyGroq // // Created by Todd Hamilton on 10/1/24. // import SwiftUI @main struct AIProxyGroqApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyGroq/AIProxyGroq/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyGroq // // Created by Todd Hamilton on 10/1/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let groqService = AIProxy.groqDirectService( unprotectedAPIKey: "your-groq-key" ) /* Uncomment for all other production use cases */ //let groqService = AIProxy.groqService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyGroq/AIProxyGroq/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGroq/AIProxyGroq/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "tinted" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGroq/AIProxyGroq/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGroq/AIProxyGroq/Assets.xcassets/groq.imageset/Contents.json ================================================ { "images" : [ { "filename" : "groq.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGroq/AIProxyGroq/ChatView.swift ================================================ // // ChatView.swift // AIProxyGroq // // Created by Todd Hamilton on 10/1/24. // import SwiftUI import AIProxy struct ChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let response = try await groqService.chatCompletionRequest(body: .init( messages: [.assistant(content: prompt)], model: "mixtral-8x7b-32768" )) print(response.choices.first?.message.content ?? "") result = response.choices.first?.message.content ?? "" showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Chat Completion") .navigationBarTitleDisplayMode(.inline) } } #Preview { ChatView() } ================================================ FILE: AIProxyGroq/AIProxyGroq/ContentView.swift ================================================ // // ContentView.swift // AIProxyGroq // // Created by Todd Hamilton on 10/1/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:24){ VStack{ Image("groq") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Groq") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Chat Completion",destination: ChatView()) NavigationLink("Streaming Chat Completion",destination: StreamingChatView()) } .bold() .controlSize(.large) .tint(.red) .buttonStyle(.bordered) } } } } #Preview { ContentView() } ================================================ FILE: AIProxyGroq/AIProxyGroq/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyGroq/AIProxyGroq/StreamingChatView.swift ================================================ // // StreamingChatView.swift // AIProxyGroq // // Created by Todd Hamilton on 10/1/24. // import SwiftUI import AIProxy struct StreamingChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let stream = try await groqService.streamingChatCompletionRequest(body: .init( messages: [.assistant(content: prompt)], model: "mixtral-8x7b-32768" ) ) for try await chunk in stream { print(chunk.choices.first?.delta.content ?? "") } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received \(statusCode) status code with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text("View the streaming response in the Xcode console."), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { showingAlert = true Task{ try await generate() } } Button{ showingAlert = true Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Streaming Chat Completion") .navigationBarTitleDisplayMode(.inline) } } #Preview { ChatView() } ================================================ FILE: AIProxyGroq/AIProxyGroq.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 2C7E8C082CAC7CAA00A70D1C /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C7E8C072CAC7CAA00A70D1C /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C7E8BF52CAC7B7E00A70D1C /* AIProxyGroq.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyGroq.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ 2C7E8BF72CAC7B7E00A70D1C /* AIProxyGroq */ = { isa = PBXFileSystemSynchronizedRootGroup; path = AIProxyGroq; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ 2C7E8BF22CAC7B7E00A70D1C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C7E8C082CAC7CAA00A70D1C /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C7E8BEC2CAC7B7E00A70D1C = { isa = PBXGroup; children = ( 2C7E8BF72CAC7B7E00A70D1C /* AIProxyGroq */, 2C7E8BF62CAC7B7E00A70D1C /* Products */, ); sourceTree = ""; }; 2C7E8BF62CAC7B7E00A70D1C /* Products */ = { isa = PBXGroup; children = ( 2C7E8BF52CAC7B7E00A70D1C /* AIProxyGroq.app */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C7E8BF42CAC7B7E00A70D1C /* AIProxyGroq */ = { isa = PBXNativeTarget; buildConfigurationList = 2C7E8C032CAC7B7F00A70D1C /* Build configuration list for PBXNativeTarget "AIProxyGroq" */; buildPhases = ( 2C7E8BF12CAC7B7E00A70D1C /* Sources */, 2C7E8BF22CAC7B7E00A70D1C /* Frameworks */, 2C7E8BF32CAC7B7E00A70D1C /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( 2C7E8BF72CAC7B7E00A70D1C /* AIProxyGroq */, ); name = AIProxyGroq; packageProductDependencies = ( 2C7E8C072CAC7CAA00A70D1C /* AIProxy */, ); productName = AIProxyGroq; productReference = 2C7E8BF52CAC7B7E00A70D1C /* AIProxyGroq.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C7E8BED2CAC7B7E00A70D1C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; TargetAttributes = { 2C7E8BF42CAC7B7E00A70D1C = { CreatedOnToolsVersion = 16.0; }; }; }; buildConfigurationList = 2C7E8BF02CAC7B7E00A70D1C /* Build configuration list for PBXProject "AIProxyGroq" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C7E8BEC2CAC7B7E00A70D1C; minimizedProjectReferenceProxies = 1; packageReferences = ( 2C7E8C062CAC7CAA00A70D1C /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); preferredProjectObjectVersion = 77; productRefGroup = 2C7E8BF62CAC7B7E00A70D1C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C7E8BF42CAC7B7E00A70D1C /* AIProxyGroq */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C7E8BF32CAC7B7E00A70D1C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C7E8BF12CAC7B7E00A70D1C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C7E8C012CAC7B7F00A70D1C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C7E8C022CAC7B7F00A70D1C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C7E8C042CAC7B7F00A70D1C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyGroq/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C7E8C052CAC7B7F00A70D1C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyGroq/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C7E8BF02CAC7B7E00A70D1C /* Build configuration list for PBXProject "AIProxyGroq" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C7E8C012CAC7B7F00A70D1C /* Debug */, 2C7E8C022CAC7B7F00A70D1C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C7E8C032CAC7B7F00A70D1C /* Build configuration list for PBXNativeTarget "AIProxyGroq" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C7E8C042CAC7B7F00A70D1C /* Debug */, 2C7E8C052CAC7B7F00A70D1C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C7E8C062CAC7CAA00A70D1C /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C7E8C072CAC7CAA00A70D1C /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C7E8C062CAC7CAA00A70D1C /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C7E8BED2CAC7B7E00A70D1C /* Project object */; } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/AIProxyOpenAIApp.swift ================================================ // // AIProxyOpenAIApp.swift // AIProxyOpenAI // // Created by Todd Hamilton on 6/14/24. // import SwiftUI @main struct AIProxyOpenAIApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyOpenAI // // Created by Todd Hamilton on 6/14/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ //let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Assets.xcassets/openai.imageset/Contents.json ================================================ { "images" : [ { "filename" : "openai.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Assets.xcassets/surfer.imageset/Contents.json ================================================ { "images" : [ { "filename" : "surfer.jpeg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/ChatView.swift ================================================ // // ChatView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 6/14/24. // import SwiftUI import AIProxy struct ChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let response = try await openAIService.chatCompletionRequest(body: .init( model: "gpt-4o", messages: [.system(content: .text(prompt))] )) result = (response.choices.first?.message.content)! showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(String(describing: responseBody))") } catch { print(error.localizedDescription) } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Chat Completion") .navigationBarTitleDisplayMode(.inline) } } #Preview { ChatView() } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/ContentView.swift ================================================ // // ContentView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 6/14/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:48){ VStack{ Image("openai") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("OpenAI") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Chat Example",destination: ChatView()) NavigationLink("Streaming Chat Example",destination: ChatView()) NavigationLink("Multi-Modal Chat Example",destination: MultiModalChatView()) NavigationLink("DALLE Example",destination: DalleView()) NavigationLink("Text-to-Speech Example",destination: TextToSpeechView()) } .bold() .controlSize(.large) .buttonStyle(.bordered) .tint(.purple) } } } } #Preview { ContentView() } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/DalleView.swift ================================================ // // DalleView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 8/13/24. // import SwiftUI import AIProxy struct DalleView: View { @State private var prompt = "" @State private var imageUrl: String? @State private var isLoading = false func generate() async throws { isLoading = true // Start loading defer { isLoading = false } do { let requestBody = OpenAICreateImageRequestBody( prompt: prompt, model: "dall-e-3" ) let response = try await openAIService.createImageRequest(body: requestBody) imageUrl = response.data.first?.url?.absoluteString ?? "" // print(response.data.first?.url ?? "") } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack{ VStack{ if (imageUrl != nil) { AsyncImage(url: URL(string: imageUrl!)) { phase in if let image = phase.image { image .resizable() .aspectRatio(contentMode: .fit) } else if phase.error != nil { Text("Failed to load image") .foregroundColor(.red) } else { ProgressView() } } } else{ ContentUnavailableView( "Generate an image", systemImage: "photo.fill", description: Text("Write a prompt below") ) } } .frame(maxHeight: .infinity) Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Generate Image") .navigationBarTitleDisplayMode(.inline) } } #Preview { DalleView() } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/MultiModalChatView.swift ================================================ // // MultiModalChatView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 6/14/24. // import SwiftUI import AIProxy import UIKit struct MultiModalChatView: View { @State private var prompt:String = "" @State private var result:String = "" @State private var showingAlert = false @State private var isLoading = false var body: some View { VStack{ VStack{ Image("surfer") .resizable() .scaledToFit() .frame(maxWidth: .infinity) } .frame(maxHeight: .infinity) .alert(isPresented: $showingAlert){ Alert( title: Text("Result"), message: Text("\(result)"), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ Button{ Task { try await generate() } }label: { if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Describe Image") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Describe Image") .navigationBarTitleDisplayMode(.inline) } func generate() async throws { isLoading = true defer { isLoading = false } let image = UIImage(named: "surfer") let localURL = createOpenAILocalURL(forImage: image!) do { let response = try await openAIService.chatCompletionRequest(body: .init( model: "gpt-4o", messages: [ .system( content: .text("Tell me what you see") ), .user( content: .parts( [ .text("What do you see?"), .imageURL(localURL!) ] ) ) ] )) result = (response.choices.first?.message.content)! showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(String(describing: responseBody))") } catch { print(error.localizedDescription) } } func createOpenAILocalURL(forImage image: UIImage) -> URL? { // Attempt to get JPEG data from the UIImage guard let jpegData = image.jpegData(compressionQuality: 1.0) else { return nil } // Encode the JPEG data to a base64 string let base64String = jpegData.base64EncodedString() // Create the data URL string let urlString = "data:image/jpeg;base64,\(base64String)" // Return the URL constructed from the data URL string return URL(string: urlString) } } #Preview { MultiModalChatView() } private extension CGImage { var jpegData: Data? { let uiImage = UIImage(cgImage: self) return uiImage.jpegData(compressionQuality: 1.0) } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/StreamingChatView.swift ================================================ // // StreamingChatView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 8/13/24. // import SwiftUI import AIProxy struct StreamingChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { let requestBody = OpenAIChatCompletionRequestBody( model: "gpt-4o", messages: [.user(content: .text(prompt))] ) isLoading = true defer { isLoading = false } do { let stream = try await openAIService.streamingChatCompletionRequest(body: requestBody) for try await chunk in stream { print(chunk.choices.first?.delta.content ?? "") } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text("View the streaming response in the Xcode console."), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ showingAlert = true Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Streaming Chat Completion") .navigationBarTitleDisplayMode(.inline) } } #Preview { StreamingChatView() } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI/TextToSpeechView.swift ================================================ // // TextToSpeechView.swift // AIProxyOpenAI // // Created by Todd Hamilton on 10/9/24. // import SwiftUI import AIProxy import AVKit struct TextToSpeechView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State var audioPlayer: AVAudioPlayer! // List of available voices let voices = ["nova", "aria", "bella", "emma"] // Replace with actual voice names from OpenAI @State private var selectedVoice = "nova" // Default selected voice func generate() async throws { isLoading = true defer { isLoading = false } do { let requestBody = OpenAITextToSpeechRequestBody( input: prompt, voice: OpenAITextToSpeechRequestBody.Voice(rawValue: selectedVoice) ?? .nova ) let mpegData = try await openAIService.createTextToSpeechRequest(body: requestBody) // Do not use a local `let` or `var` for AVAudioPlayer. // You need the lifecycle of the player to live beyond the scope of this function. // Instead, use file scope or set the player as a member of a reference type with long life. // For example, at the top of this file you may define: // // fileprivate var audioPlayer: AVAudioPlayer? = nil // // And then use the code below to play the TTS result: audioPlayer = try AVAudioPlayer(data: mpegData) audioPlayer?.prepareToPlay() audioPlayer?.play() } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received \(statusCode) status code with response body: \(responseBody)") } catch { print("Could not create ElevenLabs TTS audio: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Text-to-Speech", systemImage: "doc.plaintext.fill", description: Text("Write a prompt and select a voice") ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } // Voice Picker Picker("Select Voice", selection: $selectedVoice) { ForEach(voices, id: \.self) { Text($0.capitalized) } } .pickerStyle(.segmented) .padding() Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Speech") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Text-to-Speech") .navigationBarTitleDisplayMode(.inline) } } #Preview { TextToSpeechView() } ================================================ FILE: AIProxyOpenAI/AIProxyOpenAI.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C16EC352CB720580016790F /* TextToSpeechView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C16EC342CB720580016790F /* TextToSpeechView.swift */; }; 2CCB20342C6C03AF003A8B25 /* StreamingChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20332C6C03AF003A8B25 /* StreamingChatView.swift */; }; 2CCB20362C6C0419003A8B25 /* DalleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB20352C6C0419003A8B25 /* DalleView.swift */; }; 2CD964F02C1CA33B006DAD57 /* AIProxyOpenAIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964EF2C1CA33B006DAD57 /* AIProxyOpenAIApp.swift */; }; 2CD964F22C1CA33B006DAD57 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964F12C1CA33B006DAD57 /* ContentView.swift */; }; 2CD964F42C1CA33D006DAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD964F32C1CA33D006DAD57 /* Assets.xcassets */; }; 2CD964F72C1CA33D006DAD57 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD964F62C1CA33D006DAD57 /* Preview Assets.xcassets */; }; 2CD964FE2C1CA3DC006DAD57 /* MultiModalChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964FD2C1CA3DC006DAD57 /* MultiModalChatView.swift */; }; 2CD965002C1CA3F3006DAD57 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964FF2C1CA3F3006DAD57 /* ChatView.swift */; }; 2CD965032C1CA4BB006DAD57 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CD965022C1CA4BB006DAD57 /* AIProxy */; }; 2CD965052C1CA4CF006DAD57 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD965042C1CA4CF006DAD57 /* AppConstants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C16EC342CB720580016790F /* TextToSpeechView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextToSpeechView.swift; sourceTree = ""; }; 2CCB20332C6C03AF003A8B25 /* StreamingChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingChatView.swift; sourceTree = ""; }; 2CCB20352C6C0419003A8B25 /* DalleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DalleView.swift; sourceTree = ""; }; 2CD964EC2C1CA33B006DAD57 /* AIProxyOpenAI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyOpenAI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CD964EF2C1CA33B006DAD57 /* AIProxyOpenAIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyOpenAIApp.swift; sourceTree = ""; }; 2CD964F12C1CA33B006DAD57 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CD964F32C1CA33D006DAD57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CD964F62C1CA33D006DAD57 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CD964FD2C1CA3DC006DAD57 /* MultiModalChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiModalChatView.swift; sourceTree = ""; }; 2CD964FF2C1CA3F3006DAD57 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 2CD965042C1CA4CF006DAD57 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CD964E92C1CA33B006DAD57 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CD965032C1CA4BB006DAD57 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CD964E32C1CA33B006DAD57 = { isa = PBXGroup; children = ( 2CD964EE2C1CA33B006DAD57 /* AIProxyOpenAI */, 2CD964ED2C1CA33B006DAD57 /* Products */, ); sourceTree = ""; }; 2CD964ED2C1CA33B006DAD57 /* Products */ = { isa = PBXGroup; children = ( 2CD964EC2C1CA33B006DAD57 /* AIProxyOpenAI.app */, ); name = Products; sourceTree = ""; }; 2CD964EE2C1CA33B006DAD57 /* AIProxyOpenAI */ = { isa = PBXGroup; children = ( 2CD964EF2C1CA33B006DAD57 /* AIProxyOpenAIApp.swift */, 2CD964F12C1CA33B006DAD57 /* ContentView.swift */, 2CD964FF2C1CA3F3006DAD57 /* ChatView.swift */, 2CCB20332C6C03AF003A8B25 /* StreamingChatView.swift */, 2CD964FD2C1CA3DC006DAD57 /* MultiModalChatView.swift */, 2CCB20352C6C0419003A8B25 /* DalleView.swift */, 2C16EC342CB720580016790F /* TextToSpeechView.swift */, 2CD965042C1CA4CF006DAD57 /* AppConstants.swift */, 2CD964F32C1CA33D006DAD57 /* Assets.xcassets */, 2CD964F52C1CA33D006DAD57 /* Preview Content */, ); path = AIProxyOpenAI; sourceTree = ""; }; 2CD964F52C1CA33D006DAD57 /* Preview Content */ = { isa = PBXGroup; children = ( 2CD964F62C1CA33D006DAD57 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CD964EB2C1CA33B006DAD57 /* AIProxyOpenAI */ = { isa = PBXNativeTarget; buildConfigurationList = 2CD964FA2C1CA33D006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyOpenAI" */; buildPhases = ( 2CD964E82C1CA33B006DAD57 /* Sources */, 2CD964E92C1CA33B006DAD57 /* Frameworks */, 2CD964EA2C1CA33B006DAD57 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyOpenAI; packageProductDependencies = ( 2CD965022C1CA4BB006DAD57 /* AIProxy */, ); productName = AIProxyOpenAI; productReference = 2CD964EC2C1CA33B006DAD57 /* AIProxyOpenAI.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CD964E42C1CA33B006DAD57 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CD964EB2C1CA33B006DAD57 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CD964E72C1CA33B006DAD57 /* Build configuration list for PBXProject "AIProxyOpenAI" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CD964E32C1CA33B006DAD57; packageReferences = ( 2CD965012C1CA4BB006DAD57 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CD964ED2C1CA33B006DAD57 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CD964EB2C1CA33B006DAD57 /* AIProxyOpenAI */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CD964EA2C1CA33B006DAD57 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD964F72C1CA33D006DAD57 /* Preview Assets.xcassets in Resources */, 2CD964F42C1CA33D006DAD57 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CD964E82C1CA33B006DAD57 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD965052C1CA4CF006DAD57 /* AppConstants.swift in Sources */, 2CCB20362C6C0419003A8B25 /* DalleView.swift in Sources */, 2CD965002C1CA3F3006DAD57 /* ChatView.swift in Sources */, 2CD964FE2C1CA3DC006DAD57 /* MultiModalChatView.swift in Sources */, 2C16EC352CB720580016790F /* TextToSpeechView.swift in Sources */, 2CD964F22C1CA33B006DAD57 /* ContentView.swift in Sources */, 2CD964F02C1CA33B006DAD57 /* AIProxyOpenAIApp.swift in Sources */, 2CCB20342C6C03AF003A8B25 /* StreamingChatView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CD964F82C1CA33D006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CD964F92C1CA33D006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CD964FB2C1CA33D006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyOpenAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CD964FC2C1CA33D006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyOpenAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.bootstrap; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CD964E72C1CA33B006DAD57 /* Build configuration list for PBXProject "AIProxyOpenAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD964F82C1CA33D006DAD57 /* Debug */, 2CD964F92C1CA33D006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CD964FA2C1CA33D006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyOpenAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD964FB2C1CA33D006DAD57 /* Debug */, 2CD964FC2C1CA33D006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CD965012C1CA4BB006DAD57 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CD965022C1CA4BB006DAD57 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CD965012C1CA4BB006DAD57 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CD964E42C1CA33B006DAD57 /* Project object */; } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/AIProxyReplicateApp.swift ================================================ // // AIProxyReplicateApp.swift // AIProxyReplicate // // Created by Todd Hamilton on 6/13/24. // import SwiftUI @main struct AIProxyReplicateApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyReplicate // // Created by Todd Hamilton on 6/13/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let replicateService = AIProxy.replicateDirectService( unprotectedAPIKey: "your-replicate-key" ) /* Uncomment for all other production use cases */ //let replicateService = AIProxy.replicateService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyReplicate/AIProxyReplicate/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/Assets.xcassets/replicate.imageset/Contents.json ================================================ { "images" : [ { "filename" : "replicate.jpeg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/ContentView.swift ================================================ // // ContentView.swift // AIProxyReplicate // // Created by Todd Hamilton on 6/13/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:24){ VStack{ Image("replicate") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Replicate") .bold() .font(.largeTitle) Text("AIProxy Samples") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Generate Image Example",destination: ImageGenView()) .bold() .controlSize(.large) .tint(.pink) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/ImageGenView.swift ================================================ // // ImageGenView.swift // AIProxyReplicate // // Created by Todd Hamilton on 6/13/24. // import SwiftUI import AIProxy struct ImageGenView: View { @State private var prompt = "" @State private var imageUrl: URL? @State private var isLoading = false func generate() async throws { isLoading = true // Start loading defer { isLoading = false } do { let input = ReplicateSDXLInputSchema( prompt: prompt ) let output = try await replicateService.createSDXLImage( input: input ) print("Done creating SDXL image: ", output.first ?? "") imageUrl = output.first } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create SDXL image: \(error.localizedDescription)") } } var body: some View { VStack{ VStack{ if (imageUrl != nil) { AsyncImage(url: imageUrl) { phase in if let image = phase.image { image .resizable() .aspectRatio(contentMode: .fit) } else if phase.error != nil { Text("Failed to load image") .foregroundColor(.red) } else { ProgressView() } } } else{ ContentUnavailableView( "Generate an image", systemImage: "photo.fill", description: Text("Write a prompt below") ) } } .frame(maxHeight: .infinity) Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(.white) .cornerRadius(8) .shadow(radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Generate Image") .navigationBarTitleDisplayMode(.inline) } } #Preview { ImageGenView() } ================================================ FILE: AIProxyReplicate/AIProxyReplicate/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyReplicate/AIProxyReplicate.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C205C532C7F62330030E7AC /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C205C522C7F62330030E7AC /* AIProxy */; }; 2CD964872C1B98F5006DAD57 /* AIProxyReplicateApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964862C1B98F5006DAD57 /* AIProxyReplicateApp.swift */; }; 2CD964892C1B98F5006DAD57 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964882C1B98F5006DAD57 /* ContentView.swift */; }; 2CD9648B2C1B98F7006DAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9648A2C1B98F7006DAD57 /* Assets.xcassets */; }; 2CD9648E2C1B98F7006DAD57 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CD9648D2C1B98F7006DAD57 /* Preview Assets.xcassets */; }; 2CD9649C2C1B995F006DAD57 /* ImageGenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD9649B2C1B995F006DAD57 /* ImageGenView.swift */; }; 2CD964E02C1BDC53006DAD57 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD964DF2C1BDC53006DAD57 /* AppConstants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2CD964832C1B98F5006DAD57 /* AIProxyReplicate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyReplicate.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CD964862C1B98F5006DAD57 /* AIProxyReplicateApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyReplicateApp.swift; sourceTree = ""; }; 2CD964882C1B98F5006DAD57 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CD9648A2C1B98F7006DAD57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CD9648D2C1B98F7006DAD57 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CD9649B2C1B995F006DAD57 /* ImageGenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenView.swift; sourceTree = ""; }; 2CD964DF2C1BDC53006DAD57 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CD964802C1B98F5006DAD57 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C205C532C7F62330030E7AC /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CD9647A2C1B98F5006DAD57 = { isa = PBXGroup; children = ( 2CD964852C1B98F5006DAD57 /* AIProxyReplicate */, 2CD964842C1B98F5006DAD57 /* Products */, ); sourceTree = ""; }; 2CD964842C1B98F5006DAD57 /* Products */ = { isa = PBXGroup; children = ( 2CD964832C1B98F5006DAD57 /* AIProxyReplicate.app */, ); name = Products; sourceTree = ""; }; 2CD964852C1B98F5006DAD57 /* AIProxyReplicate */ = { isa = PBXGroup; children = ( 2CD964862C1B98F5006DAD57 /* AIProxyReplicateApp.swift */, 2CD964882C1B98F5006DAD57 /* ContentView.swift */, 2CD9649B2C1B995F006DAD57 /* ImageGenView.swift */, 2CD964DF2C1BDC53006DAD57 /* AppConstants.swift */, 2CD9648A2C1B98F7006DAD57 /* Assets.xcassets */, 2CD9648C2C1B98F7006DAD57 /* Preview Content */, ); path = AIProxyReplicate; sourceTree = ""; }; 2CD9648C2C1B98F7006DAD57 /* Preview Content */ = { isa = PBXGroup; children = ( 2CD9648D2C1B98F7006DAD57 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CD964822C1B98F5006DAD57 /* AIProxyReplicate */ = { isa = PBXNativeTarget; buildConfigurationList = 2CD964912C1B98F7006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyReplicate" */; buildPhases = ( 2CD9647F2C1B98F5006DAD57 /* Sources */, 2CD964802C1B98F5006DAD57 /* Frameworks */, 2CD964812C1B98F5006DAD57 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyReplicate; packageProductDependencies = ( 2C205C522C7F62330030E7AC /* AIProxy */, ); productName = AIProxyReplicate; productReference = 2CD964832C1B98F5006DAD57 /* AIProxyReplicate.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CD9647B2C1B98F5006DAD57 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CD964822C1B98F5006DAD57 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CD9647E2C1B98F5006DAD57 /* Build configuration list for PBXProject "AIProxyReplicate" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CD9647A2C1B98F5006DAD57; packageReferences = ( 2C205C512C7F62330030E7AC /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CD964842C1B98F5006DAD57 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CD964822C1B98F5006DAD57 /* AIProxyReplicate */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CD964812C1B98F5006DAD57 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD9648E2C1B98F7006DAD57 /* Preview Assets.xcassets in Resources */, 2CD9648B2C1B98F7006DAD57 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CD9647F2C1B98F5006DAD57 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CD964892C1B98F5006DAD57 /* ContentView.swift in Sources */, 2CD9649C2C1B995F006DAD57 /* ImageGenView.swift in Sources */, 2CD964E02C1BDC53006DAD57 /* AppConstants.swift in Sources */, 2CD964872C1B98F5006DAD57 /* AIProxyReplicateApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CD9648F2C1B98F7006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CD964902C1B98F7006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CD964922C1B98F7006DAD57 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyReplicate/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CD964932C1B98F7006DAD57 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyReplicate/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CD9647E2C1B98F5006DAD57 /* Build configuration list for PBXProject "AIProxyReplicate" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD9648F2C1B98F7006DAD57 /* Debug */, 2CD964902C1B98F7006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CD964912C1B98F7006DAD57 /* Build configuration list for PBXNativeTarget "AIProxyReplicate" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CD964922C1B98F7006DAD57 /* Debug */, 2CD964932C1B98F7006DAD57 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C205C512C7F62330030E7AC /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C205C522C7F62330030E7AC /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C205C512C7F62330030E7AC /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CD9647B2C1B98F5006DAD57 /* Project object */; } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/AIProxyStabilityAIApp.swift ================================================ // // AIProxyStabilityAIApp.swift // AIProxyStabilityAI // // Created by Todd Hamilton on 8/13/24. // import SwiftUI @main struct AIProxyStabilityAIApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyStabilityAI // // Created by Todd Hamilton on 8/13/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let stabilityService = AIProxy.stabilityAIDirectService( unprotectedAPIKey: "your-stability-key" ) /* Uncomment for all other production use cases */ //let stabilityService = AIProxy.stabilityAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/Assets.xcassets/stability.imageset/Contents.json ================================================ { "images" : [ { "filename" : "stability.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/ContentView.swift ================================================ // // ContentView.swift // AIProxyStabilityAI // // Created by Todd Hamilton on 8/13/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:48){ VStack{ Image("stability") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Stability.ai") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Generate Image Example",destination: ImageGenView()) .bold() .controlSize(.large) .tint(.indigo) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/ImageGenView.swift ================================================ // // ImageGenView.swift // AIProxyStabilityAI // // Created by Todd Hamilton on 8/13/24. // import SwiftUI import AIProxy struct ImageGenView: View { @State private var prompt = "" @State private var image: UIImage? = nil @State private var isLoading = false func generate() async throws { isLoading = true // Start loading defer { isLoading = false } do { let body = StabilityAIUltraRequestBody(prompt: prompt) // This demo is of text-to-image, which only requires a prompt // To use image-to-image the following parameters are required: // prompt - text to generate the image from // image - the image to use as the starting point for the generation // strength - controls how much influence the image parameter has on the output image // mode - must be set to image-to-image // Learn more: https://platform.stability.ai/docs/api-reference#tag/Generate let response = try await stabilityService.ultraRequest(body: body) image = UIImage(data: response.imageData) } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } var body: some View { VStack{ VStack{ if (image != nil) { Image(uiImage: image!) .resizable() .aspectRatio(contentMode: .fit) .frame(maxHeight: UIScreen.main.bounds.width) } else{ ContentUnavailableView( "Generate an image", systemImage: "photo.fill", description: Text("Write a prompt below") ) } } .frame(maxHeight: .infinity) Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color:.primary, radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Image") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Generate Image") .navigationBarTitleDisplayMode(.inline) } } #Preview { ImageGenView() } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyStabilityAI/AIProxyStabilityAI.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C1C112D2C6BE4A9000B80E0 /* AIProxyStabilityAIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C112C2C6BE4A9000B80E0 /* AIProxyStabilityAIApp.swift */; }; 2C1C112F2C6BE4A9000B80E0 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C112E2C6BE4A9000B80E0 /* ContentView.swift */; }; 2C1C11312C6BE4AA000B80E0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1C11302C6BE4AA000B80E0 /* Assets.xcassets */; }; 2C1C11342C6BE4AA000B80E0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1C11332C6BE4AA000B80E0 /* Preview Assets.xcassets */; }; 2C1C113C2C6BE4D1000B80E0 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C1C113B2C6BE4D1000B80E0 /* AIProxy */; }; 2C1C113E2C6BE4FA000B80E0 /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C113D2C6BE4FA000B80E0 /* AppConstants.swift */; }; 2C1C11402C6BE57A000B80E0 /* ImageGenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1C113F2C6BE57A000B80E0 /* ImageGenView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C1C11292C6BE4A9000B80E0 /* AIProxyStabilityAI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyStabilityAI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2C1C112C2C6BE4A9000B80E0 /* AIProxyStabilityAIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyStabilityAIApp.swift; sourceTree = ""; }; 2C1C112E2C6BE4A9000B80E0 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2C1C11302C6BE4AA000B80E0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2C1C11332C6BE4AA000B80E0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2C1C113D2C6BE4FA000B80E0 /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 2C1C113F2C6BE57A000B80E0 /* ImageGenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2C1C11262C6BE4A9000B80E0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C1C113C2C6BE4D1000B80E0 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C1C11202C6BE4A9000B80E0 = { isa = PBXGroup; children = ( 2C1C112B2C6BE4A9000B80E0 /* AIProxyStabilityAI */, 2C1C112A2C6BE4A9000B80E0 /* Products */, ); sourceTree = ""; }; 2C1C112A2C6BE4A9000B80E0 /* Products */ = { isa = PBXGroup; children = ( 2C1C11292C6BE4A9000B80E0 /* AIProxyStabilityAI.app */, ); name = Products; sourceTree = ""; }; 2C1C112B2C6BE4A9000B80E0 /* AIProxyStabilityAI */ = { isa = PBXGroup; children = ( 2C1C112C2C6BE4A9000B80E0 /* AIProxyStabilityAIApp.swift */, 2C1C112E2C6BE4A9000B80E0 /* ContentView.swift */, 2C1C113F2C6BE57A000B80E0 /* ImageGenView.swift */, 2C1C113D2C6BE4FA000B80E0 /* AppConstants.swift */, 2C1C11302C6BE4AA000B80E0 /* Assets.xcassets */, 2C1C11322C6BE4AA000B80E0 /* Preview Content */, ); path = AIProxyStabilityAI; sourceTree = ""; }; 2C1C11322C6BE4AA000B80E0 /* Preview Content */ = { isa = PBXGroup; children = ( 2C1C11332C6BE4AA000B80E0 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C1C11282C6BE4A9000B80E0 /* AIProxyStabilityAI */ = { isa = PBXNativeTarget; buildConfigurationList = 2C1C11372C6BE4AA000B80E0 /* Build configuration list for PBXNativeTarget "AIProxyStabilityAI" */; buildPhases = ( 2C1C11252C6BE4A9000B80E0 /* Sources */, 2C1C11262C6BE4A9000B80E0 /* Frameworks */, 2C1C11272C6BE4A9000B80E0 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyStabilityAI; packageProductDependencies = ( 2C1C113B2C6BE4D1000B80E0 /* AIProxy */, ); productName = AIProxyStabilityAI; productReference = 2C1C11292C6BE4A9000B80E0 /* AIProxyStabilityAI.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C1C11212C6BE4A9000B80E0 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2C1C11282C6BE4A9000B80E0 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2C1C11242C6BE4A9000B80E0 /* Build configuration list for PBXProject "AIProxyStabilityAI" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C1C11202C6BE4A9000B80E0; packageReferences = ( 2C1C113A2C6BE4D1000B80E0 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2C1C112A2C6BE4A9000B80E0 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C1C11282C6BE4A9000B80E0 /* AIProxyStabilityAI */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C1C11272C6BE4A9000B80E0 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2C1C11342C6BE4AA000B80E0 /* Preview Assets.xcassets in Resources */, 2C1C11312C6BE4AA000B80E0 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C1C11252C6BE4A9000B80E0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2C1C112F2C6BE4A9000B80E0 /* ContentView.swift in Sources */, 2C1C11402C6BE57A000B80E0 /* ImageGenView.swift in Sources */, 2C1C113E2C6BE4FA000B80E0 /* AppConstants.swift in Sources */, 2C1C112D2C6BE4A9000B80E0 /* AIProxyStabilityAIApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C1C11352C6BE4AA000B80E0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C1C11362C6BE4AA000B80E0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C1C11382C6BE4AA000B80E0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyStabilityAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C1C11392C6BE4AA000B80E0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyStabilityAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxySampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C1C11242C6BE4A9000B80E0 /* Build configuration list for PBXProject "AIProxyStabilityAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C1C11352C6BE4AA000B80E0 /* Debug */, 2C1C11362C6BE4AA000B80E0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C1C11372C6BE4AA000B80E0 /* Build configuration list for PBXNativeTarget "AIProxyStabilityAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C1C11382C6BE4AA000B80E0 /* Debug */, 2C1C11392C6BE4AA000B80E0 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C1C113A2C6BE4D1000B80E0 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C1C113B2C6BE4D1000B80E0 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C1C113A2C6BE4D1000B80E0 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C1C11212C6BE4A9000B80E0 /* Project object */; } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/AIProxyTogetherAIApp.swift ================================================ // // AIProxyTogetherAIApp.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import SwiftUI @main struct AIProxyTogetherAIApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let togetherAIService = AIProxy.togetherAIDirectService( unprotectedAPIKey: "your-togetherAI-key" ) /* Uncomment for all other production use cases */ //let togetherAIService = AIProxy.togetherAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/Assets.xcassets/togetherai.imageset/Contents.json ================================================ { "images" : [ { "filename" : "togetherai.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/ChatView.swift ================================================ // // ChatView.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import SwiftUI import AIProxy struct ChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let requestBody = TogetherAIChatCompletionRequestBody( messages: [TogetherAIMessage(content: prompt, role: .user)], model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo" ) let response = try await togetherAIService.chatCompletionRequest(body: requestBody) print(response.choices.first?.message.content ?? "") result = response.choices.first?.message.content ?? "" showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create TogetherAI chat completion: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(.white) .cornerRadius(8) .shadow(radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Chat Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { ChatView() } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/ContentView.swift ================================================ // // ContentView.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import SwiftUI struct ContentView: View { var body: some View { NavigationStack{ VStack(spacing:48){ VStack{ Image("togetherai") .resizable() .scaledToFit() .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/) .cornerRadius(14) .foregroundColor(.primary) Text("Together AI") .bold() .font(.largeTitle) Text("AIProxy Sample") .font(.subheadline) .foregroundColor(.secondary) } .frame(maxWidth:.infinity,alignment:.center) VStack{ NavigationLink("Chat Example",destination: ChatView()) .bold() .controlSize(.large) .tint(.blue) .buttonStyle(.bordered) NavigationLink("Streaming Chat Example",destination: StreamingChatView()) .bold() .controlSize(.large) .tint(.blue) .buttonStyle(.bordered) NavigationLink("JSON Response",destination: JSONResponseView()) .bold() .controlSize(.large) .tint(.blue) .buttonStyle(.bordered) } } } } } #Preview { ContentView() } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/JSONResponseView.swift ================================================ // // JSONResponseView.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import SwiftUI import AIProxy struct JSONResponseView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let schema: [String: AIProxyJSONValue] = [ "type": "object", "properties": [ "people": [ "type": "array", "items": [ "type": "object", "properties": [ "name": [ "type": "string", "description": "The name of the person" ], "address": [ "type": "string", "description": "The address of the person" ] ], "required": ["name", "address"] ] ] ] ] let requestBody = TogetherAIChatCompletionRequestBody( messages: [ TogetherAIMessage( content: "You are a helpful assistant that answers in JSON", role: .system ), TogetherAIMessage( content: prompt, role: .user ) ], model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", responseFormat: .json(schema: schema) ) let response = try await togetherAIService.chatCompletionRequest(body: requestBody) print(response.choices.first?.message.content ?? "") result = response.choices.first?.message.content ?? "" showingAlert = true } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create TogetherAI JSON chat completion: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate JSON", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text(result), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(.white) .cornerRadius(8) .shadow(radius: 1) .onSubmit { Task{ try await generate() } } Button{ Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate JSON") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("JSON Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { JSONResponseView() } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI/StreamingChatView.swift ================================================ // // StreamingChatView.swift // AIProxyTogetherAI // // Created by Todd Hamilton on 8/18/24. // import SwiftUI import AIProxy struct StreamingChatView: View { @State private var prompt = "" @State private var result = "" @State private var isLoading = false @State private var showingAlert = false func generate() async throws { isLoading = true defer { isLoading = false } do { let requestBody = TogetherAIChatCompletionRequestBody( messages: [TogetherAIMessage(content: prompt, role: .user)], model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo" ) let stream = try await togetherAIService.streamingChatCompletionRequest(body: requestBody) for try await chunk in stream { print(chunk.choices.first?.delta.content ?? "") } } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create TogetherAI streaming chat completion: \(error.localizedDescription)") } } var body: some View { VStack { VStack{ ContentUnavailableView( "Generate Text", systemImage: "doc.plaintext.fill", description: Text("Write a prompt below") ) } .alert(isPresented: $showingAlert) { Alert( title: Text("Result"), message: Text("View streaming response in the Xcode console."), dismissButton: .default(Text("Close")) ) } Spacer() VStack(spacing:12){ TextField("Type a prompt", text:$prompt) .submitLabel(.go) .padding(12) .background(.white) .cornerRadius(8) .shadow(radius: 1) .onSubmit { Task{ try await generate() } } Button{ showingAlert = true Task{ try await generate() } }label:{ if isLoading { ProgressView() .controlSize(.regular) .frame(maxWidth:.infinity) } else { Text("Generate Text") .bold() .frame(maxWidth:.infinity) } } .controlSize(.large) .buttonStyle(.borderedProminent) .disabled(isLoading ? true : false) } } .padding() .navigationTitle("Streaming Chat Example") .navigationBarTitleDisplayMode(.inline) } } #Preview { StreamingChatView() } ================================================ FILE: AIProxyTogetherAI/AIProxyTogetherAI.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2CCA22CA2C72D78A0054393E /* AIProxyTogetherAIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22C92C72D78A0054393E /* AIProxyTogetherAIApp.swift */; }; 2CCA22CC2C72D78A0054393E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22CB2C72D78A0054393E /* ContentView.swift */; }; 2CCA22CE2C72D78B0054393E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CCA22CD2C72D78B0054393E /* Assets.xcassets */; }; 2CCA22D12C72D78B0054393E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CCA22D02C72D78B0054393E /* Preview Assets.xcassets */; }; 2CCA22D92C72D7930054393E /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CCA22D82C72D7930054393E /* AIProxy */; }; 2CCA22DB2C72D7AD0054393E /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22DA2C72D7AD0054393E /* AppConstants.swift */; }; 2CCA22DD2C72DA570054393E /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22DC2C72DA570054393E /* ChatView.swift */; }; 2CCA22DF2C72DBB00054393E /* StreamingChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22DE2C72DBB00054393E /* StreamingChatView.swift */; }; 2CCA22E12C72DDDE0054393E /* JSONResponseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCA22E02C72DDDE0054393E /* JSONResponseView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2CCA22C62C72D78A0054393E /* AIProxyTogetherAI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIProxyTogetherAI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CCA22C92C72D78A0054393E /* AIProxyTogetherAIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyTogetherAIApp.swift; sourceTree = ""; }; 2CCA22CB2C72D78A0054393E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CCA22CD2C72D78B0054393E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CCA22D02C72D78B0054393E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CCA22DA2C72D7AD0054393E /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 2CCA22DC2C72DA570054393E /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 2CCA22DE2C72DBB00054393E /* StreamingChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingChatView.swift; sourceTree = ""; }; 2CCA22E02C72DDDE0054393E /* JSONResponseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONResponseView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CCA22C32C72D78A0054393E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CCA22D92C72D7930054393E /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CCA22BD2C72D78A0054393E = { isa = PBXGroup; children = ( 2CCA22C82C72D78A0054393E /* AIProxyTogetherAI */, 2CCA22C72C72D78A0054393E /* Products */, ); sourceTree = ""; }; 2CCA22C72C72D78A0054393E /* Products */ = { isa = PBXGroup; children = ( 2CCA22C62C72D78A0054393E /* AIProxyTogetherAI.app */, ); name = Products; sourceTree = ""; }; 2CCA22C82C72D78A0054393E /* AIProxyTogetherAI */ = { isa = PBXGroup; children = ( 2CCA22C92C72D78A0054393E /* AIProxyTogetherAIApp.swift */, 2CCA22CB2C72D78A0054393E /* ContentView.swift */, 2CCA22DC2C72DA570054393E /* ChatView.swift */, 2CCA22DE2C72DBB00054393E /* StreamingChatView.swift */, 2CCA22E02C72DDDE0054393E /* JSONResponseView.swift */, 2CCA22DA2C72D7AD0054393E /* AppConstants.swift */, 2CCA22CD2C72D78B0054393E /* Assets.xcassets */, 2CCA22CF2C72D78B0054393E /* Preview Content */, ); path = AIProxyTogetherAI; sourceTree = ""; }; 2CCA22CF2C72D78B0054393E /* Preview Content */ = { isa = PBXGroup; children = ( 2CCA22D02C72D78B0054393E /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CCA22C52C72D78A0054393E /* AIProxyTogetherAI */ = { isa = PBXNativeTarget; buildConfigurationList = 2CCA22D42C72D78B0054393E /* Build configuration list for PBXNativeTarget "AIProxyTogetherAI" */; buildPhases = ( 2CCA22C22C72D78A0054393E /* Sources */, 2CCA22C32C72D78A0054393E /* Frameworks */, 2CCA22C42C72D78A0054393E /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIProxyTogetherAI; packageProductDependencies = ( 2CCA22D82C72D7930054393E /* AIProxy */, ); productName = AIProxyTogetherAI; productReference = 2CCA22C62C72D78A0054393E /* AIProxyTogetherAI.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CCA22BE2C72D78A0054393E /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CCA22C52C72D78A0054393E = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CCA22C12C72D78A0054393E /* Build configuration list for PBXProject "AIProxyTogetherAI" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CCA22BD2C72D78A0054393E; packageReferences = ( 2CCA22D72C72D7930054393E /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CCA22C72C72D78A0054393E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CCA22C52C72D78A0054393E /* AIProxyTogetherAI */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CCA22C42C72D78A0054393E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CCA22D12C72D78B0054393E /* Preview Assets.xcassets in Resources */, 2CCA22CE2C72D78B0054393E /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CCA22C22C72D78A0054393E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CCA22DD2C72DA570054393E /* ChatView.swift in Sources */, 2CCA22E12C72DDDE0054393E /* JSONResponseView.swift in Sources */, 2CCA22CC2C72D78A0054393E /* ContentView.swift in Sources */, 2CCA22DB2C72D7AD0054393E /* AppConstants.swift in Sources */, 2CCA22DF2C72DBB00054393E /* StreamingChatView.swift in Sources */, 2CCA22CA2C72D78A0054393E /* AIProxyTogetherAIApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CCA22D22C72D78B0054393E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CCA22D32C72D78B0054393E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CCA22D52C72D78B0054393E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyTogetherAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxyTogetherAI; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CCA22D62C72D78B0054393E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIProxyTogetherAI/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIProxyTogetherAI; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CCA22C12C72D78A0054393E /* Build configuration list for PBXProject "AIProxyTogetherAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CCA22D22C72D78B0054393E /* Debug */, 2CCA22D32C72D78B0054393E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CCA22D42C72D78B0054393E /* Build configuration list for PBXNativeTarget "AIProxyTogetherAI" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CCA22D52C72D78B0054393E /* Debug */, 2CCA22D62C72D78B0054393E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CCA22D72C72D7930054393E /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CCA22D82C72D7930054393E /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CCA22D72C72D7930054393E /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CCA22BE2C72D78A0054393E /* Project object */; } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/AIColorPaletteApp.swift ================================================ // // AIColorPaletteApp.swift // AIColorPalette // // Created by Todd Hamilton on 6/20/24. // import SwiftUI @main struct AIColorPaletteApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/AIProxyIntegration.swift ================================================ // // AIProxyIntegration.swift // AIColorPalette // // Created by Todd Hamilton on 6/20/24. // import AIProxy // The AIProxy SPM package is found at https://github.com/lzell/AIProxySwift import Foundation import UIKit #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ //let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) struct AIProxyIntegration { static func getColorPalette(forImage image: UIImage) async -> String? { let message = "generate a color palette based on the provided image, return 4 colors in valid JSON, nothing else. Here's an example of the JSON format: 'colors': [{red: 0.85, green: 0.85, blue: 0.85}, {red: 0.85, green: 0.85, blue: 0.85}, {red: 0.85, green: 0.85, blue: 0.85}, {red: 0.85, green: 0.85, blue: 0.85}, {red: 0.85, green: 0.85, blue: 0.85}]." let localURL = createOpenAILocalURL(forImage: image)! do { let response = try await openAIService.chatCompletionRequest(body: .init( model: "gpt-4o", messages: [ .system( content: .text(message) ), .user( content: .parts( [ .imageURL(localURL, detail: .auto) ] ) ) ], responseFormat: .jsonObject )) return response.choices.first?.message.content } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } return nil } private init() { fatalError("This type is not intended to be instantiated") } } func createOpenAILocalURL(forImage image: UIImage) -> URL? { // Attempt to get JPEG data from the UIImage guard let jpegData = image.jpegData(compressionQuality: 0.4) else { return nil } // Encode the JPEG data to a base64 string let base64String = jpegData.base64EncodedString() // Create the data URL string let urlString = "data:image/jpeg;base64,\(base64String)" // Return the URL constructed from the data URL string return URL(string: urlString) } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Assets.xcassets/palm.imageset/Contents.json ================================================ { "images" : [ { "filename" : "palm.jpg", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/ColorData.swift ================================================ // // ColorData.swift // AIColorPalette // // Created by Todd Hamilton on 6/21/24. // import SwiftUI // Define the structure for the JSON data struct ColorData: Codable { let red: Double let green: Double let blue: Double enum CodingKeys: String, CodingKey { case red case green case blue } } struct Colors: Codable { let colors: [ColorData] } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/ColorDetailView.swift ================================================ // // ColorDetailView.swift // AIColorPalette // // Created by Todd Hamilton on 6/22/24. // import SwiftUI struct ColorDetailView: View { @Binding var currentColor:ColorData @State var copiedToClipboard:Bool = false @State private var hexValue:String = "" @State private var rgbValue:String = "" var body: some View { VStack(spacing:16){ ZStack{ Circle() .fill(.white) .frame(width:68) .shadow(radius: 2, x: 0, y: 2) Circle() .fill(Color(red:currentColor.red, green: currentColor.green, blue: currentColor.blue)) .frame(width:60) } .padding(.vertical, 16) HStack{ Text(hexValue) .fontWeight(.medium) Spacer() Button{ copyToClipboard(copyItem: hexValue) }label:{ Image(systemName: "square.on.square") } .buttonStyle(.bordered) .tint(Color(red:currentColor.red, green: currentColor.green, blue: currentColor.blue)) .sensoryFeedback(.success, trigger: copiedToClipboard) } Divider() HStack{ Text(rgbValue) .fontWeight(.medium) Spacer() Button{ copyToClipboard(copyItem: rgbValue) }label:{ Image(systemName: "square.on.square") } .buttonStyle(.bordered) .tint(Color(red:currentColor.red, green: currentColor.green, blue: currentColor.blue)) .sensoryFeedback(.success, trigger: copiedToClipboard) } Spacer() } .padding() .overlay( ZStack{ if copiedToClipboard { Text("Copied to clipboard") .fontWeight(.medium) .foregroundColor(.white) .padding() .background(Color.black.opacity(0.75).cornerRadius(20)) .padding(.bottom) .shadow(radius: 5) .transition(.move(edge: .bottom)) .frame(maxHeight:.infinity, alignment:.bottom) } } ) .onAppear{ hexValue = rgbToHex(red: currentColor.red, green: currentColor.green, blue: currentColor.blue) rgbValue = "rgb(\(String(format: "%.2f", currentColor.red)), \(String(format: "%.2f", currentColor.green)), \(String(format: "%.2f", currentColor.blue)))" } } func rgbToHex(red: CGFloat, green: CGFloat, blue: CGFloat) -> String { let r = Int(red * 255.0) let g = Int(green * 255.0) let b = Int(blue * 255.0) return String(format: "#%02X%02X%02X", r, g, b) } func copyToClipboard(copyItem: String) { UIPasteboard.general.string = copyItem withAnimation(.snappy){ copiedToClipboard = true } DispatchQueue.main.asyncAfter(deadline: .now() + 2){ withAnimation(.snappy){ copiedToClipboard = false } } } } #Preview { ZStack{ Image("palm") .resizable() .scaledToFill() .ignoresSafeArea() .frame(width:UIScreen.main.bounds.width) ColorDetailView(currentColor: .constant(ColorData(red: 0.5, green:0.2, blue:0.3))) .background(.thickMaterial) } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/ContentView.swift ================================================ // // ContentView.swift // AIColorPalette // // Created by Todd Hamilton on 6/20/24. // import SwiftUI import PhotosUI struct ContentView: View { @State private var presentSheet = false @State private var sheetColor:ColorData = ColorData(red: 1.0, green:1.0, blue:1.0) @State private var copiedToClipboard = false @State var counter: Int = 0 @State var origin: CGPoint = .zero @State private var sampleImage: UIImage? = UIImage(named: "palm") @State private var photosPickerItem: PhotosPickerItem? @State private var loading = false @State private var loadingIndicator = false @State private var sampleColors:Colors = Colors( colors: [ ColorData(red: 0.28, green: 0.28, blue: 0.20), ColorData(red: 0.72, green: 0.75, blue: 0.73), ColorData(red: 0.08, green: 0.54, blue: 0.63), ColorData(red: 0.59, green: 0.49, blue: 0.35) ] ) var body: some View { ZStack{ MeshGradient( width: 2, height: 2, points: [ [0, 0], [1, 0], [0, 1], [1, 1], ], colors: createMeshColors()) .ignoresSafeArea() VStack(spacing:48){ // Sample image Image(uiImage: sampleImage!) .resizable() .scaledToFill() .modifier(RippleEffect(at: origin, trigger: counter)) .frame(maxWidth:320, maxHeight:360) .cornerRadius(24) .clipped() .shadow(radius: 10) .overlay( ZStack{ Color .black .opacity(0.75) Text("Generating palette...") .bold() .foregroundStyle(.white) } .cornerRadius(24) .clipped() .opacity(loadingIndicator ? 1 : 0) ) VStack(spacing: 48){ ZStack{ // Color swatches HStack(spacing:loading ? -60 : 20){ ForEach(Array(sampleColors.colors.enumerated()), id: \.offset) { index, color in Circle() .stroke(.white, lineWidth: 8) .fill(Color(red: color.red,green: color.green,blue: color.blue)) .frame(width:60) .onTapGesture { sheetColor = color presentSheet.toggle() } } } if loadingIndicator{ Circle() .fill(.white) .stroke(.white, lineWidth:8) .frame(width:60) .overlay( ProgressView() .tint(.black) ) } } PhotosPicker(selection: $photosPickerItem, matching: .images){ Text("Choose Photo") .bold() } .buttonStyle(.borderedProminent) .tint(.black) .controlSize(.extraLarge) } } } .sheet(isPresented: $presentSheet) { ColorDetailView(currentColor: $sheetColor) .presentationDetents([.medium]) .presentationBackground(.thickMaterial) } .onChange(of: photosPickerItem){ _, _ in Task{ loading = true loadingIndicator = true if let photosPickerItem, let data = try? await photosPickerItem.loadTransferable(type: Data.self){ if let image = UIImage(data: data){ withAnimation(){ sampleImage = image } try await generateColorPalette(image: sampleImage!) } } withAnimation(){ loading = false } withAnimation(.bouncy){ loadingIndicator = false } counter += 1 } } } func createMeshColors() -> [Color] { var meshColors: [Color] = [] for color in sampleColors.colors { meshColors.append(Color(red: color.red, green: color.green, blue: color.blue)) } return meshColors } func generateColorPalette(image: UIImage) async throws { let jsonString = await AIProxyIntegration.getColorPalette(forImage: image) let jsonData = jsonString!.data(using: .utf8)! do { sampleColors = try JSONDecoder().decode(Colors.self, from: jsonData) } catch { fatalError("Failed to decode JSON: \(error)") } } } #Preview { ContentView() } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Ripple.metal ================================================ // // Ripple.metal // AIColorPalette // // Created by Todd Hamilton on 6/21/24. // // Insert #include #include using namespace metal; [[ stitchable ]] half4 Ripple( float2 position, SwiftUI::Layer layer, float2 origin, float time, float amplitude, float frequency, float decay, float speed ) { // The distance of the current pixel position from `origin`. float distance = length(position - origin); // The amount of time it takes for the ripple to arrive at the current pixel position. float delay = distance / speed; // Adjust for delay, clamp to 0. time -= delay; time = max(0.0, time); // The ripple is a sine wave that Metal scales by an exponential decay // function. float rippleAmount = amplitude * sin(frequency * time) * exp(-decay * time); // A vector of length `amplitude` that points away from position. float2 n = normalize(position - origin); // Scale `n` by the ripple amount at the current pixel position and add it // to the current pixel position. // // This new position moves toward or away from `origin` based on the // sign and magnitude of `rippleAmount`. float2 newPosition = position + rippleAmount * n; // Sample the layer at the new position. half4 color = layer.sample(newPosition); // Lighten or darken the color based on the ripple amount and its alpha // component. color.rgb += 0.3 * (rippleAmount / amplitude) * color.a; return color; } ================================================ FILE: Demos/AIColorPalette/AIColorPalette/Ripple.swift ================================================ // // Ripple.swift // AIColorPalette // // Created by Todd Hamilton on 6/21/24. // import SwiftUI struct PushEffect: ViewModifier { var trigger: T func body(content: Content) -> some View { content.keyframeAnimator( initialValue: 1.0, trigger: trigger ) { view, value in view.visualEffect { view, _ in view.scaleEffect(value) } } keyframes: { _ in SpringKeyframe(0.95, duration: 0.2, spring: .snappy) SpringKeyframe(1.0, duration: 0.2, spring: .bouncy) } } } /// A modifer that performs a ripple effect to its content whenever its /// trigger value changes. struct RippleEffect: ViewModifier { var origin: CGPoint var trigger: T init(at origin: CGPoint, trigger: T) { self.origin = origin self.trigger = trigger } func body(content: Content) -> some View { let origin = origin let duration = duration content.keyframeAnimator( initialValue: 0, trigger: trigger ) { view, elapsedTime in view.modifier(RippleModifier( origin: origin, elapsedTime: elapsedTime, duration: duration )) } keyframes: { _ in MoveKeyframe(0) LinearKeyframe(duration, duration: duration) } } var duration: TimeInterval { 3 } } /// A modifier that applies a ripple effect to its content. struct RippleModifier: ViewModifier { var origin: CGPoint var elapsedTime: TimeInterval var duration: TimeInterval var amplitude: Double = 12 var frequency: Double = 15 var decay: Double = 8 var speed: Double = 1200 func body(content: Content) -> some View { let shader = ShaderLibrary.Ripple( .float2(origin), .float(elapsedTime), // Parameters .float(amplitude), .float(frequency), .float(decay), .float(speed) ) let maxSampleOffset = maxSampleOffset let elapsedTime = elapsedTime let duration = duration content.visualEffect { view, _ in view.layerEffect( shader, maxSampleOffset: maxSampleOffset, isEnabled: 0 < elapsedTime && elapsedTime < duration ) } } var maxSampleOffset: CGSize { CGSize(width: amplitude, height: amplitude) } } extension View { func onPressingChanged(_ action: @escaping (CGPoint?) -> Void) -> some View { modifier(SpatialPressingGestureModifier(action: action)) } } struct SpatialPressingGestureModifier: ViewModifier { var onPressingChanged: (CGPoint?) -> Void @State var currentLocation: CGPoint? init(action: @escaping (CGPoint?) -> Void) { self.onPressingChanged = action } func body(content: Content) -> some View { let gesture = SpatialPressingGesture(location: $currentLocation) content .gesture(gesture) .onChange(of: currentLocation, initial: false) { _, location in onPressingChanged(location) } } } struct SpatialPressingGesture: UIGestureRecognizerRepresentable { final class Coordinator: NSObject, UIGestureRecognizerDelegate { @objc func gestureRecognizer( _ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer ) -> Bool { true } } @Binding var location: CGPoint? func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator { Coordinator() } func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer { let recognizer = UILongPressGestureRecognizer() recognizer.minimumPressDuration = 0 recognizer.delegate = context.coordinator return recognizer } func handleUIGestureRecognizerAction( _ recognizer: UIGestureRecognizerType, context: Context) { switch recognizer.state { case .began: location = context.converter.localLocation case .ended, .cancelled, .failed: location = nil default: break } } } ================================================ FILE: Demos/AIColorPalette/AIColorPalette.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0D24C88C2C29DA4B009E0102 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 0D24C88B2C29DA4B009E0102 /* AIProxy */; }; 2C22A6612C25D9EA00C9B21C /* ColorData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22A6602C25D9EA00C9B21C /* ColorData.swift */; }; 2C754B602C267044008A4329 /* Ripple.metal in Sources */ = {isa = PBXBuildFile; fileRef = 2C754B5F2C267044008A4329 /* Ripple.metal */; }; 2C754B622C2674EF008A4329 /* Ripple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C754B612C2674EA008A4329 /* Ripple.swift */; }; 2C754B642C27831B008A4329 /* ColorDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C754B632C27831B008A4329 /* ColorDetailView.swift */; }; 2CE5D2E62C247CA500E53983 /* AIColorPaletteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE5D2E52C247CA500E53983 /* AIColorPaletteApp.swift */; }; 2CE5D2E82C247CA500E53983 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE5D2E72C247CA500E53983 /* ContentView.swift */; }; 2CE5D2EA2C247CA600E53983 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CE5D2E92C247CA600E53983 /* Assets.xcassets */; }; 2CE5D2ED2C247CA600E53983 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2CE5D2EC2C247CA600E53983 /* Preview Assets.xcassets */; }; 2CF0AB4E2C5C133B00BFA8BC /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CF0AB4D2C5C133B00BFA8BC /* AIProxy */; }; 2CFD48A92C24BC480065C162 /* AIProxyIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFD48A82C24BC480065C162 /* AIProxyIntegration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C22A6602C25D9EA00C9B21C /* ColorData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorData.swift; sourceTree = ""; }; 2C754B5F2C267044008A4329 /* Ripple.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Ripple.metal; sourceTree = ""; }; 2C754B612C2674EA008A4329 /* Ripple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ripple.swift; sourceTree = ""; }; 2C754B632C27831B008A4329 /* ColorDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorDetailView.swift; sourceTree = ""; }; 2CE5D2E22C247CA500E53983 /* AIColorPalette.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AIColorPalette.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2CE5D2E52C247CA500E53983 /* AIColorPaletteApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIColorPaletteApp.swift; sourceTree = ""; }; 2CE5D2E72C247CA500E53983 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2CE5D2E92C247CA600E53983 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2CE5D2EC2C247CA600E53983 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2CFD48A82C24BC480065C162 /* AIProxyIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIProxyIntegration.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2CE5D2DF2C247CA500E53983 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0D24C88C2C29DA4B009E0102 /* AIProxy in Frameworks */, 2CF0AB4E2C5C133B00BFA8BC /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CE5D2D92C247CA500E53983 = { isa = PBXGroup; children = ( 2CE5D2E42C247CA500E53983 /* AIColorPalette */, 2CE5D2E32C247CA500E53983 /* Products */, ); sourceTree = ""; }; 2CE5D2E32C247CA500E53983 /* Products */ = { isa = PBXGroup; children = ( 2CE5D2E22C247CA500E53983 /* AIColorPalette.app */, ); name = Products; sourceTree = ""; }; 2CE5D2E42C247CA500E53983 /* AIColorPalette */ = { isa = PBXGroup; children = ( 2CE5D2E52C247CA500E53983 /* AIColorPaletteApp.swift */, 2CFD48A82C24BC480065C162 /* AIProxyIntegration.swift */, 2C22A6602C25D9EA00C9B21C /* ColorData.swift */, 2C754B632C27831B008A4329 /* ColorDetailView.swift */, 2CE5D2E72C247CA500E53983 /* ContentView.swift */, 2C754B612C2674EA008A4329 /* Ripple.swift */, 2C754B5F2C267044008A4329 /* Ripple.metal */, 2CE5D2E92C247CA600E53983 /* Assets.xcassets */, 2CE5D2EB2C247CA600E53983 /* Preview Content */, ); path = AIColorPalette; sourceTree = ""; }; 2CE5D2EB2C247CA600E53983 /* Preview Content */ = { isa = PBXGroup; children = ( 2CE5D2EC2C247CA600E53983 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CE5D2E12C247CA500E53983 /* AIColorPalette */ = { isa = PBXNativeTarget; buildConfigurationList = 2CE5D2F02C247CA600E53983 /* Build configuration list for PBXNativeTarget "AIColorPalette" */; buildPhases = ( 2CE5D2DE2C247CA500E53983 /* Sources */, 2CE5D2DF2C247CA500E53983 /* Frameworks */, 2CE5D2E02C247CA500E53983 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = AIColorPalette; packageProductDependencies = ( 0D24C88B2C29DA4B009E0102 /* AIProxy */, 2CF0AB4D2C5C133B00BFA8BC /* AIProxy */, ); productName = AIColorPalette; productReference = 2CE5D2E22C247CA500E53983 /* AIColorPalette.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CE5D2DA2C247CA500E53983 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2CE5D2E12C247CA500E53983 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2CE5D2DD2C247CA500E53983 /* Build configuration list for PBXProject "AIColorPalette" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CE5D2D92C247CA500E53983; packageReferences = ( 2CF0AB4C2C5C133B00BFA8BC /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2CE5D2E32C247CA500E53983 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CE5D2E12C247CA500E53983 /* AIColorPalette */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CE5D2E02C247CA500E53983 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CE5D2ED2C247CA600E53983 /* Preview Assets.xcassets in Resources */, 2CE5D2EA2C247CA600E53983 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CE5D2DE2C247CA500E53983 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2CE5D2E82C247CA500E53983 /* ContentView.swift in Sources */, 2C754B622C2674EF008A4329 /* Ripple.swift in Sources */, 2C22A6612C25D9EA00C9B21C /* ColorData.swift in Sources */, 2CE5D2E62C247CA500E53983 /* AIColorPaletteApp.swift in Sources */, 2C754B642C27831B008A4329 /* ColorDetailView.swift in Sources */, 2C754B602C267044008A4329 /* Ripple.metal in Sources */, 2CFD48A92C24BC480065C162 /* AIProxyIntegration.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CE5D2EE2C247CA600E53983 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CE5D2EF2C247CA600E53983 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CE5D2F12C247CA600E53983 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIColorPalette/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Needed to capture images and generate color palettes."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIColorPalette; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2CE5D2F22C247CA600E53983 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"AIColorPalette/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Needed to capture images and generate color palettes."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.AIColorPalette; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CE5D2DD2C247CA500E53983 /* Build configuration list for PBXProject "AIColorPalette" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CE5D2EE2C247CA600E53983 /* Debug */, 2CE5D2EF2C247CA600E53983 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CE5D2F02C247CA600E53983 /* Build configuration list for PBXNativeTarget "AIColorPalette" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CE5D2F12C247CA600E53983 /* Debug */, 2CE5D2F22C247CA600E53983 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CF0AB4C2C5C133B00BFA8BC /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 0D24C88B2C29DA4B009E0102 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; productName = AIProxy; }; 2CF0AB4D2C5C133B00BFA8BC /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CF0AB4C2C5C133B00BFA8BC /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CE5D2DA2C247CA500E53983 /* Project object */; } ================================================ FILE: Demos/AIColorPalette/README.md ================================================ ## Example projects ### AIColorPalette #### About AIColorPalette generates a color palette from a photo in your camera roll. This project uses [AIProxy](https://www.aiproxy.pro) to secure your OpenAI key. #### Features demonstrated - Shows off new iOS 18 SwiftUI effects - Calls the OpenAI chat completion endpoint - Submits a photo as the chat completion request body - Compels OpenAI to return valid JSON in the chat completion response #### Minimum requirements The minimum deployment target for this sample app is 18.0, as it uses beta UI effects. You'll need: - macOS Sonoma 14.5 or higher - Xcode Beta 16.0 or higher #### How to run with your own AIProxy settings - Set the `AIPROXY_DEVICE_CHECK_BYPASS` environment variable in your Xcode build settings. Refer to the [README](https://github.com/lzell/AIProxySwift?tab=readme-ov-file#adding-this-package-as-a-dependency-to-your-xcode-project) for instructions on adding an env variable to your Xcode project. - Replace the `partialKey` placeholder value in `AIColorPalette/AIProxyIntegration.swift` with the value provided to you in the AIProxy dashboard when you submit your OpenAI key ================================================ FILE: Demos/Chat/Chat/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import AIProxy enum AppConstants { #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // static let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Chat/Chat/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapChat") ================================================ FILE: Demos/Chat/Chat/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Chat/Chat/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "chat.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Chat/Chat/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Chat/Chat/ChatApp.swift ================================================ // // ChatApp.swift // Chat // // Created by Lou Zell // import SwiftUI @main @MainActor struct ChatApp: App { @State private var chatManager = ChatManager() var body: some Scene { WindowGroup { ChatView(chatManager: chatManager) } } } ================================================ FILE: Demos/Chat/Chat/ChatBubble.swift ================================================ // // ChatBubbleView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI /// A view to contain a single message from either the user or OpenAI. struct ChatBubble: View { /// The message to display let message: ChatMessage /// Whether to animate in the chat bubble let animateIn: Bool /// State used to animate in the chat bubble if `animateIn` is true @State private var animationTrigger = false var body: some View { HStack(alignment: .top, spacing: 12) { chatIcon VStack(alignment: .leading) { chatName chatBody } } .opacity(bubbleOpacity) .animation(.easeIn(duration: 0.75), value: animationTrigger) .onAppear { adjustAnimationTriggerIfNecessary() } } private var bubbleOpacity: Double { guard animateIn else { return 1 } return animationTrigger ? 1 : 0 } private func adjustAnimationTriggerIfNecessary() { guard animateIn else { return } animationTrigger = true } private var chatIcon: some View { Image(systemName: message.isUser ? "person.circle.fill" : "command.circle.fill") .font(.title2) .frame(width:24, height:24) .foregroundColor(message.isUser ? .primary : .teal) } private var chatName: some View { Text(message.isUser ? "You" : "ChatGPT") .fontWeight(.bold) .frame(maxWidth: .infinity, maxHeight:24, alignment: .leading) } @ViewBuilder private var chatBody: some View { if message.isUser { Text(LocalizedStringKey(message.text)) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.primary) } else { if message.isWaitingForFirstText { ProgressView() } else { Text(LocalizedStringKey(message.text)) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.primary) } } } } #Preview { ChatBubble(message: ChatMessage(text: "hello", isUser: false), animateIn: false) .frame(maxWidth:.infinity) .padding() } ================================================ FILE: Demos/Chat/Chat/ChatDataLoader.swift ================================================ // // ChatDataLoader.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import AIProxy enum ChatDataLoaderError: Error { case busy } /// Asynchronously sends prompts to OpenAI and streams back the response final actor ChatDataLoader { private var streamingResponseAccumulator: String? /// All chat messages, including user queries and openai responses. /// The full history of the chat is sent with each request to openai to provide an ongoing conversation with memory. private var messages = [OpenAIChatCompletionMessage]() /// Add a user message to the conversation and stream back the openai response func addToConversation(_ prompt: String) async throws -> AsyncThrowingStream { guard streamingResponseAccumulator == nil else { throw ChatDataLoaderError.busy } self.streamingResponseAccumulator = "" self.messages.append((.user(content: .text(prompt)))) let requestBody = OpenAIChatCompletionRequestBody( model: "gpt-4o-mini", messages: [.user(content: .text(prompt))] ) let stream = try await AppConstants.openAIService.streamingChatCompletionRequest(body: requestBody) return AsyncThrowingStream { continuation in let task = Task { for try await result in stream { guard let choice = result.choices.first, let content = choice.delta.content else { self.addAccumulatedResponseToMessageHistory() continuation.finish() return } self.addToResponseAccumulator(text: content) continuation.yield(content) } } continuation.onTermination = { @Sendable termination in task.cancel() if case .cancelled = termination { Task { await self.addAccumulatedResponseToMessageHistory() } } } } } private func addAccumulatedResponseToMessageHistory() { if let accumulator = self.streamingResponseAccumulator { self.messages.append(.assistant(content: .text(accumulator))) self.streamingResponseAccumulator = nil } } private func addToResponseAccumulator(text: String) { if let accumulator = self.streamingResponseAccumulator { self.streamingResponseAccumulator = accumulator + text } else { self.streamingResponseAccumulator = text } } } ================================================ FILE: Demos/Chat/Chat/ChatInputView.swift ================================================ // // ChatInputView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI /// A view for the user to enter chat messages struct ChatInputView: View { private enum FocusedField { case newMessageText } /// Is a streaming chat response in progress let isStreamingResponse: Bool /// Callback invoked when the user taps the submit button or presses return var didSubmit: (String) -> Void /// Callback invoked when the user taps on the stop button var didTapStop: () -> Void /// State to collect new text messages @State private var newMessageText: String = "" @FocusState private var focusedField: FocusedField? var body: some View { HStack(spacing:0){ chatInputTextField actionButton } .padding(8) } private var chatInputTextField: some View { TextField("Type a message", text: $newMessageText, axis: .vertical) .focused($focusedField, equals: .newMessageText) .lineLimit(5) .padding(.horizontal, 16) .padding(.vertical, 10) .background( RoundedRectangle(cornerRadius:30) .fill(Color(.tertiarySystemGroupedBackground)) .stroke(.separator) ) .onAppear { focusedField = .newMessageText } .onSubmit { didSubmit(newMessageText) newMessageText = "" } } private var actionButton: some View { Button { if isStreamingResponse { didTapStop() } else { didSubmit(newMessageText) newMessageText = "" } } label:{ Image(systemName: isStreamingResponse ? "stop.circle.fill" : "arrow.up.circle.fill") .font(.title) .foregroundColor((isStreamingResponse || !newMessageText.isEmpty) ? .primary : .secondary) .frame(width:40, height:40) } .contentTransition(.symbolEffect(.replace)) .padding(.horizontal, 8) } } #Preview { ChatInputView(isStreamingResponse: false, didSubmit: { _ in }, didTapStop: { }) } ================================================ FILE: Demos/Chat/Chat/ChatManager.swift ================================================ // // ChatManager.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI @MainActor @Observable final class ChatManager { /// Messages sent from the user or received from OpenAI var messages = [ChatMessage]() /// Returns true if OpenAI is still streaming a response back to us var isProcessing: Bool { return self.streamTask != nil } /// Task that encapsulates OpenAI's streaming response. /// Cancel this to interrupt OpenAI's response. private var streamTask: Task? = nil private let chatDataLoader = ChatDataLoader() /// Send a new message to OpenAI and start streaming OpenAI's response func send(message: ChatMessage) { self.messages.append(message) self.setupStreamingTask(withPrompt: message.text) } /// Stop the streaming response from OpenAI func stop() { self.streamTask?.cancel() self.streamTask = nil } private func setupStreamingTask(withPrompt prompt: String) { self.messages.append(ChatMessage(text: "", isUser: false, isWaitingForFirstText: true)) self.streamTask = Task { [weak self] in guard let this = self else { return } do { let responseStream = try await this.chatDataLoader.addToConversation(prompt) for try await responseText in responseStream { if var last = this.messages.popLast() { last.isWaitingForFirstText = false last.text += responseText this.messages.append(last) } } this.streamTask = nil } catch { AppLogger.error("Received an unexpected error from OpenAI streaming: \(error)") } } } } ================================================ FILE: Demos/Chat/Chat/ChatMessage.swift ================================================ // // ChatMessage.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation /// Data model to represent a chat message struct ChatMessage: Identifiable, Equatable { /// Unique identifier let id = UUID() /// The body of the chat message var text: String /// True if the message originates from the user, false if it originates from OpenAI let isUser: Bool /// Indicates that we are waiting for the first bit of message content from OpenAI var isWaitingForFirstText = false } ================================================ FILE: Demos/Chat/Chat/ChatView.swift ================================================ // // ChatView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI @MainActor struct ChatView: View { private enum ScrollChange { case userInteraction case programmatic } /// The chat manager that controls communication with OpenAI let chatManager: ChatManager /// Should we auto-scroll the scrollview as message content arrives @State private var isTrackingScrollView = false /// As the scroll position changes, this state indicates whether the change originated from a user interation or programmatically @State private var scrollChange: ScrollChange = .userInteraction /// The user's scroll position of the content view (e.g. where in the chat message contents they are positioned) @State private var scrollPosition: CGPoint = .zero { didSet { switch scrollChange { case .userInteraction: isTrackingScrollView = false case .programmatic: scrollChange = .userInteraction } } } /// The height of the scroll view (e.g. the frame's height) @State private var scrollViewHeight: CGFloat = .infinity /// The height of the contents contained within the scroll view @State private var scrollViewContentHeight: CGFloat = .zero @State private var scrollTrigger = false var body: some View { VStack { autoScrollingChatView .padding([.top, .leading, .trailing]) ZStack(alignment: .top) { AutoScrollButton(isVisible: !isTrackingScrollView && scrollViewContentHeight > scrollViewHeight) { isTrackingScrollView = true scrollTrigger.toggle() } .offset(y: -50) ChatInputView(isStreamingResponse: chatManager.isProcessing, didSubmit: { sendMessage($0) }, didTapStop: { chatManager.stop() }) } } } private var autoScrollingChatView: some View { ScrollViewReader { scrollView in ScrollView(.vertical) { ChatMessagesView(chatMessages: chatManager.messages) .id("MessagesView") .background(GeometryReader { geometry in Color.clear .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("chatScrollView")).origin) .preference(key: ScrollHeightPreferenceKey.self, value: geometry.size.height) }) .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in scrollPosition = value } .onPreferenceChange(ScrollHeightPreferenceKey.self) { value in scrollViewContentHeight = value } } .coordinateSpace(name: "chatScrollView") .background( GeometryReader { outerScrollViewGeometry in Color.clear .preference(key: ScrollHeightPreferenceKey.self, value: outerScrollViewGeometry.size.height) } ) .onPreferenceChange(ScrollHeightPreferenceKey.self) { value in scrollViewHeight = value } .onChange(of: scrollTrigger) { scrollChange = .programmatic scrollView.scrollTo("MessagesView", anchor: .bottom) } .onChange(of: chatManager.messages) { _, _ in if isTrackingScrollView { scrollTrigger.toggle() } } } } private func sendMessage(_ message: String) { guard !message.isEmpty else { return } chatManager.send(message: ChatMessage(text: message, isUser: true)) } } private struct ChatMessagesView: View { /// Flags to prevent messages from animating in multiple times as dependencies that drive `body` change @State private var shouldAnimateMessageIn = [UUID: Bool]() let chatMessages: [ChatMessage] var body: some View { VStack(alignment: .leading) { ChatBubble(message: ChatMessage(text: "How can I help you?", isUser: false), animateIn: true) .listRowSeparator(.hidden) ForEach(chatMessages) { message in ChatBubble(message: message, animateIn: shouldAnimateMessageIn[message.id] ?? true) .listRowSeparator(.hidden) .transition(.opacity) .onAppear { shouldAnimateMessageIn[message.id] = false } } } } } // Credit: https://saeedrz.medium.com/detect-scroll-position-in-swiftui-3d6e0d81fc6b private struct ScrollOffsetPreferenceKey: PreferenceKey { static var defaultValue: CGPoint = .zero static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) {} } private struct ScrollHeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = .zero static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} } private struct AutoScrollButton: View { let isVisible: Bool let action: () -> Void var body: some View { Button { action() } label: { Image(systemName: "arrowshape.down.fill") .font(.body) .foregroundColor(.primary) .padding(8) } .background( Circle() .fill(Color(.secondarySystemBackground)) .stroke(.primary.opacity(0.1), lineWidth:1) ).shadow(color:.primary.opacity(0.14), radius: 3, x:0, y:2) .opacity(isVisible ? 1 : 0) } } #Preview { ChatView(chatManager: ChatManager()) } ================================================ FILE: Demos/Chat/Chat/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Chat/Chat.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0DDD23062B4DE02100B2AE5C /* ChatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23052B4DE02100B2AE5C /* ChatApp.swift */; }; 0DDD230A2B4DE02200B2AE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23092B4DE02200B2AE5C /* Assets.xcassets */; }; 0DDD230D2B4DE02200B2AE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD230C2B4DE02200B2AE5C /* Preview Assets.xcassets */; }; 0DDD23F42B4DE6DC00B2AE5C /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23F32B4DE6DC00B2AE5C /* ChatView.swift */; }; 0DDD23F62B4DE6FA00B2AE5C /* ChatBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23F52B4DE6FA00B2AE5C /* ChatBubble.swift */; }; 0DDD23FB2B4DE70A00B2AE5C /* ChatMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23F72B4DE70A00B2AE5C /* ChatMessage.swift */; }; 0DDD23FC2B4DE70A00B2AE5C /* ChatManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23F82B4DE70A00B2AE5C /* ChatManager.swift */; }; 0DDD23FD2B4DE70A00B2AE5C /* ChatInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23F92B4DE70A00B2AE5C /* ChatInputView.swift */; }; 0DDD23FE2B4DE70A00B2AE5C /* ChatDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23FA2B4DE70A00B2AE5C /* ChatDataLoader.swift */; }; 0DDD24002B4DE79300B2AE5C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23FF2B4DE79300B2AE5C /* AppConstants.swift */; }; 0DDD24022B4DE7AF00B2AE5C /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24012B4DE7AF00B2AE5C /* AppLogger.swift */; }; 2CE7709B2C9B59C000EC6C74 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CE7709A2C9B59C000EC6C74 /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0DDD23022B4DE02100B2AE5C /* Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DDD23052B4DE02100B2AE5C /* ChatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatApp.swift; sourceTree = ""; }; 0DDD23092B4DE02200B2AE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0DDD230C2B4DE02200B2AE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0DDD23F32B4DE6DC00B2AE5C /* ChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 0DDD23F52B4DE6FA00B2AE5C /* ChatBubble.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBubble.swift; sourceTree = ""; }; 0DDD23F72B4DE70A00B2AE5C /* ChatMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessage.swift; sourceTree = ""; }; 0DDD23F82B4DE70A00B2AE5C /* ChatManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatManager.swift; sourceTree = ""; }; 0DDD23F92B4DE70A00B2AE5C /* ChatInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputView.swift; sourceTree = ""; }; 0DDD23FA2B4DE70A00B2AE5C /* ChatDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatDataLoader.swift; sourceTree = ""; }; 0DDD23FF2B4DE79300B2AE5C /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 0DDD24012B4DE7AF00B2AE5C /* AppLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0DDD22FF2B4DE02100B2AE5C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CE7709B2C9B59C000EC6C74 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0DDD22F92B4DE02100B2AE5C = { isa = PBXGroup; children = ( 0DDD23042B4DE02100B2AE5C /* Chat */, 0DDD23032B4DE02100B2AE5C /* Products */, ); sourceTree = ""; }; 0DDD23032B4DE02100B2AE5C /* Products */ = { isa = PBXGroup; children = ( 0DDD23022B4DE02100B2AE5C /* Chat.app */, ); name = Products; sourceTree = ""; }; 0DDD23042B4DE02100B2AE5C /* Chat */ = { isa = PBXGroup; children = ( 0DDD23FF2B4DE79300B2AE5C /* AppConstants.swift */, 0DDD24012B4DE7AF00B2AE5C /* AppLogger.swift */, 0DDD23052B4DE02100B2AE5C /* ChatApp.swift */, 0DDD23F52B4DE6FA00B2AE5C /* ChatBubble.swift */, 0DDD23FA2B4DE70A00B2AE5C /* ChatDataLoader.swift */, 0DDD23F92B4DE70A00B2AE5C /* ChatInputView.swift */, 0DDD23F82B4DE70A00B2AE5C /* ChatManager.swift */, 0DDD23F72B4DE70A00B2AE5C /* ChatMessage.swift */, 0DDD23F32B4DE6DC00B2AE5C /* ChatView.swift */, 0DDD23092B4DE02200B2AE5C /* Assets.xcassets */, 0DDD230B2B4DE02200B2AE5C /* Preview Content */, ); path = Chat; sourceTree = ""; }; 0DDD230B2B4DE02200B2AE5C /* Preview Content */ = { isa = PBXGroup; children = ( 0DDD230C2B4DE02200B2AE5C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0DDD23012B4DE02100B2AE5C /* Chat */ = { isa = PBXNativeTarget; buildConfigurationList = 0DDD23102B4DE02200B2AE5C /* Build configuration list for PBXNativeTarget "Chat" */; buildPhases = ( 0DDD22FE2B4DE02100B2AE5C /* Sources */, 0DDD22FF2B4DE02100B2AE5C /* Frameworks */, 0DDD23002B4DE02100B2AE5C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Chat; packageProductDependencies = ( 2CE7709A2C9B59C000EC6C74 /* AIProxy */, ); productName = Chat; productReference = 0DDD23022B4DE02100B2AE5C /* Chat.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0DDD22FA2B4DE02100B2AE5C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1510; TargetAttributes = { 0DDD23012B4DE02100B2AE5C = { CreatedOnToolsVersion = 15.1; }; }; }; buildConfigurationList = 0DDD22FD2B4DE02100B2AE5C /* Build configuration list for PBXProject "Chat" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0DDD22F92B4DE02100B2AE5C; packageReferences = ( 2CE770992C9B59C000EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 0DDD23032B4DE02100B2AE5C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0DDD23012B4DE02100B2AE5C /* Chat */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0DDD23002B4DE02100B2AE5C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD230D2B4DE02200B2AE5C /* Preview Assets.xcassets in Resources */, 0DDD230A2B4DE02200B2AE5C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0DDD22FE2B4DE02100B2AE5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD23FE2B4DE70A00B2AE5C /* ChatDataLoader.swift in Sources */, 0DDD23F42B4DE6DC00B2AE5C /* ChatView.swift in Sources */, 0DDD23FD2B4DE70A00B2AE5C /* ChatInputView.swift in Sources */, 0DDD24002B4DE79300B2AE5C /* AppConstants.swift in Sources */, 0DDD23FB2B4DE70A00B2AE5C /* ChatMessage.swift in Sources */, 0DDD23F62B4DE6FA00B2AE5C /* ChatBubble.swift in Sources */, 0DDD24022B4DE7AF00B2AE5C /* AppLogger.swift in Sources */, 0DDD23FC2B4DE70A00B2AE5C /* ChatManager.swift in Sources */, 0DDD23062B4DE02100B2AE5C /* ChatApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0DDD230E2B4DE02200B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0DDD230F2B4DE02200B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 0DDD23112B4DE02200B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Chat/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.chat; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0DDD23122B4DE02200B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Chat/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.chat; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0DDD22FD2B4DE02100B2AE5C /* Build configuration list for PBXProject "Chat" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD230E2B4DE02200B2AE5C /* Debug */, 0DDD230F2B4DE02200B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0DDD23102B4DE02200B2AE5C /* Build configuration list for PBXNativeTarget "Chat" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23112B4DE02200B2AE5C /* Debug */, 0DDD23122B4DE02200B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CE770992C9B59C000EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CE7709A2C9B59C000EC6C74 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CE770992C9B59C000EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DDD22FA2B4DE02100B2AE5C /* Project object */; } ================================================ FILE: Demos/Classifier/Classifier/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftData import AIProxy enum AppConstants { static let videoSampleQueue = DispatchQueue(label: "com.AIProxyBootstrap.videoSampleQueue") #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // static let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Classifier/Classifier/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapClassifier") ================================================ FILE: Demos/Classifier/Classifier/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Classifier/Classifier/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "classify.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Classifier/Classifier/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Classifier/Classifier/CameraControlsView.swift ================================================ // // CameraControlsView.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI struct CameraControlsView: View { let shutterButtonAction: () -> Void var body: some View { Button(action: shutterButtonAction) { ZStack{ Circle() .fill(.clear) .stroke(.mint, lineWidth: 4) .frame(width:72, height: 72) Circle() .fill(.mint.gradient) .frame(width:60, height: 60) Image(systemName: "camera") .font(.title2) .fontWeight(.semibold) .foregroundColor(.black.opacity(0.4)) } } .buttonStyle(.plain) } } #Preview { CameraControlsView(shutterButtonAction: {}) } ================================================ FILE: Demos/Classifier/Classifier/CameraDataLoader.swift ================================================ // // CameraFrameHandler.swift // AIProxyBootstrap // // Created by Todd Hamilton // import AVFoundation import CoreImage /// Vends camera frames from the built-in back camera. final actor CameraDataLoader { private let sampleBufferDelegate = CameraFrameSampleBufferDelegate() private let captureSession = AVCaptureSession() /// Streams images of the camera frame. /// Use the returned stream in a `for await` loop. func imageStream() -> AsyncStream { self.setupCaptureSession() self.captureSession.startRunning() return AsyncStream { [weak self] continuation in self?.sampleBufferDelegate.didReceiveImage = { image in continuation.yield(image) } } } private func setupCaptureSession() { let videoOutput = AVCaptureVideoDataOutput() guard let videoDevice = AVCaptureDevice.default(.builtInDualWideCamera,for: .video, position: .back) else { return } guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else { return } guard captureSession.canAddInput(videoDeviceInput) else { return } captureSession.addInput(videoDeviceInput) videoOutput.setSampleBufferDelegate( sampleBufferDelegate, queue: AppConstants.videoSampleQueue ) captureSession.addOutput(videoOutput) videoOutput.connection(with: .video)?.videoRotationAngle = 90 } } private final class CameraFrameSampleBufferDelegate: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { private let coreImageContext = CIContext() var didReceiveImage: ((CGImage) -> Void)? /// Delegate implementation for AVCaptureVideoDataOutputSampleBufferDelegate conformance func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { dispatchPrecondition(condition: .onQueue(AppConstants.videoSampleQueue)) guard let cgImage = self.imageFromSampleBuffer(sampleBuffer: sampleBuffer) else { AppLogger.info("Could not convert a sample buffer from the camera into a CGImage") return } self.didReceiveImage?(cgImage) } private func imageFromSampleBuffer(sampleBuffer: CMSampleBuffer) -> CGImage? { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { AppLogger.info("Could not get an image buffer from CMSampleBuffer") return nil } let ciImage = CIImage(cvPixelBuffer: imageBuffer) guard let cgImage = self.coreImageContext.createCGImage(ciImage, from: ciImage.extent) else { AppLogger.info("Could not create a CGImage using a core image context") return nil } return cgImage } } ================================================ FILE: Demos/Classifier/Classifier/CameraFrameManager.swift ================================================ // // CameraFrameManager.swift // AIProxyBootstrap // // Created by Lou Zell // import AVFoundation import Foundation import SwiftUI @MainActor @Observable final class CameraFrameManager { /// The most recent camera frame of the back-facing built-in camera private(set) var cameraFrameImage: CGImage? private let cameraDataLoader = CameraDataLoader() init() { self.checkPermission() { [weak self] granted in if granted { self?.startCapturingCameraFrames() } } } private func startCapturingCameraFrames() { Task { let stream = await self.cameraDataLoader.imageStream() for await image in stream { self.cameraFrameImage = image } } } private func checkPermission(checkComplete: @escaping (Bool) -> Void) { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: checkComplete(true) case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { granted in checkComplete(granted) } default: checkComplete(false) } } } ================================================ FILE: Demos/Classifier/Classifier/CameraView.swift ================================================ // // CameraView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct CameraView: View { /// The camera frame image to display var image: CGImage? private let label = Text("frame") var body: some View { GeometryReader { geo in VStack { if let image = image { Image(image, scale: 0.5, orientation: .up, label: label) .resizable() .scaledToFill() .frame(maxWidth:geo.size.width, maxHeight: geo.size.width) .clipShape(RoundedRectangle(cornerRadius: 14)) .padding() } else { Color.black .frame(maxWidth:geo.size.width, maxHeight: geo.size.width) .clipShape(RoundedRectangle(cornerRadius: 14)) .padding() } } } } } #Preview { CameraView() } ================================================ FILE: Demos/Classifier/Classifier/ClassifierApp.swift ================================================ // // ClassifierApp.swift // Classifier // // Created by Lou Zell // import SwiftUI @main @MainActor struct ClassifierApp: App { @State private var cameraFrameManager = CameraFrameManager() @State private var classifierManager = ClassifierManager() var body: some Scene { WindowGroup { ClassifierView(cameraFrameManager: cameraFrameManager, classifierManager: classifierManager) } } } ================================================ FILE: Demos/Classifier/Classifier/ClassifierDataLoader.swift ================================================ // // ClassifierDataLoader.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import UIKit import RegexBuilder import AIProxy enum ClassifierDataLoaderError: Error { case couldNotCreateImageURL case couldNotIdentifyPlant } /// Sends requests to OpenAI to classify plant images, and returns the result asynchronously final actor ClassifierDataLoader { /// Uses OpenAI to fetch a description of the image passed as argument /// - Parameter image: The image to describe /// - Returns: An OpenAI description of the image func identify(fromImage image: CGImage) async throws -> (String, URL?) { guard let localURL = image.openAILocalURLEncoding() else { throw ClassifierDataLoaderError.couldNotCreateImageURL } let prompt = "What kind of plant is this and provide the wikipedia link for it, not in markdown" let response = try await AppConstants.openAIService.chatCompletionRequest(body: .init( model: "gpt-4o", messages: [ .user( content: .parts( [ .text(prompt), .imageURL(localURL, detail: .auto) ] ) ) ] )) let choices = response.choices guard let text = choices.first?.message.content else { throw ClassifierDataLoaderError.couldNotIdentifyPlant } return extractDescriptionAndWikipediaURL(text) } } // Assumes that the wikipedia link as at the end of input `text` private func extractDescriptionAndWikipediaURL(_ text: String) -> (String, URL?) { var mutableText = text let re = Regex { TryCapture { /https?:\/\/[^.]*\.wikipedia\.org[^\b]+$/ } transform: { URL(string: String($0)) } } var matchingURL: URL? = nil mutableText.replace(re, maxReplacements: 1) { matchingURL = $0.1; return "" } return (mutableText, matchingURL) } private extension CGImage { func openAILocalURLEncoding() -> URL? { if let data = UIImage(cgImage: self).jpegData(compressionQuality: 0.4) { let base64String = data.base64EncodedString() if let url = URL(string: "data:image/jpeg;base64,\(base64String)") { return url } } return nil } } ================================================ FILE: Demos/Classifier/Classifier/ClassifierManager.swift ================================================ // // ClassifierManager.swift // AIProxyBootstrap // // Created by Lou Zell // import SwiftUI @MainActor @Observable final class ClassifierManager { /// The description of a plant. Descriptions are generated by OpenAI private(set) var plantDescription: String? /// The image of a plant. This camera image is supplied by the user private(set) var image: CGImage? /// A wikipedia URL for the user to learn more about the identified plant. This URL is generated by OpenAI private(set) var wikipediaURL: URL? /// Loads data from OpenAI private let classifierDataLoader = ClassifierDataLoader() /// Identify a plant based on a passed in image /// - Parameter image: a camera frame that the user took of a plant in their surroundings func identify(_ image: CGImage) { self.image = image Task { let (description, wikipediaURL) = try await classifierDataLoader.identify(fromImage: image) self.plantDescription = description self.wikipediaURL = wikipediaURL } } /// Reset all previously classified state func reset() { self.plantDescription = nil self.image = nil self.wikipediaURL = nil } } ================================================ FILE: Demos/Classifier/Classifier/ClassifierView.swift ================================================ // // ClassifierView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI @MainActor struct ClassifierView: View { let cameraFrameManager: CameraFrameManager let classifierManager: ClassifierManager @Environment(\.dismiss) private var dismiss @State private var showingSheet = false var body: some View { ZStack { Rectangle() .fill(Color(.secondarySystemBackground)) .ignoresSafeArea() VStack { CameraView(image: cameraFrameManager.cameraFrameImage) Text("Take a photo to identify a plant") .fontWeight(.semibold) .font(.subheadline) .padding(16) .background(Color(.tertiarySystemBackground)) .cornerRadius(8) CameraControlsView(shutterButtonAction: { if let image = cameraFrameManager.cameraFrameImage { classifierManager.identify(image) showingSheet = true } }) .padding() .sheet(isPresented: $showingSheet){ SheetView(classifierManager: classifierManager) .presentationDetents([.medium, .large]) .presentationBackground(Color(.systemBackground)) } } } } } /// A sheet that slides up over the camera controls to display the results of plant classification private struct SheetView: View { let classifierManager: ClassifierManager @Environment(\.dismiss) var dismiss var body: some View { if let plantDescription = classifierManager.plantDescription, let plantImage = classifierManager.image { ZStack(alignment:.topTrailing){ Button(){ classifierManager.reset() dismiss() }label:{ Image(systemName: "xmark.circle.fill") .font(.system(size: 32)) .foregroundColor(.secondary) } VStack(spacing:16){ Image(uiImage: UIImage(cgImage: plantImage)) .resizable() .scaledToFill() .frame(width:80, height:80) .cornerRadius(8) .clipShape(Circle()) .overlay( Circle() .fill(.clear) .stroke(.mint, lineWidth:2) ) .shadow(radius: 8, x: 0, y: 4) VStack(spacing:8){ Text("Description") .font(.title) Text(plantDescription) .font(.subheadline) .foregroundColor(.secondary) } if let wikipediaURL = classifierManager.wikipediaURL { Button(){ UIApplication.shared.open(wikipediaURL) } label:{ Text("View on Wikipedia") .frame(maxWidth:.infinity) .fontWeight(.semibold) .foregroundColor(.white) } .buttonStyle(.borderedProminent) .controlSize(.large) } } .padding(.top, 16) } .padding(.horizontal, 16) .padding(.vertical, 24) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment:.topLeading) .transition(.opacity) } else { ProgressView() } } } #Preview { ClassifierView(cameraFrameManager: CameraFrameManager(), classifierManager: ClassifierManager()) } ================================================ FILE: Demos/Classifier/Classifier/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Classifier/Classifier.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0DDD23B42B4DE37500B2AE5C /* ClassifierApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23B32B4DE37500B2AE5C /* ClassifierApp.swift */; }; 0DDD23B82B4DE37500B2AE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23B72B4DE37500B2AE5C /* Assets.xcassets */; }; 0DDD23BB2B4DE37500B2AE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23BA2B4DE37500B2AE5C /* Preview Assets.xcassets */; }; 0DDD240D2B4DEE9700B2AE5C /* ClassifierManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24062B4DEE9700B2AE5C /* ClassifierManager.swift */; }; 0DDD240E2B4DEE9700B2AE5C /* CameraControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24072B4DEE9700B2AE5C /* CameraControlsView.swift */; }; 0DDD240F2B4DEE9700B2AE5C /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24082B4DEE9700B2AE5C /* CameraView.swift */; }; 0DDD24102B4DEE9700B2AE5C /* CameraFrameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24092B4DEE9700B2AE5C /* CameraFrameManager.swift */; }; 0DDD24112B4DEE9700B2AE5C /* ClassifierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD240A2B4DEE9700B2AE5C /* ClassifierView.swift */; }; 0DDD24122B4DEE9700B2AE5C /* ClassifierDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD240B2B4DEE9700B2AE5C /* ClassifierDataLoader.swift */; }; 0DDD24132B4DEE9700B2AE5C /* CameraDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD240C2B4DEE9700B2AE5C /* CameraDataLoader.swift */; }; 0DDD24162B4DEEB400B2AE5C /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24142B4DEEB400B2AE5C /* AppLogger.swift */; }; 0DDD24172B4DEEB400B2AE5C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24152B4DEEB400B2AE5C /* AppConstants.swift */; }; 2CE770982C9B55D700EC6C74 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CE770972C9B55D700EC6C74 /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0DDD23B02B4DE37500B2AE5C /* Classifier.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Classifier.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DDD23B32B4DE37500B2AE5C /* ClassifierApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassifierApp.swift; sourceTree = ""; }; 0DDD23B72B4DE37500B2AE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0DDD23BA2B4DE37500B2AE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0DDD24062B4DEE9700B2AE5C /* ClassifierManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassifierManager.swift; sourceTree = ""; }; 0DDD24072B4DEE9700B2AE5C /* CameraControlsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraControlsView.swift; sourceTree = ""; }; 0DDD24082B4DEE9700B2AE5C /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; 0DDD24092B4DEE9700B2AE5C /* CameraFrameManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraFrameManager.swift; sourceTree = ""; }; 0DDD240A2B4DEE9700B2AE5C /* ClassifierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassifierView.swift; sourceTree = ""; }; 0DDD240B2B4DEE9700B2AE5C /* ClassifierDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassifierDataLoader.swift; sourceTree = ""; }; 0DDD240C2B4DEE9700B2AE5C /* CameraDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraDataLoader.swift; sourceTree = ""; }; 0DDD24142B4DEEB400B2AE5C /* AppLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; }; 0DDD24152B4DEEB400B2AE5C /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0DDD23AD2B4DE37500B2AE5C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CE770982C9B55D700EC6C74 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0DDD23A72B4DE37500B2AE5C = { isa = PBXGroup; children = ( 0DDD23B22B4DE37500B2AE5C /* Classifier */, 0DDD23B12B4DE37500B2AE5C /* Products */, ); sourceTree = ""; }; 0DDD23B12B4DE37500B2AE5C /* Products */ = { isa = PBXGroup; children = ( 0DDD23B02B4DE37500B2AE5C /* Classifier.app */, ); name = Products; sourceTree = ""; }; 0DDD23B22B4DE37500B2AE5C /* Classifier */ = { isa = PBXGroup; children = ( 0DDD24152B4DEEB400B2AE5C /* AppConstants.swift */, 0DDD24142B4DEEB400B2AE5C /* AppLogger.swift */, 0DDD24072B4DEE9700B2AE5C /* CameraControlsView.swift */, 0DDD240C2B4DEE9700B2AE5C /* CameraDataLoader.swift */, 0DDD24092B4DEE9700B2AE5C /* CameraFrameManager.swift */, 0DDD24082B4DEE9700B2AE5C /* CameraView.swift */, 0DDD23B32B4DE37500B2AE5C /* ClassifierApp.swift */, 0DDD240B2B4DEE9700B2AE5C /* ClassifierDataLoader.swift */, 0DDD24062B4DEE9700B2AE5C /* ClassifierManager.swift */, 0DDD240A2B4DEE9700B2AE5C /* ClassifierView.swift */, 0DDD23B72B4DE37500B2AE5C /* Assets.xcassets */, 0DDD23B92B4DE37500B2AE5C /* Preview Content */, ); path = Classifier; sourceTree = ""; }; 0DDD23B92B4DE37500B2AE5C /* Preview Content */ = { isa = PBXGroup; children = ( 0DDD23BA2B4DE37500B2AE5C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0DDD23AF2B4DE37500B2AE5C /* Classifier */ = { isa = PBXNativeTarget; buildConfigurationList = 0DDD23BE2B4DE37500B2AE5C /* Build configuration list for PBXNativeTarget "Classifier" */; buildPhases = ( 0DDD23AC2B4DE37500B2AE5C /* Sources */, 0DDD23AD2B4DE37500B2AE5C /* Frameworks */, 0DDD23AE2B4DE37500B2AE5C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Classifier; packageProductDependencies = ( 2CE770972C9B55D700EC6C74 /* AIProxy */, ); productName = Classifier; productReference = 0DDD23B02B4DE37500B2AE5C /* Classifier.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0DDD23A82B4DE37500B2AE5C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1510; TargetAttributes = { 0DDD23AF2B4DE37500B2AE5C = { CreatedOnToolsVersion = 15.1; }; }; }; buildConfigurationList = 0DDD23AB2B4DE37500B2AE5C /* Build configuration list for PBXProject "Classifier" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0DDD23A72B4DE37500B2AE5C; packageReferences = ( 2CE770962C9B55D700EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 0DDD23B12B4DE37500B2AE5C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0DDD23AF2B4DE37500B2AE5C /* Classifier */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0DDD23AE2B4DE37500B2AE5C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD23BB2B4DE37500B2AE5C /* Preview Assets.xcassets in Resources */, 0DDD23B82B4DE37500B2AE5C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0DDD23AC2B4DE37500B2AE5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD24112B4DEE9700B2AE5C /* ClassifierView.swift in Sources */, 0DDD23B42B4DE37500B2AE5C /* ClassifierApp.swift in Sources */, 0DDD24102B4DEE9700B2AE5C /* CameraFrameManager.swift in Sources */, 0DDD240D2B4DEE9700B2AE5C /* ClassifierManager.swift in Sources */, 0DDD24132B4DEE9700B2AE5C /* CameraDataLoader.swift in Sources */, 0DDD24122B4DEE9700B2AE5C /* ClassifierDataLoader.swift in Sources */, 0DDD24162B4DEEB400B2AE5C /* AppLogger.swift in Sources */, 0DDD240E2B4DEE9700B2AE5C /* CameraControlsView.swift in Sources */, 0DDD240F2B4DEE9700B2AE5C /* CameraView.swift in Sources */, 0DDD24172B4DEEB400B2AE5C /* AppConstants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0DDD23BC2B4DE37500B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0DDD23BD2B4DE37500B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 0DDD23BF2B4DE37500B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Classifier/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Access to the camera is required to take a photo of your plant"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.classifier; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0DDD23C02B4DE37500B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Classifier/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Access to the camera is required to take a photo of your plant"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.classifier; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0DDD23AB2B4DE37500B2AE5C /* Build configuration list for PBXProject "Classifier" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23BC2B4DE37500B2AE5C /* Debug */, 0DDD23BD2B4DE37500B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0DDD23BE2B4DE37500B2AE5C /* Build configuration list for PBXNativeTarget "Classifier" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23BF2B4DE37500B2AE5C /* Debug */, 0DDD23C02B4DE37500B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CE770962C9B55D700EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CE770972C9B55D700EC6C74 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CE770962C9B55D700EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DDD23A82B4DE37500B2AE5C /* Project object */; } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/ContentView.swift ================================================ // // ContentView.swift // EmojiPuzzleMaker // // Created by Todd Hamilton on 8/1/24. // import AIProxy import SwiftUI import WebKit import Foundation #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let anthropicService = AIProxy.anthropicDirectService( unprotectedAPIKey: "your-anthropic-key" ) /* Uncomment for all other production use cases */ //let anthropicService = AIProxy.anthropicService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) struct ContentView: View { @State var emojis = "🏔️💦" @State var hint = "A fizzy green beverage" @State var prompt = "" @State var color = "blue" @State var json = "" let instructions = """ You are an AI tasked with generating a fun and interesting emoji puzzle based on a given word or phrase. Your goal is to create a puzzle using 2-3 emojis that represent the word or phrase, along with a hint to help solve the puzzle. Follow these guidelines: 1. Analyze the given word or phrase and think of relevant emojis that can represent it. 2. Choose 2-3 emojis that, when combined, cleverly represent the word or phrase. 3. Ensure the emojis are not too obvious, but also not impossibly difficult to decipher. 4. Create a short, helpful hint that gives a clue about the word or phrase without directly stating it. 5. Choose a SwiftUI color to match the theme of the puzzle. 6. Your returned message content should only be valid JSON. Do not introduce the JSON with "Here is my emoji puzzle response" or any other introduction. 7. In the JSON for `emojis` field, always put a space between unicode characters Generate a hint text that provides a subtle clue about the word or phrase without giving it away completely. The hint should be concise and engaging, encouraging the puzzle solver to think creatively. Your response should be in valid JSON format with the following structure: { "emojis": "unicode_emoji1 unicode_emoji2 ...", "hint": "Your hint text here", "color": "The SwiftUI color here", "solution": "The original word or phrase" } Example output for the prompt 'Starbucks': { "emojis": "⭐️ 💵", "hint": "Your daily fix", "color": "brown", "solution": "Starbucks" } Remember to use unicode representations for the emojis and ensure your output is in valid JSON format. Here is the word or phrase to base your emoji puzzle on: """ var body: some View { VStack{ Spacer() Text("Make an emoji puzzle!") .font(.title) .fontWeight(.bold) .fontDesign(.rounded) if emojis == "" { ProgressView() .controlSize(.large) .frame(maxHeight: 280) }else { VStack(spacing:12){ Text(emojis) .font(.system(size: 72, weight: .bold, design: .rounded)) .minimumScaleFactor(0.1) .frame(maxWidth:.infinity) .padding(.vertical, 72) Text("Hint: \(hint)") .font(.subheadline) .fontWeight(.medium) .fontDesign(.rounded) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.center) .padding(16) .foregroundStyle(.white) .background(.black.opacity(0.75)) .cornerRadius(30) } .frame(maxWidth:.infinity, maxHeight: 280) .transition(.scale) } Spacer() VStack(spacing:8){ TextField("ex. Mountain Dew", text:$prompt) .keyboardType(.alphabet) .disableAutocorrection(true) .padding() .overlay( RoundedRectangle(cornerRadius: 10) .stroke(.black.opacity(0.24), lineWidth: 2) ) .background(Color(.white)) .cornerRadius(10) Button{ emojis = "" hint = "" Task{ do{ let prompt = "\n\nHuman: \(instructions + prompt)\n\nAssistant:" let requestBody = AnthropicMessageRequestBody( maxTokens: 1024, messages: [ .init(content: [.text(prompt)], role: .user) ], model: "claude-3-5-sonnet-20240620" ) let response = try await anthropicService.messageRequest(body: requestBody) if let puzzle = getPuzzle(fromClaudeResponse: response) { updateUI(withPuzzle: puzzle) } } catch { print(error.localizedDescription) } } }label:{ Text("Generate Puzzle") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .tint(Color(string: color)) .fontWeight(.bold) } .padding(.horizontal,24) Spacer() } } func getPuzzle(fromClaudeResponse response: AnthropicMessageResponseBody) -> Puzzle? { // We expected Claude to return a single text content in the response: guard case .text(let messageContent) = response.content.first else { print("Claude didn't return a text response") return nil } print("Puzzle content:\n\n\(messageContent)") guard let jsonData = messageContent.data(using: .utf8) else { print("Could not convert Claude's message to jsonData") return nil } do { let decoder = JSONDecoder() return try decoder.decode(Puzzle.self, from: jsonData) } catch { print("Error decoding puzzle JSON: \(error)") return nil } } func updateUI(withPuzzle puzzle: Puzzle) { withAnimation(.snappy){ emojis = puzzle.emojis hint = puzzle.hint color = puzzle.color } } } // Define the struct that matches the JSON structure struct Puzzle: Codable { let emojis: String let hint: String let color: String let solution: String } extension Color { init?(string: String) { switch string.lowercased() { case "red": self = .red case "orange": self = .orange case "yellow": self = .yellow case "green": self = .green case "blue": self = .blue case "purple": self = .purple case "pink": self = .pink case "black": self = .black case "white": self = .white case "gray": self = .gray case "teal": self = .teal default: return nil } } } #Preview { ContentView() } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/EmojiPuzzleMakerApp.swift ================================================ // // EmojiPuzzleMakerApp.swift // EmojiPuzzleMaker // // Created by Todd Hamilton on 8/1/24. // import SwiftUI @main struct EmojiPuzzleMakerApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/EmojiPuzzleMaker/EmojiPuzzleMaker.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 2C734B722C5C0415005EF970 /* EmojiPuzzleMakerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C734B712C5C0415005EF970 /* EmojiPuzzleMakerApp.swift */; }; 2C734B742C5C0415005EF970 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C734B732C5C0415005EF970 /* ContentView.swift */; }; 2C734B762C5C0417005EF970 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C734B752C5C0417005EF970 /* Assets.xcassets */; }; 2C734B792C5C0417005EF970 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C734B782C5C0417005EF970 /* Preview Assets.xcassets */; }; 2C734B812C5C0445005EF970 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C734B802C5C0445005EF970 /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C734B6E2C5C0415005EF970 /* EmojiPuzzleMaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EmojiPuzzleMaker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2C734B712C5C0415005EF970 /* EmojiPuzzleMakerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPuzzleMakerApp.swift; sourceTree = ""; }; 2C734B732C5C0415005EF970 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2C734B752C5C0417005EF970 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2C734B782C5C0417005EF970 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 2C734B6B2C5C0415005EF970 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C734B812C5C0445005EF970 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C734B652C5C0415005EF970 = { isa = PBXGroup; children = ( 2C734B702C5C0415005EF970 /* EmojiPuzzleMaker */, 2C734B6F2C5C0415005EF970 /* Products */, ); sourceTree = ""; }; 2C734B6F2C5C0415005EF970 /* Products */ = { isa = PBXGroup; children = ( 2C734B6E2C5C0415005EF970 /* EmojiPuzzleMaker.app */, ); name = Products; sourceTree = ""; }; 2C734B702C5C0415005EF970 /* EmojiPuzzleMaker */ = { isa = PBXGroup; children = ( 2C734B712C5C0415005EF970 /* EmojiPuzzleMakerApp.swift */, 2C734B732C5C0415005EF970 /* ContentView.swift */, 2C734B752C5C0417005EF970 /* Assets.xcassets */, 2C734B772C5C0417005EF970 /* Preview Content */, ); path = EmojiPuzzleMaker; sourceTree = ""; }; 2C734B772C5C0417005EF970 /* Preview Content */ = { isa = PBXGroup; children = ( 2C734B782C5C0417005EF970 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C734B6D2C5C0415005EF970 /* EmojiPuzzleMaker */ = { isa = PBXNativeTarget; buildConfigurationList = 2C734B7C2C5C0417005EF970 /* Build configuration list for PBXNativeTarget "EmojiPuzzleMaker" */; buildPhases = ( 2C734B6A2C5C0415005EF970 /* Sources */, 2C734B6B2C5C0415005EF970 /* Frameworks */, 2C734B6C2C5C0415005EF970 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = EmojiPuzzleMaker; packageProductDependencies = ( 2C734B802C5C0445005EF970 /* AIProxy */, ); productName = EmojiPuzzleMaker; productReference = 2C734B6E2C5C0415005EF970 /* EmojiPuzzleMaker.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C734B662C5C0415005EF970 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; TargetAttributes = { 2C734B6D2C5C0415005EF970 = { CreatedOnToolsVersion = 15.4; }; }; }; buildConfigurationList = 2C734B692C5C0415005EF970 /* Build configuration list for PBXProject "EmojiPuzzleMaker" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C734B652C5C0415005EF970; packageReferences = ( 2C734B7F2C5C0445005EF970 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 2C734B6F2C5C0415005EF970 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C734B6D2C5C0415005EF970 /* EmojiPuzzleMaker */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C734B6C2C5C0415005EF970 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 2C734B792C5C0417005EF970 /* Preview Assets.xcassets in Resources */, 2C734B762C5C0417005EF970 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C734B6A2C5C0415005EF970 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 2C734B742C5C0415005EF970 /* ContentView.swift in Sources */, 2C734B722C5C0415005EF970 /* EmojiPuzzleMakerApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C734B7A2C5C0417005EF970 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C734B7B2C5C0417005EF970 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.5; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C734B7D2C5C0417005EF970 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"EmojiPuzzleMaker/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.EmojiPuzzleMaker; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C734B7E2C5C0417005EF970 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"EmojiPuzzleMaker/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.EmojiPuzzleMaker; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C734B692C5C0415005EF970 /* Build configuration list for PBXProject "EmojiPuzzleMaker" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C734B7A2C5C0417005EF970 /* Debug */, 2C734B7B2C5C0417005EF970 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C734B7C2C5C0417005EF970 /* Build configuration list for PBXNativeTarget "EmojiPuzzleMaker" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C734B7D2C5C0417005EF970 /* Debug */, 2C734B7E2C5C0417005EF970 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C734B7F2C5C0445005EF970 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C734B802C5C0445005EF970 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C734B7F2C5C0445005EF970 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C734B662C5C0415005EF970 /* Project object */; } ================================================ FILE: Demos/FilmFinder/FilmFinder/AppConstants.swift ================================================ // // AppConstants.swift // FilmFinder // // Created by Todd Hamilton on 11/4/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") You will also need a read access token from TMDB: https://developer.themoviedb.org/docs/getting-started """ ) /* Uncomment for BYOK use cases */ let groqService = AIProxy.groqDirectService( unprotectedAPIKey: "your-groq-key" ) /* Uncomment for all other production use cases */ //static let groqService = AIProxy.groqService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) let tmdb = "api-read-access-token-from-tmdb" ================================================ FILE: Demos/FilmFinder/FilmFinder/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/FilmFinder/FilmFinder/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "icon.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "tinted" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/FilmFinder/FilmFinder/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/FilmFinder/FilmFinder/ContentView.swift ================================================ // // ContentView.swift // FilmFinder // // Created by Todd Hamilton on 10/28/24. // import Foundation import SwiftUI import AIProxy import UIKit struct ContentView: View { @State private var isLoading = false @State private var showingDetails = false @State var counter: Int = 0 @State var origin: CGPoint = CGPoint(x: 0.5, y: 0.5) @State var movieID: String = "335984" @State var movieTitle: String = "Inside Out 2" @State var movieReleaseDate: String = "10/06/2017 (US)" @State var moviePoster: String = "vpnVM9B6NMmQpWeZvzLvDESb2QY.jpg" @State var movieBackdrop: String = "p5ozvmdgsmbWe0H8Xk7Rc8SCwAB.jpg" @State var movieOverview: String = "Teenager Riley's mind headquarters is undergoing a sudden demolition to make room for something entirely unexpected: new Emotions! Joy, Sadness, Anger, Fear and Disgust, who’ve long been running a successful operation by all accounts, aren’t sure how to feel when Anxiety shows up. And it looks like she’s not alone." @State var previousSuggestions:[String] = [] @State var genreLabel: String = "" let jsonExample:String = """ { "title": "the title of the movie" } """ var body: some View { ZStack { // Background Color.clear .edgesIgnoringSafeArea(.all) .background( AsyncImage( url: URL(string: "https://image.tmdb.org/t/p/w500/\(moviePoster)"), content: { image in image.resizable() .ignoresSafeArea() .blur(radius: 50) .modifier(RippleEffect(at: origin, trigger: counter)) .overlay(Color.black.opacity(0.5)) }, placeholder: { Color.clear } ) ) VStack(spacing:0){ AsyncImage( url: URL(string: "https://image.tmdb.org/t/p/w500/\(moviePoster)"), content: { image in image.resizable() .aspectRatio(contentMode: .fill) .frame(width: 192, height: 296) .cornerRadius(8) .shadow(radius: 8, x: 0, y: 4) .modifier(RippleEffect(at: origin, trigger: counter)) .onTapGesture { showingDetails.toggle() } }, placeholder: { ProgressView() .frame(width: 192, height: 296) } ) Button{ showingDetails.toggle() }label:{ if isLoading { ProgressView() .controlSize(.small) .padding(.horizontal, 12) } else { Text("\(movieTitle)") .font(.caption) .fontDesign(.monospaced) .lineLimit(1) .truncationMode(.tail) .padding(.horizontal, 12) } } .frame(maxHeight:34) .background( Capsule() .fill(.thickMaterial) .stroke(.white.opacity(0.14)) ) .foregroundStyle(.white) .padding() .sheet(isPresented: $showingDetails) { MovieDetailsView( movieID: $movieID, movieBackdrop: $movieBackdrop, movieTitle: $movieTitle, movieReleaseDate: $movieReleaseDate, movieOverview: $movieOverview, genreLabel: $genreLabel ) .presentationDetents([.large, .large]) .presentationDragIndicator(.visible) } GenreSelectorView(getMovieRecFromGroq: { Task{ await getMovieRecFromGroq() } }, genreLabel: $genreLabel) } } .preferredColorScheme(.dark) } // Get the movie recommendation from Groq based on the selected genre func getMovieRecFromGroq() async { isLoading = true defer { isLoading = false } do { let response = try await groqService.chatCompletionRequest(body: .init( messages: [ .system(content: "Recommend a \(genreLabel) movie to watch. Respond with a single movie title and a description for why it was chosen only in json. Don't recommend any of the previous movies: \(previousSuggestions). Do not include any other content in the repsonse. Use this example json as a template: \(jsonExample). Make sure to recommend a wide variety of movies.") ], model: "llama3-8b-8192", responseFormat: .jsonObject )) let movie = response.choices.first?.message.content ?? "" await parseRecommendationFromGroq(movieData: movie) } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print(error.localizedDescription) } } // Parse the response from Groq, set variables, the fetch data from TMDB func parseRecommendationFromGroq(movieData: String) async { let jsonData = movieData.data(using: .utf8)! // Decode JSON data if let movie = try? JSONDecoder().decode(Recommendation.self, from: jsonData) { movieTitle = movie.title previousSuggestions.append(movieTitle) await fetchMovieDataFromTMDB() } } // Fetch data from TMDB func fetchMovieDataFromTMDB() async { let url = URL(string: "https://api.themoviedb.org/3/search/movie")! var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! let queryItems: [URLQueryItem] = [ URLQueryItem(name: "query", value: movieTitle), URLQueryItem(name: "include_adult", value: "false"), URLQueryItem(name: "language", value: "en-US"), URLQueryItem(name: "page", value: "1"), ] components.queryItems = components.queryItems.map { $0 + queryItems } ?? queryItems var request = URLRequest(url: components.url!) request.httpMethod = "GET" request.timeoutInterval = 10 request.allHTTPHeaderFields = [ "accept": "application/json", "Authorization": "Bearer \(tmdb)" ] do { let (data, _) = try await URLSession.shared.data(for: request) extractMovieDataFromTMDBResponse(tmdbResponse: String(decoding: data, as: UTF8.self)) } catch { print(error.localizedDescription) } } // Parse the response from TMDB and store in state variables func extractMovieDataFromTMDBResponse(tmdbResponse: String) { // Convert JSON string to Data if let jsonData = tmdbResponse.data(using: .utf8) { do { // Decode JSON to MovieResponse object let movieResponse = try JSONDecoder().decode(MovieResponse.self, from: jsonData) // Access parsed data if let firstMovie = movieResponse.results.first { movieID = String(firstMovie.id) movieReleaseDate = firstMovie.releaseDate movieOverview = firstMovie.overview moviePoster = firstMovie.posterPath ?? "" movieBackdrop = firstMovie.backdropPath ?? "" counter += 1 } } catch { print("Failed to decode JSON:", error) } } } } #Preview { ContentView() } ================================================ FILE: Demos/FilmFinder/FilmFinder/FilmFinderApp.swift ================================================ // // FilmFinderApp.swift // FilmFinder // // Created by Todd Hamilton on 10/30/24. // import SwiftUI import TipKit @main struct FilmFinderApp: App { var body: some Scene { WindowGroup { ContentView() } } init() { /// Load and configure the state of all the tips of the app try? Tips.configure() } } ================================================ FILE: Demos/FilmFinder/FilmFinder/GenreSelectorView.swift ================================================ // // GenreSelectorView.swift // FilmFinder // // Created by Todd Hamilton on 11/3/24. // import SwiftUI import TipKit struct GenreSelectorView: View { private let getStartedTip = GetStartedTip() private let modelSize: CGFloat = UIScreen.main.bounds.width - 40 private let gridSize = 40 // Adjust for more dots if desired private let dotSize: CGFloat = 2 private let spacing: CGFloat = 20 // Increase spacing between dots private let thresholdDistance: CGFloat = 75 // Max distance for attraction let getMovieRecFromGroq: () -> Void @State private var isDragging = false @Binding var genreLabel: String @State var genreScores: [String: Double] = [ "Action": 0.0, "Comedy": 0.0, "Drama": 0.0, "Horror": 0.0, "Sci-Fi": 0.0, "Fantasy": 0.0, "Romance": 0.0, "Documentary": 0.0 ] // Define genre centers (normalized) @State var genreCenters: [String: CGPoint] = [ "Drama": CGPoint(x: 1.000, y: 0.500), "Horror": CGPoint(x: 0.853, y: 0.853), "Sci-Fi": CGPoint(x: 0.500, y: 1.000), "Fantasy": CGPoint(x: 0.147, y: 0.853), "Romance": CGPoint(x: 0.000, y: 0.500), "Documentary": CGPoint(x: 0.147, y: 0.147), "Action": CGPoint(x: 0.500, y: 0.000), "Comedy": CGPoint(x: 0.853, y: 0.147) ] @State var knobPosition: CGPoint = CGPoint(x: (UIScreen.main.bounds.width - 40) / 2, y: (UIScreen.main.bounds.width - 40) / 2) var body: some View { ZStack { // Drag area background RoundedRectangle(cornerRadius: 25) .fill(.thinMaterial) .frame(width: modelSize, height: modelSize) .overlay( ZStack{ RoundedRectangle(cornerRadius: 25) .stroke(.white.opacity(0.24)) } ) .shadow(radius: 8, x: 0, y: 4) // Dots GeometryReader { geo in let rows = Int(modelSize / spacing) let cols = Int(modelSize / spacing) ZStack { ForEach(0.. CGPoint { CGPoint(x: CGFloat(col) * spacing, y: CGFloat(row) * spacing) } private func dotPosition(row: Int, col: Int) -> CGPoint { guard isDragging else { // Check if dragging is active return initialDotPosition(row: row, col: col) } let fingerPosition = knobPosition let dotPos = initialDotPosition(row: row, col: col) let distance = hypot(dotPos.x - fingerPosition.x + 10, dotPos.y - fingerPosition.y + 10) if distance < thresholdDistance { // Calculate offset based on distance to create attraction let offsetFactor = (thresholdDistance - distance) / thresholdDistance let direction = CGPoint(x: (fingerPosition.x - dotPos.x) * offsetFactor, y: (fingerPosition.y - dotPos.y) * offsetFactor) return CGPoint(x: dotPos.x + direction.x, y: dotPos.y + direction.y) } else { // Outside of threshold, snap back to original position return dotPos } } private func dotOpacity(row: Int, col: Int) -> Double { guard isDragging else { // Only change opacity if dragging return 0.1 } let fingerPosition = knobPosition let dotPos = initialDotPosition(row: row, col: col) let distance = hypot(dotPos.x - fingerPosition.x + 10, dotPos.y - fingerPosition.y + 10) // Calculate opacity based on proximity; closer dots are more opaque let opacityFactor = max(0.1, min(1.0, (thresholdDistance - distance) / thresholdDistance)) return opacityFactor } // Determine the closest genre based on knob position private func calculateGenre() { // Normalize knob position let normalizedKnobPosition = CGPoint(x: knobPosition.x / modelSize, y: knobPosition.y / modelSize) // Find the genre with the minimum distance to the knob position var closestGenre: String = "Neutral" var minDistance: CGFloat = .greatestFiniteMagnitude for (genre, center) in genreCenters { let distance = hypot(normalizedKnobPosition.x - center.x, normalizedKnobPosition.y - center.y) if distance < minDistance { minDistance = distance closestGenre = genre } } // Update the genre label with the closest genre withAnimation { genreLabel = closestGenre } } // Calculate proximity scores for each emotion based on knob position private func calculateGenreProximityScores() { // Calculate proximity scores let normalizedKnobPosition = CGPoint(x: knobPosition.x / modelSize, y: knobPosition.y / modelSize) var scores: [String: Double] = [:] for (genre, center) in genreCenters { // Calculate inverse distance as proximity score (higher = closer) let distance = hypot(normalizedKnobPosition.x - center.x, normalizedKnobPosition.y - center.y) scores[genre] = max(0, 1 - distance) // Scale to 0-1 range } withAnimation(.bouncy){ genreScores = scores } } } #Preview { GenreSelectorView(getMovieRecFromGroq: {}, genreLabel: .constant("")) } ================================================ FILE: Demos/FilmFinder/FilmFinder/GetStartedTip.swift ================================================ // // GetStartedTip.swift // FilmFinder // // Created by Todd Hamilton on 10/31/24. // import SwiftUI import TipKit // Tooltip for first time users struct GetStartedTip: Tip { var title: Text { Text("Get movie recommendations") } var message: Text? { Text("Drag the circle to choose a genre.") } } ================================================ FILE: Demos/FilmFinder/FilmFinder/Info.plist ================================================ ================================================ FILE: Demos/FilmFinder/FilmFinder/Movie.swift ================================================ // // Movie.swift // FilmFinder // // Created by Todd Hamilton on 10/29/24. // import Foundation struct Recommendation: Codable { let title: String } // Define the structs to match the JSON structure struct MovieResponse: Codable { let page: Int let results: [Movie] let totalPages: Int let totalResults: Int // Map JSON keys to Swift property names if they differ enum CodingKeys: String, CodingKey { case page, results case totalPages = "total_pages" case totalResults = "total_results" } } struct Movie: Codable { let adult: Bool let backdropPath: String? let genreIds: [Int] let id: Int let originalLanguage: String let originalTitle: String let overview: String let popularity: Double let posterPath: String? let releaseDate: String let title: String let video: Bool let voteAverage: Double let voteCount: Int enum CodingKeys: String, CodingKey { case adult case backdropPath = "backdrop_path" case genreIds = "genre_ids" case id case originalLanguage = "original_language" case originalTitle = "original_title" case overview, popularity case posterPath = "poster_path" case releaseDate = "release_date" case title, video case voteAverage = "vote_average" case voteCount = "vote_count" } } ================================================ FILE: Demos/FilmFinder/FilmFinder/MovieDetailsView.swift ================================================ // // MovieDetailsView.swift // MovieMoods // // Created by Todd Hamilton on 10/29/24. // import SwiftUI struct MovieDetailsView: View { @Binding var movieID: String @Binding var movieBackdrop: String @Binding var movieTitle: String @Binding var movieReleaseDate: String @Binding var movieOverview: String @Binding var genreLabel: String @State var showPoster:Bool = false @State var showTitle:Bool = false @State var showRelease:Bool = false @State var showOverview:Bool = false @State var showCTA:Bool = false var body: some View { VStack(spacing:24){ AsyncImage( url: URL(string: "https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/\(movieBackdrop)"), content: { image in image .resizable() .scaledToFit() .frame(maxWidth:.infinity) .clipped() .cornerRadius(8) }, placeholder: { Color.clear } ) .opacity(showPoster ? 1 : 0) VStack(spacing:24){ Text(movieTitle) .font(.title2) .fontWeight(.bold) .frame(maxWidth:.infinity, alignment: .leading) .opacity(showTitle ? 1 : 0) VStack(spacing:8){ Text("Release date") .frame(maxWidth:.infinity, alignment: .leading) .foregroundColor(.secondary) .textCase(.uppercase) Text("\(movieReleaseDate)") .frame(maxWidth:.infinity, alignment: .leading) } .font(.caption) .opacity(showRelease ? 1 : 0) VStack(spacing:8){ Text("Overview") .frame(maxWidth:.infinity, alignment: .leading) .foregroundColor(.secondary) .textCase(.uppercase) Text(movieOverview) .frame(maxWidth:.infinity, alignment: .leading) } .font(.caption) .opacity(showOverview ? 1 : 0) } .fontDesign(.monospaced) Link(destination: URL(string: "https://www.themoviedb.org/movie/\(movieID)")!){ Text("View on TMDB") .foregroundColor(.black) .padding(6) .frame(maxWidth:.infinity) } .buttonStyle(.borderedProminent) .tint(.white) .fontDesign(.monospaced) .fontWeight(.bold) .opacity(showCTA ? 1 : 0) Spacer() } .padding(16) .padding(.top, 12) .preferredColorScheme(.dark) .onAppear(){ withAnimation(.easeInOut.delay(0.2)){ showPoster.toggle() } withAnimation(.easeInOut.delay(0.25)){ showTitle.toggle() } withAnimation(.easeInOut.delay(0.3)){ showRelease.toggle() } withAnimation(.easeInOut.delay(0.35)){ showOverview.toggle() } withAnimation(.easeInOut.delay(0.4)){ showCTA.toggle() } } } } #Preview { MovieDetailsView( movieID: .constant("23232"), movieBackdrop: .constant("p5ozvmdgsmbWe0H8Xk7Rc8SCwAB.jpg"), movieTitle: .constant("Inside Out 2"), movieReleaseDate: .constant("06/14/2024 (US)"), movieOverview: .constant("Teenager Riley's mind headquarters is undergoing a sudden demolition to make room for something entirely unexpected: new Emotions! Joy, Sadness, Anger, Fear and Disgust, who’ve long been running a successful operation by all accounts, aren’t sure how to feel when Anxiety shows up. And it looks like she’s not alone."), genreLabel: .constant("Comedy") ) } ================================================ FILE: Demos/FilmFinder/FilmFinder/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/FilmFinder/FilmFinder/Ripple.metal ================================================ // // Ripple.metal // FilmFinder // // Created by Todd Hamilton on 6/21/24. // // Insert #include #include using namespace metal; [[ stitchable ]] half4 Ripple( float2 position, SwiftUI::Layer layer, float2 origin, float time, float amplitude, float frequency, float decay, float speed ) { // The distance of the current pixel position from `origin`. float distance = length(position - origin); // The amount of time it takes for the ripple to arrive at the current pixel position. float delay = distance / speed; // Adjust for delay, clamp to 0. time -= delay; time = max(0.0, time); // The ripple is a sine wave that Metal scales by an exponential decay // function. float rippleAmount = amplitude * sin(frequency * time) * exp(-decay * time); // A vector of length `amplitude` that points away from position. float2 n = normalize(position - origin); // Scale `n` by the ripple amount at the current pixel position and add it // to the current pixel position. // // This new position moves toward or away from `origin` based on the // sign and magnitude of `rippleAmount`. float2 newPosition = position + rippleAmount * n; // Sample the layer at the new position. half4 color = layer.sample(newPosition); // Lighten or darken the color based on the ripple amount and its alpha // component. color.rgb += 0.3 * (rippleAmount / amplitude) * color.a; return color; } ================================================ FILE: Demos/FilmFinder/FilmFinder/Ripple.swift ================================================ // // Ripple.swift // FilmFinder // // Created by Todd Hamilton on 6/21/24. // import SwiftUI struct PushEffect: ViewModifier { var trigger: T func body(content: Content) -> some View { content.keyframeAnimator( initialValue: 1.0, trigger: trigger ) { view, value in view.visualEffect { view, _ in view.scaleEffect(value) } } keyframes: { _ in SpringKeyframe(0.95, duration: 0.2, spring: .snappy) SpringKeyframe(1.0, duration: 0.2, spring: .bouncy) } } } /// A modifer that performs a ripple effect to its content whenever its /// trigger value changes. struct RippleEffect: ViewModifier { var origin: CGPoint var trigger: T init(at origin: CGPoint, trigger: T) { self.origin = origin self.trigger = trigger } func body(content: Content) -> some View { let origin = origin let duration = duration content.keyframeAnimator( initialValue: 0, trigger: trigger ) { view, elapsedTime in view.modifier(RippleModifier( origin: origin, elapsedTime: elapsedTime, duration: duration )) } keyframes: { _ in MoveKeyframe(0) LinearKeyframe(duration, duration: duration) } } var duration: TimeInterval { 3 } } /// A modifier that applies a ripple effect to its content. struct RippleModifier: ViewModifier { var origin: CGPoint var elapsedTime: TimeInterval var duration: TimeInterval var amplitude: Double = 12 var frequency: Double = 15 var decay: Double = 8 var speed: Double = 1200 func body(content: Content) -> some View { let shader = ShaderLibrary.Ripple( .float2(origin), .float(elapsedTime), // Parameters .float(amplitude), .float(frequency), .float(decay), .float(speed) ) let maxSampleOffset = maxSampleOffset let elapsedTime = elapsedTime let duration = duration content.visualEffect { view, _ in view.layerEffect( shader, maxSampleOffset: maxSampleOffset, isEnabled: 0 < elapsedTime && elapsedTime < duration ) } } var maxSampleOffset: CGSize { CGSize(width: amplitude, height: amplitude) } } extension View { func onPressingChanged(_ action: @escaping (CGPoint?) -> Void) -> some View { modifier(SpatialPressingGestureModifier(action: action)) } } struct SpatialPressingGestureModifier: ViewModifier { var onPressingChanged: (CGPoint?) -> Void @State var currentLocation: CGPoint? init(action: @escaping (CGPoint?) -> Void) { self.onPressingChanged = action } func body(content: Content) -> some View { let gesture = SpatialPressingGesture(location: $currentLocation) content .gesture(gesture) .onChange(of: currentLocation, initial: false) { _, location in onPressingChanged(location) } } } struct SpatialPressingGesture: UIGestureRecognizerRepresentable { final class Coordinator: NSObject, UIGestureRecognizerDelegate { @objc func gestureRecognizer( _ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer ) -> Bool { true } } @Binding var location: CGPoint? func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator { Coordinator() } func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer { let recognizer = UILongPressGestureRecognizer() recognizer.minimumPressDuration = 0 recognizer.delegate = context.coordinator return recognizer } func handleUIGestureRecognizerAction( _ recognizer: UIGestureRecognizerType, context: Context) { switch recognizer.state { case .began: location = context.converter.localLocation case .ended, .cancelled, .failed: location = nil default: break } } } ================================================ FILE: Demos/FilmFinder/FilmFinder.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 2CB7E6132CD2AC05004E3AFD /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CB7E6122CD2AC05004E3AFD /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2CB7E5F82CD2ABAA004E3AFD /* FilmFinder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FilmFinder.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 2C658F572CD8797500300EDE /* Exceptions for "FilmFinder" folder in "FilmFinder" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, ); target = 2CB7E5F72CD2ABAA004E3AFD /* FilmFinder */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ 2CB7E5FA2CD2ABAA004E3AFD /* FilmFinder */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 2C658F572CD8797500300EDE /* Exceptions for "FilmFinder" folder in "FilmFinder" target */, ); path = FilmFinder; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ 2CB7E5F52CD2ABAA004E3AFD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CB7E6132CD2AC05004E3AFD /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2CB7E5EF2CD2ABAA004E3AFD = { isa = PBXGroup; children = ( 2CB7E5FA2CD2ABAA004E3AFD /* FilmFinder */, 2CB7E5F92CD2ABAA004E3AFD /* Products */, ); sourceTree = ""; }; 2CB7E5F92CD2ABAA004E3AFD /* Products */ = { isa = PBXGroup; children = ( 2CB7E5F82CD2ABAA004E3AFD /* FilmFinder.app */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2CB7E5F72CD2ABAA004E3AFD /* FilmFinder */ = { isa = PBXNativeTarget; buildConfigurationList = 2CB7E6062CD2ABAC004E3AFD /* Build configuration list for PBXNativeTarget "FilmFinder" */; buildPhases = ( 2CB7E5F42CD2ABAA004E3AFD /* Sources */, 2CB7E5F52CD2ABAA004E3AFD /* Frameworks */, 2CB7E5F62CD2ABAA004E3AFD /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( 2CB7E5FA2CD2ABAA004E3AFD /* FilmFinder */, ); name = FilmFinder; packageProductDependencies = ( 2CB7E6122CD2AC05004E3AFD /* AIProxy */, ); productName = FilmFinder; productReference = 2CB7E5F82CD2ABAA004E3AFD /* FilmFinder.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2CB7E5F02CD2ABAA004E3AFD /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1610; LastUpgradeCheck = 1610; TargetAttributes = { 2CB7E5F72CD2ABAA004E3AFD = { CreatedOnToolsVersion = 16.1; }; }; }; buildConfigurationList = 2CB7E5F32CD2ABAA004E3AFD /* Build configuration list for PBXProject "FilmFinder" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2CB7E5EF2CD2ABAA004E3AFD; minimizedProjectReferenceProxies = 1; packageReferences = ( 2CB7E6112CD2AC05004E3AFD /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); preferredProjectObjectVersion = 77; productRefGroup = 2CB7E5F92CD2ABAA004E3AFD /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2CB7E5F72CD2ABAA004E3AFD /* FilmFinder */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2CB7E5F62CD2ABAA004E3AFD /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2CB7E5F42CD2ABAA004E3AFD /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2CB7E6042CD2ABAC004E3AFD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.1; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2CB7E6052CD2ABAC004E3AFD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.1; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2CB7E6072CD2ABAC004E3AFD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"FilmFinder/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FilmFinder/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Film Finder"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.HamiltonZell.FilmFinder; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; 2CB7E6082CD2ABAC004E3AFD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"FilmFinder/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FilmFinder/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Film Finder"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.HamiltonZell.FilmFinder; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2CB7E5F32CD2ABAA004E3AFD /* Build configuration list for PBXProject "FilmFinder" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CB7E6042CD2ABAC004E3AFD /* Debug */, 2CB7E6052CD2ABAC004E3AFD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2CB7E6062CD2ABAC004E3AFD /* Build configuration list for PBXNativeTarget "FilmFinder" */ = { isa = XCConfigurationList; buildConfigurations = ( 2CB7E6072CD2ABAC004E3AFD /* Debug */, 2CB7E6082CD2ABAC004E3AFD /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CB7E6112CD2AC05004E3AFD /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CB7E6122CD2AC05004E3AFD /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CB7E6112CD2AC05004E3AFD /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2CB7E5F02CD2ABAA004E3AFD /* Project object */; } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/AppConstants.swift ================================================ // // AppConstants.swift // PuLIDDemo // // Created by Todd Hamilton on 9/28/24. // import AIProxy #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ let replicateService = AIProxy.replicateDirectService( unprotectedAPIKey: "your-replicate-key" ) /* Uncomment for all other production use cases */ //let replicateService = AIProxy.replicateService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" //) ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "tinted" } ], "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Assets.xcassets/pulid.imageset/Contents.json ================================================ { "images" : [ { "filename" : "pulid.png", "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/ContentView.swift ================================================ // // ContentView.swift // PuLIDDemo // // Created by Todd Hamilton on 9/26/24. // import SwiftUI import AIProxy import PhotosUI struct ContentView: View { @State var isAnimating = false @State private var selectedPhoto: PhotosPickerItem? @State private var image: UIImage? = UIImage(named: "pulid") @State var prompt = "" @State private var isLoading = false @State var counter: Int = 0 @State var origin: CGPoint = .zero @FocusState var isFocused : Bool var body: some View { ZStack { if #available(iOS 18.0, *) { MeshGradient(width: 2, height: 2, points: [ [0, 0], [1, 0], [0, 1], [1, 1] ], colors: [.blue, .teal, .cyan, .blue]) .ignoresSafeArea() } else { // Fallback on earlier versions Color.purple } LinearGradient(gradient: Gradient(colors: [.white.opacity(0), .black]), startPoint: .top, endPoint: .bottom) .ignoresSafeArea() VStack{ ZStack(alignment: .bottom){ if let image = image { Image(uiImage: image) .resizable() .scaledToFill() .frame(height:400) .overlay( RoundedRectangle(cornerRadius: 16) .stroke( LinearGradient( gradient: Gradient(colors: [.white.opacity(0.75), .white.opacity(0.25), .white.opacity(0.25), .white.opacity(0.15), .white.opacity(0.15)]), startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 2 ) .blendMode(.overlay) ) .cornerRadius(16) .modifier(RippleEffect(at: origin, trigger: counter)) .shadow(color:.black.opacity(0.14),radius: 24) .shadow(radius: 25) PhotosPicker(selection: $selectedPhoto, matching: .images){ Image(systemName: "photo.circle.fill") } .foregroundStyle(.regularMaterial) .buttonBorderShape(.circle) .padding() .font(.largeTitle) .textCase(.uppercase) if isLoading{ ProgressView(){ Text("Generating") .foregroundStyle(.white) .font(.system(size: 12, weight:.semibold, design: .monospaced)) } .tint(.white) .frame(maxWidth: .infinity, maxHeight: 400) .background(.black.opacity(0.5)) .cornerRadius(16) } } } .padding() .task(id: selectedPhoto) { if let data = try? await selectedPhoto?.loadTransferable(type: Data.self), let uiImage = UIImage(data: data) { image = uiImage } } VStack(spacing:12){ Text("Type a prompt to generate a new image.") .font(.caption) .fontWeight(.medium) .textCase(.uppercase) .foregroundStyle(.white) .fontDesign(.monospaced) ZStack{ TextField("Prompt", text: $prompt) .focused($isFocused) .submitLabel(.done) .onSubmit { isFocused = false Task{ try await generateImage() } } } .padding(.vertical) .padding(.horizontal, 12) .fontDesign(.monospaced) .background(.white) .clipShape(RoundedRectangle(cornerRadius: 8)) .font(.system(size: 14)) .shadow(radius: 24) HStack{ Button("Generate"){ isFocused = false Task{ try await generateImage() } } .font(.system(size: 16, weight: .semibold, design: .monospaced)) .textCase(.uppercase) .controlSize(.large) .padding(8) .buttonStyle(.borderedProminent) .tint(.teal) .disabled(isLoading ? true : false) } } .padding(16) Spacer() } } .preferredColorScheme(.light) } private func generateImage() async throws { withAnimation(.default.delay(0.5)){ isLoading = true } defer { withAnimation(){ isLoading = false } } guard let imageURL = AIProxy.encodeImageAsURL(image: image!, compressionQuality: 0.8) else { print("Could not convert image to a local data URI") return } do { let input = ReplicateFluxPulidInputSchema( mainFaceImage: imageURL, prompt: prompt, numOutputs: 1, startStep: 4 ) let output = try await replicateService.createFluxPulidImage( input: input ) print("Done creating Flux-PuLID image: ", output) if let url = output.first?.absoluteString { image = await loadImage(from: url) } counter += 1 } catch AIProxyError.unsuccessfulRequest(let statusCode, let responseBody) { print("Received non-200 status code: \(statusCode) with response body: \(responseBody)") } catch { print("Could not create Flux-Pulid images: \(error.localizedDescription)") } } // Function to load an image from a URL private func loadImage(from url: String) async -> UIImage? { guard let url = URL(string: url) else { return nil } do { let (data, _) = try await URLSession.shared.data(from: url) return UIImage(data: data) } catch { print("Failed to load image from URL: \(error.localizedDescription)") return nil } } } #Preview { ContentView() } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Info.plist ================================================ ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/PuLIDDemoApp.swift ================================================ // // PuLIDDemoApp.swift // PuLIDDemo // // Created by Todd Hamilton on 9/26/24. // import SwiftUI @main struct PuLIDDemoApp: App { var body: some Scene { WindowGroup { ContentView() } } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Ripple.metal ================================================ // // Ripple.metal // AIColorPalette // // Created by Todd Hamilton on 6/21/24. // // Insert #include #include using namespace metal; [[ stitchable ]] half4 Ripple( float2 position, SwiftUI::Layer layer, float2 origin, float time, float amplitude, float frequency, float decay, float speed ) { // The distance of the current pixel position from `origin`. float distance = length(position - origin); // The amount of time it takes for the ripple to arrive at the current pixel position. float delay = distance / speed; // Adjust for delay, clamp to 0. time -= delay; time = max(0.0, time); // The ripple is a sine wave that Metal scales by an exponential decay // function. float rippleAmount = amplitude * sin(frequency * time) * exp(-decay * time); // A vector of length `amplitude` that points away from position. float2 n = normalize(position - origin); // Scale `n` by the ripple amount at the current pixel position and add it // to the current pixel position. // // This new position moves toward or away from `origin` based on the // sign and magnitude of `rippleAmount`. float2 newPosition = position + rippleAmount * n; // Sample the layer at the new position. half4 color = layer.sample(newPosition); // Lighten or darken the color based on the ripple amount and its alpha // component. color.rgb += 0.3 * (rippleAmount / amplitude) * color.a; return color; } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo/Ripple.swift ================================================ // // Ripple.swift // AIColorPalette // // Created by Todd Hamilton on 6/21/24. // import SwiftUI struct PushEffect: ViewModifier { var trigger: T func body(content: Content) -> some View { content.keyframeAnimator( initialValue: 1.0, trigger: trigger ) { view, value in view.visualEffect { view, _ in view.scaleEffect(value) } } keyframes: { _ in SpringKeyframe(0.95, duration: 0.2, spring: .snappy) SpringKeyframe(1.0, duration: 0.2, spring: .bouncy) } } } /// A modifer that performs a ripple effect to its content whenever its /// trigger value changes. struct RippleEffect: ViewModifier { var origin: CGPoint var trigger: T init(at origin: CGPoint, trigger: T) { self.origin = origin self.trigger = trigger } func body(content: Content) -> some View { let origin = origin let duration = duration content.keyframeAnimator( initialValue: 0, trigger: trigger ) { view, elapsedTime in view.modifier(RippleModifier( origin: origin, elapsedTime: elapsedTime, duration: duration )) } keyframes: { _ in MoveKeyframe(0) LinearKeyframe(duration, duration: duration) } } var duration: TimeInterval { 3 } } /// A modifier that applies a ripple effect to its content. struct RippleModifier: ViewModifier { var origin: CGPoint var elapsedTime: TimeInterval var duration: TimeInterval var amplitude: Double = 12 var frequency: Double = 15 var decay: Double = 8 var speed: Double = 1200 func body(content: Content) -> some View { let shader = ShaderLibrary.Ripple( .float2(origin), .float(elapsedTime), // Parameters .float(amplitude), .float(frequency), .float(decay), .float(speed) ) let maxSampleOffset = maxSampleOffset let elapsedTime = elapsedTime let duration = duration content.visualEffect { view, _ in view.layerEffect( shader, maxSampleOffset: maxSampleOffset, isEnabled: 0 < elapsedTime && elapsedTime < duration ) } } var maxSampleOffset: CGSize { CGSize(width: amplitude, height: amplitude) } } extension View { func onPressingChanged(_ action: @escaping (CGPoint?) -> Void) -> some View { modifier(SpatialPressingGestureModifier(action: action)) } } struct SpatialPressingGestureModifier: ViewModifier { var onPressingChanged: (CGPoint?) -> Void @State var currentLocation: CGPoint? init(action: @escaping (CGPoint?) -> Void) { self.onPressingChanged = action } func body(content: Content) -> some View { let gesture = SpatialPressingGesture(location: $currentLocation) content .gesture(gesture) .onChange(of: currentLocation, initial: false) { _, location in onPressingChanged(location) } } } struct SpatialPressingGesture: UIGestureRecognizerRepresentable { final class Coordinator: NSObject, UIGestureRecognizerDelegate { @objc func gestureRecognizer( _ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer ) -> Bool { true } } @Binding var location: CGPoint? func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator { Coordinator() } func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer { let recognizer = UILongPressGestureRecognizer() recognizer.minimumPressDuration = 0 recognizer.delegate = context.coordinator return recognizer } func handleUIGestureRecognizerAction( _ recognizer: UIGestureRecognizerType, context: Context) { switch recognizer.state { case .began: location = context.converter.localLocation case .ended, .cancelled, .failed: location = nil default: break } } } ================================================ FILE: Demos/PuLIDDemo/PuLIDDemo.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 2C4C32D22CA5A8EA001B3CDF /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2C4C32D12CA5A8EA001B3CDF /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2C4C32BF2CA5A8DD001B3CDF /* PuLIDDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PuLIDDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 2CEFF9DF2CA85CFB001959B4 /* Exceptions for "PuLIDDemo" folder in "PuLIDDemo" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( Info.plist, ); target = 2C4C32BE2CA5A8DD001B3CDF /* PuLIDDemo */; }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ 2C4C32C12CA5A8DD001B3CDF /* PuLIDDemo */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 2CEFF9DF2CA85CFB001959B4 /* Exceptions for "PuLIDDemo" folder in "PuLIDDemo" target */, ); path = PuLIDDemo; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ 2C4C32BC2CA5A8DD001B3CDF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2C4C32D22CA5A8EA001B3CDF /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2C4C32B62CA5A8DD001B3CDF = { isa = PBXGroup; children = ( 2C4C32C12CA5A8DD001B3CDF /* PuLIDDemo */, 2C4C32C02CA5A8DD001B3CDF /* Products */, ); sourceTree = ""; }; 2C4C32C02CA5A8DD001B3CDF /* Products */ = { isa = PBXGroup; children = ( 2C4C32BF2CA5A8DD001B3CDF /* PuLIDDemo.app */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 2C4C32BE2CA5A8DD001B3CDF /* PuLIDDemo */ = { isa = PBXNativeTarget; buildConfigurationList = 2C4C32CD2CA5A8DE001B3CDF /* Build configuration list for PBXNativeTarget "PuLIDDemo" */; buildPhases = ( 2C4C32BB2CA5A8DD001B3CDF /* Sources */, 2C4C32BC2CA5A8DD001B3CDF /* Frameworks */, 2C4C32BD2CA5A8DD001B3CDF /* Resources */, ); buildRules = ( ); dependencies = ( ); fileSystemSynchronizedGroups = ( 2C4C32C12CA5A8DD001B3CDF /* PuLIDDemo */, ); name = PuLIDDemo; packageProductDependencies = ( 2C4C32D12CA5A8EA001B3CDF /* AIProxy */, ); productName = PuLIDDemo; productReference = 2C4C32BF2CA5A8DD001B3CDF /* PuLIDDemo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 2C4C32B72CA5A8DD001B3CDF /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; TargetAttributes = { 2C4C32BE2CA5A8DD001B3CDF = { CreatedOnToolsVersion = 16.0; }; }; }; buildConfigurationList = 2C4C32BA2CA5A8DD001B3CDF /* Build configuration list for PBXProject "PuLIDDemo" */; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 2C4C32B62CA5A8DD001B3CDF; minimizedProjectReferenceProxies = 1; packageReferences = ( 2C4C32D02CA5A8EA001B3CDF /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); preferredProjectObjectVersion = 77; productRefGroup = 2C4C32C02CA5A8DD001B3CDF /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 2C4C32BE2CA5A8DD001B3CDF /* PuLIDDemo */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 2C4C32BD2CA5A8DD001B3CDF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 2C4C32BB2CA5A8DD001B3CDF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 2C4C32CB2CA5A8DE001B3CDF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2C4C32CC2CA5A8DE001B3CDF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 2C4C32CE2CA5A8DE001B3CDF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"PuLIDDemo/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PuLIDDemo/Info.plist; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "App needs permission to save photos."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.PuLIDDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 2C4C32CF2CA5A8DE001B3CDF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"PuLIDDemo/Preview Content\""; DEVELOPMENT_TEAM = 553LMX46SJ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PuLIDDemo/Info.plist; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "App needs permission to save photos."; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.toddham.PuLIDDemo; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 2C4C32BA2CA5A8DD001B3CDF /* Build configuration list for PBXProject "PuLIDDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C4C32CB2CA5A8DE001B3CDF /* Debug */, 2C4C32CC2CA5A8DE001B3CDF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 2C4C32CD2CA5A8DE001B3CDF /* Build configuration list for PBXNativeTarget "PuLIDDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( 2C4C32CE2CA5A8DE001B3CDF /* Debug */, 2C4C32CF2CA5A8DE001B3CDF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2C4C32D02CA5A8EA001B3CDF /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2C4C32D12CA5A8EA001B3CDF /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2C4C32D02CA5A8EA001B3CDF /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2C4C32B72CA5A8DD001B3CDF /* Project object */; } ================================================ FILE: Demos/Stickers/Stickers/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import AIProxy enum AppConstants { #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Stickers/Stickers/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapStickers") ================================================ FILE: Demos/Stickers/Stickers/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Stickers/Stickers/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "sticker.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Stickers/Stickers/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Stickers/Stickers/ButtonStyles.swift ================================================ // // ButtonStyles.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct ButtonStyles: View { var body: some View { ZStack{ Color(.systemGroupedBackground) .ignoresSafeArea() VStack(spacing:48){ Button{ /// do something }label:{ HStack(spacing:6){ Image(systemName: "play.circle.fill") .font(.system(size: 15, weight:.semibold, design: .rounded)) Text("10s") .font(.system(size: 11, weight: .regular, design: .monospaced)) .fontDesign(.monospaced) } } .buttonStyle(TranscriptionButtonStyle()) Button(){ /// do something } label:{ Label("Chunky Button", systemImage: "sparkles") } .buttonStyle(ChunkyButtonStyle(offsetSize: 10.0)) Button{ /// do something } label: { ZStack{ RoundedRectangle(cornerRadius: 45, style:.continuous) .fill(.red.gradient) .frame(width:85, height:85) Image(systemName: "waveform") .font(.title) .foregroundColor(.white) } } .buttonStyle(RecordButton()) Button(){ /// do something }label:{ Image(systemName: "arrow.forward") .font(.system(size: 17, weight: .bold)) } .buttonStyle(TranslateButton()) } .padding() } } } struct ChunkyButtonStyle: ButtonStyle { var offsetSize = 10.0 func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 24, weight: .bold, design: .rounded)) .foregroundColor(.black) .padding() .offset(y: configuration.isPressed ? offsetSize-2 : 0) .background( GeometryReader{ geo in RoundedRectangle(cornerRadius: 17) .strokeBorder(Color.black, lineWidth: 4) .background( RoundedRectangle(cornerRadius: 17) .fill(.white) ) .offset(y:offsetSize) .overlay( RoundedRectangle(cornerRadius: 17) .fill(.white) .strokeBorder(Color.black, lineWidth: 4) .background(RoundedRectangle(cornerRadius: 17).fill(Color.white)) .offset(y: configuration.isPressed ? offsetSize : 0) ) } ) } } struct RecordButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .frame(maxWidth:80, maxHeight: 80) .shadow(color:.black.opacity(0.28), radius: 10, y:8) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) .padding(.bottom, 40) } } struct TranslateButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.white) .frame(width:56, height:56) .background( Circle() .fill(.teal.gradient) ) .brightness(configuration.isPressed ? -0.1 : 0.0) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) } } struct TranscriptionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.secondary) .padding(.leading, 4) .padding(.trailing, 8) .padding(.vertical, 4) .background( Capsule().fill(.secondary.opacity(configuration.isPressed ? 0.28 : 0.14)) ) .scaleEffect(configuration.isPressed ? 0.9 : 1.0) } } #Preview { ButtonStyles() } ================================================ FILE: Demos/Stickers/Stickers/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Stickers/Stickers/StickerDataLoader.swift ================================================ // // StickerDataLoader.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import Vision import UIKit import AIProxy final actor StickerDataLoader { /// Creates a sticker from a given `prompt` using OpenAI's APIs /// On simulator, the sticker has an opaque background because the Vision framework is not available. /// On device, the sticker has a transparent background /// /// - Parameter prompt: The user-entered prompt /// - Returns: A sticker as a UIImage if we were able to get one from OpenAI, or nil otherwise func create(fromPrompt prompt: String) async throws -> UIImage? { let requestBody = OpenAICreateImageRequestBody( prompt: "cute design of a " + prompt + " kawaii sticker. nothing in the bg. white bg.", model: "dall-e-3" ) let response = try await AppConstants.openAIService.createImageRequest(body: requestBody) print(response.data.first?.url ?? "") guard let url = response.data.first?.url, let data = try? Data(contentsOf: url) else { AppLogger.error("OpenAI returned a sticker imageURL that we could not fetch") return nil } guard let img = UIImage(data: data) else { AppLogger.error("Could not create a UIImage from the imageURL provided by OpenAI") return nil } return img.extractForegroundWithVision() ?? img } } private extension UIImage { convenience init?(pixelBuffer: CVPixelBuffer) { let ciImage = CIImage(cvPixelBuffer: pixelBuffer) let context = CIContext(options: nil) guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil } self.init(cgImage: cgImage) } func extractForegroundWithVision() -> UIImage? { guard let cgImage = self.cgImage else { return nil } let request = VNGenerateForegroundInstanceMaskRequest() let handler = VNImageRequestHandler(cgImage: cgImage) do { try handler.perform([request]) guard let result = request.results?.first else { return nil } let foregroundPixelBuffer = try result.generateMaskedImage( ofInstances: result.allInstances, from: handler, croppedToInstancesExtent: false ) if let foregroundImage = UIImage(pixelBuffer: foregroundPixelBuffer) { return foregroundImage } } catch { AppLogger.info("Could not use Vision to cut the sticker out. Perhaps you are running on simulator?") } return nil } } ================================================ FILE: Demos/Stickers/Stickers/StickerImageView.swift ================================================ // // StickerImageView.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI /// Holds a sticker image. /// The sticker animates into view with a scale effect, and then floats in the Y-axis. struct StickerImageView: View { /// The sticker as UIImage let uiImage: UIImage @State private var floating = false @State private var showSticker = false private let floatingAnimation = Animation.easeInOut(duration: 2.0).repeatForever(autoreverses: true) var body: some View { Image(uiImage: uiImage) .resizable() .scaledToFit() .cornerRadius(14) .shadow(color:.black.opacity(0.28), radius: 8, x:0, y:4) .padding() .offset(y:floating ? 8.0 : -8.0) .animation(floatingAnimation, value: floating) .scaleEffect(showSticker ? 1.0 : 0.5) .animation(.bouncy, value: showSticker) .onAppear{ withAnimation(.bouncy){ floating = true showSticker = true } } } } ================================================ FILE: Demos/Stickers/Stickers/StickerInputView.swift ================================================ // // StickerInputView.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI /// The user enters a sticker prompt using this view. struct StickerInputView: View { enum FocusedField { case currentPrompt } /// Bind to a UI model's property for that property to change as the user enters text, /// and for programmatic changes to the UI model's property to be reflected in this view @Binding var currentPrompt: String @FocusState private var focusedField: FocusedField? var body: some View { VStack(spacing:8){ Text("Describe your sticker below") .frame(maxWidth: .infinity, alignment: .topLeading) .font(.system(size: 20, weight: .bold, design: .rounded)) .foregroundColor(.black.opacity(0.28)) TextField("type here...", text: $currentPrompt, axis: .vertical) .focused($focusedField, equals: .currentPrompt) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .font(.system(size: 36, weight: .bold, design: .rounded)) .textFieldStyle(.plain) .foregroundColor(.black.opacity(0.75)) .onAppear { focusedField = .currentPrompt } } .padding() } } ================================================ FILE: Demos/Stickers/Stickers/StickerLoadingView.swift ================================================ // // StickerLoadingView.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI @MainActor struct StickerLoadingView: View { /// Loading text to display while long requests to OpenAI are fulfilled @State private var currentLoadState = "Hold tight" var body: some View { VStack(spacing:16){ ProgressView() .controlSize(.extraLarge) .tint(.white) Text(currentLoadState) .transition(.move(edge: .bottom)) .font(.system(size: 20, weight: .semibold, design: .rounded)) .foregroundColor(.black.opacity(0.28)) } .frame(maxWidth:.infinity, maxHeight:.infinity) .onAppear { Task { try await Task.sleep(for: .seconds(4)) currentLoadState = "Generating sticker" try await Task.sleep(for: .seconds(4)) currentLoadState = "Finalizing" } } } } ================================================ FILE: Demos/Stickers/Stickers/StickerManager.swift ================================================ // // StickerManager.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import UIKit import SwiftUI /// The default message to display in the result view of the sticker experience. private let defaultUserMessage = "Tap on the image to copy to your clipboard." @MainActor @Observable final class StickerManager { /// The user-entered prompt var prompt: String = "" /// The current background color to use for the view var currentColor: Color = .teal /// The generated sticker as a UIImage private(set) var image: UIImage? /// A flag to indicate that the sticker is being generated and we are waiting on I/O from OpenAI private(set) var isProcessing = false /// The user message to display along with the generated sticker private(set) var userMessage = defaultUserMessage /// The set of potential background colors for the view private var bgColors: Set = [.teal, .mint, .indigo, .red, .pink, .purple, .orange, .brown, .blue, .cyan, .green, .yellow, .gray] /// A few examples to get the user's wheels turning private let placeholderExamples = [ "a cactus wearing a sombrero...", "a hedgehog riding a motorcycle...", "a kangaroo holding a basketball..." ] private var placeholderIndex = 0 private let stickerDataLoader = StickerDataLoader() /// Changes the user message briefly away from the default text. /// After two seconds, the user message reverts to the default message func flashUserMessage(_ message: String) { withAnimation(.bouncy) { userMessage = message } Task { [weak self] in try await Task.sleep(for: .seconds(2)) withAnimation(.bouncy) { [weak self] in self?.userMessage = defaultUserMessage } } } /// Creates a sticker from the current prompt stored in `self.prompt` func createSticker() { guard !self.isProcessing else { AppLogger.info("Already creating a sticker. Please wait") return } let prompt = self.prompt guard prompt.count > 0 else { AppLogger.error("Trying to submit a sticker without a prompt. This is a programmer error") return } self.isProcessing = true Task { self.image = try await stickerDataLoader.create(fromPrompt: prompt) self.isProcessing = false } } /// Change the placeholder prompt func nextPlaceholder() { self.placeholderIndex = (self.placeholderIndex + 1) % self.placeholderExamples.count self.prompt = self.placeholderExamples[self.placeholderIndex] } /// Returns to the starting point of the sticker experience, e.g. where no sticker is in the UI func startOver() { self.image = nil self.nextPlaceholder() self.currentColor = self.bgColors.randomElement()! } /// Regenerate a sticker using the same prompt func regenerate() { self.image = nil self.createSticker() self.currentColor = self.bgColors.randomElement()! } } ================================================ FILE: Demos/Stickers/Stickers/StickerView.swift ================================================ // // StickerView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI @MainActor struct StickerView: View { @Environment(\.dismiss) private var dismiss /// The manager that drives this view @Bindable var stickerManager: StickerManager var body: some View { VStack{ if let image = stickerManager.image { buildResultView(withUIImage: image) } else { promptView } } .toolbar { stickerToolbar } .onAppear() { stickerManager.startOver() } } private func buildResultView(withUIImage uiImage: UIImage) -> some View { VStack(spacing:16) { VStack{ StickerImageView(uiImage: uiImage) .onTapGesture { self.copyImage(uiImage: uiImage) } Spacer() userMessageView Spacer() } .background(stickerManager.currentColor.gradient) .cornerRadius(17) startOverButton regenerateButton } .padding() } private var promptView: some View { VStack(spacing:16) { if stickerManager.isProcessing { StickerLoadingView() .background(stickerManager.currentColor.gradient) .cornerRadius(17) } else { StickerInputView(currentPrompt: $stickerManager.prompt) .background(stickerManager.currentColor.gradient) .cornerRadius(17) generateButton } } .padding() } private var stickerToolbar: some View { Button("Clear") { stickerManager.prompt = "" } .disabled(stickerManager.prompt.count == 0) } private var userMessageView: some View { Text(stickerManager.userMessage) .font(.system(size: 24, weight: .semibold, design: .rounded)) .multilineTextAlignment(.center) .transition(.scale) } // MARK: - Actions /// Copy the passed `uiImage` to system clipboard private func copyImage(uiImage: UIImage) { UIPasteboard.general.image = uiImage stickerManager.flashUserMessage("Copied!") } // MARK: - Buttons /// Button to start from the prompt input view and dispose of the existing sticker private var startOverButton: some View { Button { withAnimation { stickerManager.startOver() } } label: { Label("Start Over", systemImage: "sparkles") .frame(maxWidth:.infinity) } .buttonStyle(ChunkyButtonStyle()) } /// Button to regenerate a sticker from the current prompt, disposing of the existing sticker. private var regenerateButton: some View { Button{ withAnimation{ stickerManager.regenerate() } }label:{ Label("Regenerate", systemImage: "arrow.triangle.2.circlepath") .frame(maxWidth:.infinity) } .buttonStyle(ChunkyButtonStyle()) } /// Button to generate a sticker from the current user prompt private var generateButton: some View { Button { withAnimation() { stickerManager.createSticker() } } label: { Label("Generate", systemImage: "sparkles") .frame(maxWidth: .infinity) } .buttonStyle(ChunkyButtonStyle()) } } #Preview { StickerView(stickerManager: StickerManager()) } ================================================ FILE: Demos/Stickers/Stickers/StickersApp.swift ================================================ // // StickersApp.swift // Stickers // // Created by Lou Zell // import SwiftUI @main @MainActor struct StickersApp: App { @State var stickerManager = StickerManager() var body: some Scene { WindowGroup { StickerView(stickerManager: stickerManager) } } } ================================================ FILE: Demos/Stickers/Stickers.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0DDD23262B4DE0A300B2AE5C /* StickersApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23252B4DE0A300B2AE5C /* StickersApp.swift */; }; 0DDD232A2B4DE0A400B2AE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23292B4DE0A400B2AE5C /* Assets.xcassets */; }; 0DDD232D2B4DE0A400B2AE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD232C2B4DE0A400B2AE5C /* Preview Assets.xcassets */; }; 0DDD24642B4DF17C00B2AE5C /* StickerDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD245E2B4DF17C00B2AE5C /* StickerDataLoader.swift */; }; 0DDD24652B4DF17C00B2AE5C /* StickerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD245F2B4DF17C00B2AE5C /* StickerInputView.swift */; }; 0DDD24662B4DF17C00B2AE5C /* StickerLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24602B4DF17C00B2AE5C /* StickerLoadingView.swift */; }; 0DDD24672B4DF17C00B2AE5C /* StickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24612B4DF17C00B2AE5C /* StickerView.swift */; }; 0DDD24682B4DF17C00B2AE5C /* StickerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24622B4DF17C00B2AE5C /* StickerImageView.swift */; }; 0DDD24692B4DF17C00B2AE5C /* StickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24632B4DF17C00B2AE5C /* StickerManager.swift */; }; 0DDD246C2B4DF18100B2AE5C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD246A2B4DF18100B2AE5C /* AppConstants.swift */; }; 0DDD246D2B4DF18100B2AE5C /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD246B2B4DF18100B2AE5C /* AppLogger.swift */; }; 0DDD24722B4DF1B000B2AE5C /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24712B4DF1B000B2AE5C /* ButtonStyles.swift */; }; 2CE770902C9B43CD00EC6C74 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CE7708F2C9B43CD00EC6C74 /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0DDD23222B4DE0A300B2AE5C /* Stickers.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stickers.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DDD23252B4DE0A300B2AE5C /* StickersApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersApp.swift; sourceTree = ""; }; 0DDD23292B4DE0A400B2AE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0DDD232C2B4DE0A400B2AE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0DDD245E2B4DF17C00B2AE5C /* StickerDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerDataLoader.swift; sourceTree = ""; }; 0DDD245F2B4DF17C00B2AE5C /* StickerInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerInputView.swift; sourceTree = ""; }; 0DDD24602B4DF17C00B2AE5C /* StickerLoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerLoadingView.swift; sourceTree = ""; }; 0DDD24612B4DF17C00B2AE5C /* StickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerView.swift; sourceTree = ""; }; 0DDD24622B4DF17C00B2AE5C /* StickerImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerImageView.swift; sourceTree = ""; }; 0DDD24632B4DF17C00B2AE5C /* StickerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManager.swift; sourceTree = ""; }; 0DDD246A2B4DF18100B2AE5C /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 0DDD246B2B4DF18100B2AE5C /* AppLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; }; 0DDD24712B4DF1B000B2AE5C /* ButtonStyles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0DDD231F2B4DE0A300B2AE5C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CE770902C9B43CD00EC6C74 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0DDD23192B4DE0A300B2AE5C = { isa = PBXGroup; children = ( 0DDD23242B4DE0A300B2AE5C /* Stickers */, 0DDD23232B4DE0A300B2AE5C /* Products */, ); sourceTree = ""; }; 0DDD23232B4DE0A300B2AE5C /* Products */ = { isa = PBXGroup; children = ( 0DDD23222B4DE0A300B2AE5C /* Stickers.app */, ); name = Products; sourceTree = ""; }; 0DDD23242B4DE0A300B2AE5C /* Stickers */ = { isa = PBXGroup; children = ( 0DDD246A2B4DF18100B2AE5C /* AppConstants.swift */, 0DDD246B2B4DF18100B2AE5C /* AppLogger.swift */, 0DDD24712B4DF1B000B2AE5C /* ButtonStyles.swift */, 0DDD245E2B4DF17C00B2AE5C /* StickerDataLoader.swift */, 0DDD24622B4DF17C00B2AE5C /* StickerImageView.swift */, 0DDD245F2B4DF17C00B2AE5C /* StickerInputView.swift */, 0DDD24602B4DF17C00B2AE5C /* StickerLoadingView.swift */, 0DDD24632B4DF17C00B2AE5C /* StickerManager.swift */, 0DDD23252B4DE0A300B2AE5C /* StickersApp.swift */, 0DDD24612B4DF17C00B2AE5C /* StickerView.swift */, 0DDD23292B4DE0A400B2AE5C /* Assets.xcassets */, 0DDD232B2B4DE0A400B2AE5C /* Preview Content */, ); path = Stickers; sourceTree = ""; }; 0DDD232B2B4DE0A400B2AE5C /* Preview Content */ = { isa = PBXGroup; children = ( 0DDD232C2B4DE0A400B2AE5C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0DDD23212B4DE0A300B2AE5C /* Stickers */ = { isa = PBXNativeTarget; buildConfigurationList = 0DDD23302B4DE0A400B2AE5C /* Build configuration list for PBXNativeTarget "Stickers" */; buildPhases = ( 0DDD231E2B4DE0A300B2AE5C /* Sources */, 0DDD231F2B4DE0A300B2AE5C /* Frameworks */, 0DDD23202B4DE0A300B2AE5C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Stickers; packageProductDependencies = ( 2CE7708F2C9B43CD00EC6C74 /* AIProxy */, ); productName = Stickers; productReference = 0DDD23222B4DE0A300B2AE5C /* Stickers.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0DDD231A2B4DE0A300B2AE5C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1510; TargetAttributes = { 0DDD23212B4DE0A300B2AE5C = { CreatedOnToolsVersion = 15.1; }; }; }; buildConfigurationList = 0DDD231D2B4DE0A300B2AE5C /* Build configuration list for PBXProject "Stickers" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0DDD23192B4DE0A300B2AE5C; packageReferences = ( 2CE7708E2C9B43CD00EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 0DDD23232B4DE0A300B2AE5C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0DDD23212B4DE0A300B2AE5C /* Stickers */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0DDD23202B4DE0A300B2AE5C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD232D2B4DE0A400B2AE5C /* Preview Assets.xcassets in Resources */, 0DDD232A2B4DE0A400B2AE5C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0DDD231E2B4DE0A300B2AE5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD24642B4DF17C00B2AE5C /* StickerDataLoader.swift in Sources */, 0DDD24692B4DF17C00B2AE5C /* StickerManager.swift in Sources */, 0DDD24672B4DF17C00B2AE5C /* StickerView.swift in Sources */, 0DDD24722B4DF1B000B2AE5C /* ButtonStyles.swift in Sources */, 0DDD246C2B4DF18100B2AE5C /* AppConstants.swift in Sources */, 0DDD24682B4DF17C00B2AE5C /* StickerImageView.swift in Sources */, 0DDD24662B4DF17C00B2AE5C /* StickerLoadingView.swift in Sources */, 0DDD246D2B4DF18100B2AE5C /* AppLogger.swift in Sources */, 0DDD24652B4DF17C00B2AE5C /* StickerInputView.swift in Sources */, 0DDD23262B4DE0A300B2AE5C /* StickersApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0DDD232E2B4DE0A400B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0DDD232F2B4DE0A400B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 0DDD23312B4DE0A400B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Stickers/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.stickers; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0DDD23322B4DE0A400B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Stickers/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.stickers; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0DDD231D2B4DE0A300B2AE5C /* Build configuration list for PBXProject "Stickers" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD232E2B4DE0A400B2AE5C /* Debug */, 0DDD232F2B4DE0A400B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0DDD23302B4DE0A400B2AE5C /* Build configuration list for PBXNativeTarget "Stickers" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23312B4DE0A400B2AE5C /* Debug */, 0DDD23322B4DE0A400B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CE7708E2C9B43CD00EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CE7708F2C9B43CD00EC6C74 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CE7708E2C9B43CD00EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DDD231A2B4DE0A300B2AE5C /* Project object */; } ================================================ FILE: Demos/Transcriber/Transcriber/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftData import AIProxy /// Use this actor for audio work @globalActor actor AudioActor { static let shared = AudioActor() } enum AppConstants { static let swiftDataModels: [any PersistentModel.Type] = [AudioRecording.self, TranscribedAudioRecording.self] static let swiftDataContainer = try! ModelContainer(for: AudioRecording.self, TranscribedAudioRecording.self) static let audioSampleQueue = DispatchQueue(label: "com.AIProxyBootstrap.audioSampleQueue") #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Transcriber/Transcriber/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapTranscriber") ================================================ FILE: Demos/Transcriber/Transcriber/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Transcriber/Transcriber/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "transcribe.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Transcriber/Transcriber/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Transcriber/Transcriber/AudioFileWriter.swift ================================================ // // AudioFileWriter.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import AVFoundation /// One of the following errors will be thrown at initialization if the microphone vendor can't vend samples. enum AudioFileWriterError: Error { case couldNotWriteToDestinationURL case couldNotCreateAudioInput } /// Writes an m4a file out of audio sample buffers. /// Samples passed to the `append` method will be written to the m4a file between calls to `init()` and `finishWriting()`. /// Create one instance of AudioFileWriter for each audio file that you'd like to write. @AudioActor final class AudioFileWriter { /// The location to write the audio file to let fileURL: URL private let assetWriter: AVAssetWriter private let microphoneWriter: AVAssetWriterInput private let audioSettings: [String: Any] = [ AVFormatIDKey: kAudioFormatMPEG4AAC, AVSampleRateKey: 48_000, AVNumberOfChannelsKey: 2, AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] private var isWriting = false /// Throws one of `AudioFileWriterError` if we can't initialize the AVFoundation dependencies /// - Parameter fileURL: The location to write the audio file to init(fileURL: URL) throws { self.fileURL = fileURL do { self.assetWriter = try AVAssetWriter(outputURL: fileURL, fileType: .m4a) } catch { throw AudioFileWriterError.couldNotWriteToDestinationURL } self.microphoneWriter = AVAssetWriterInput(mediaType: .audio, outputSettings: self.audioSettings) self.microphoneWriter.expectsMediaDataInRealTime = true if self.assetWriter.canAdd(self.microphoneWriter) { self.assetWriter.add(self.microphoneWriter) } else { throw AudioFileWriterError.couldNotCreateAudioInput } } /// Append a sample buffer to the audio file /// - Parameter sample: A core media sample buffer. See the `MicrophoneSampleVendor` file for an example of how to source these. func append(sample: CMSampleBuffer) { if !self.isWriting { self.assetWriter.startWriting() self.assetWriter.startSession(atSourceTime: sample.presentationTimeStamp) self.isWriting = true } if self.microphoneWriter.isReadyForMoreMediaData { self.microphoneWriter.append(sample) } else { AppLogger.warning("The AudioFileWriter is not ready for more audio data") } } /// Finishes writing the file to disk /// - Returns: URL location of the m4a file on disk func finishWriting() async -> URL { self.microphoneWriter.markAsFinished() await self.assetWriter.finishWriting() self.isWriting = false return self.fileURL } } ================================================ FILE: Demos/Transcriber/Transcriber/AudioRecorder.swift ================================================ // // Manager.swift // OpenAIExperiment // // Created by Lou Zell // import AVFoundation import Foundation @AudioActor final class AudioRecorder { private var microphoneSampleVendor: MicrophoneSampleVendor? private var audioFileWriter: AudioFileWriter? nonisolated init() {} /// Start recording an audio file /// - Returns: true if the audio recorder was able to start recording, false otherwise func start() -> Bool { do { self.microphoneSampleVendor = try MicrophoneSampleVendor() } catch { AppLogger.error("Could not create a MicrophoneSampleVendor: \(error)") return false } do { self.audioFileWriter = try AudioFileWriter(fileURL: FileUtils.getFileURL()) } catch { AppLogger.error("Could not create an audio file writer: \(error)") return false } self.microphoneSampleVendor?.start(onSample: { [weak self] sampleBuffer in self?.audioFileWriter?.append(sample: sampleBuffer) }) return true } /// Returns the recording created between calls to `startRecording` and `stopRecording` func stopRecording(duration: String) async -> AudioRecording? { guard let fileWriter = self.audioFileWriter, let sampleVendor = self.microphoneSampleVendor else { AppLogger.warning("Expected audio dependencies to be set") return nil } sampleVendor.stop() let url = await fileWriter.finishWriting() return AudioRecording(localUrl: url, duration: duration) } } ================================================ FILE: Demos/Transcriber/Transcriber/AudioRecording.swift ================================================ // // AudioRecording.swift // Transcriber // // Created by Lou Zell // import Foundation import SwiftData /// Encapsulates a recording. The `url` is the location on disk of the raw audio file (an m4a). @Model final class AudioRecording { @Attribute(.unique) let localUrl: URL let duration: String init(localUrl: URL, duration: String) { self.localUrl = localUrl self.duration = duration } var resolvedURL: URL? { // There is a little nuance here. Every time you build and run the app the apple sandbox changes. // We first try to find the associated file at the spot that we stored it, but if it's not there then // we construct a new URL based on the current apple sandbox if (FileManager.default.fileExists(atPath: self.localUrl.path)) { return self.localUrl } else { let resolvedURL = FileUtils.getDocumentsURL().appending(component: self.localUrl.lastPathComponent) if FileManager.default.fileExists(atPath: resolvedURL.path) { return resolvedURL } } return nil } } ================================================ FILE: Demos/Transcriber/Transcriber/ButtonStyles.swift ================================================ // // ButtonStyles.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct ButtonStyles: View { var body: some View { ZStack{ Color(.systemGroupedBackground) .ignoresSafeArea() VStack(spacing:48){ Button{ /// do something }label:{ HStack(spacing:6){ Image(systemName: "play.circle.fill") .font(.system(size: 15, weight:.semibold, design: .rounded)) Text("10s") .font(.system(size: 11, weight: .regular, design: .monospaced)) .fontDesign(.monospaced) } } .buttonStyle(TranscriptionButtonStyle()) Button(){ /// do something } label:{ Label("Chunky Button", systemImage: "sparkles") } .buttonStyle(ChunkyButtonStyle(offsetSize: 10.0)) Button{ /// do something } label: { ZStack{ RoundedRectangle(cornerRadius: 45, style:.continuous) .fill(.red.gradient) .frame(width:85, height:85) Image(systemName: "waveform") .font(.title) .foregroundColor(.white) } } .buttonStyle(RecordButton()) Button(){ /// do something }label:{ Image(systemName: "arrow.forward") .font(.system(size: 17, weight: .bold)) } .buttonStyle(TranslateButton()) } .padding() } } } struct ChunkyButtonStyle: ButtonStyle { var offsetSize = 10.0 func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 24, weight: .bold, design: .rounded)) .foregroundColor(.black) .padding() .offset(y: configuration.isPressed ? offsetSize-2 : 0) .background( GeometryReader{ geo in RoundedRectangle(cornerRadius: 17) .strokeBorder(Color.black, lineWidth: 4) .background( RoundedRectangle(cornerRadius: 17) .fill(.white) ) .offset(y:offsetSize) .overlay( RoundedRectangle(cornerRadius: 17) .fill(.white) .strokeBorder(Color.black, lineWidth: 4) .background(RoundedRectangle(cornerRadius: 17).fill(Color.white)) .offset(y: configuration.isPressed ? offsetSize : 0) ) } ) } } struct RecordButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .frame(maxWidth:80, maxHeight: 80) .shadow(color:.black.opacity(0.28), radius: 10, y:8) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) .padding(.bottom, 40) } } struct TranslateButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.white) .frame(width:56, height:56) .background( Circle() .fill(.teal.gradient) ) .brightness(configuration.isPressed ? -0.1 : 0.0) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) } } struct TranscriptionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.secondary) .padding(.leading, 4) .padding(.trailing, 8) .padding(.vertical, 4) .background( Capsule().fill(.secondary.opacity(configuration.isPressed ? 0.28 : 0.14)) ) .scaleEffect(configuration.isPressed ? 0.9 : 1.0) } } #Preview { ButtonStyles() } ================================================ FILE: Demos/Transcriber/Transcriber/FileUtils.swift ================================================ // // FileUtils.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation struct FileUtils { private init() { fatalError("FileUtils is a namespace only") } static func getDocumentsURL() -> URL { guard let documentsUrl = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask).first else { fatalError("Could could not find the Documents directory") } return documentsUrl } static func getFileURL() -> URL { let documentsUrl = self.getDocumentsURL() let isoFormatter = ISO8601DateFormatter() isoFormatter.formatOptions = [.withFullDate, .withTime, .withColonSeparatorInTime] isoFormatter.timeZone = .current var dateString = isoFormatter.string(from: Date()) dateString = dateString.replacingOccurrences(of: ":", with: ".") let filename = "AIProxyBootstrap-\(dateString).m4a" return documentsUrl.appendingPathComponent(filename) } static func deleteFile(at url: URL) { do { try FileManager.default.removeItem(at: url) } catch { AppLogger.info("Could not find file to delete at \(url)") } } } ================================================ FILE: Demos/Transcriber/Transcriber/MicrophoneSampleVendor.swift ================================================ // // MicrophoneSampleVendor.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import CoreMedia import AVFoundation /// One of the following errors will be thrown at initialization if the microphone vendor can't vend samples. enum MicrophoneSampleVendorError: Error { case micNotFound case micNotUsableAsCaptureDevice case captureSessionRejectedMic case captureSessionRejectedOutput } /// Vends samples of the microphone audio /// /// ## Requirements /// /// - Assumes an `NSMicrophoneUsageDescription` description has been added to Target > Info /// - Assumes that microphone permissions have already been granted /// /// ## Usage /// /// ``` /// self.microphoneVendor = try MicrophoneSampleVendor() /// self.microphoneVendor.start { sample in /// // Do something with `sample` /// // Note: this callback is invoked on a background thread /// } /// ``` /// @AudioActor final class MicrophoneSampleVendor { private let captureSession = AVCaptureSession() private let audioOutput = AVCaptureAudioDataOutput() private let sampleBufferDelegate = SampleBufferDelegate() init() throws { let mic = try findMic() let micInput = try mic.asInput() try self.captureSession.addMicInput(micInput) try self.captureSession.addAudioOutput(self.audioOutput) self.audioOutput.setSampleBufferDelegate(self.sampleBufferDelegate, queue: AppConstants.audioSampleQueue) } func start(onSample: @escaping (CMSampleBuffer) -> Void) { self.sampleBufferDelegate.sampleCallback = onSample self.captureSession.startRunning() } func stop() { self.captureSession.stopRunning() } } private class SampleBufferDelegate: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate { var sampleCallback: ((CMSampleBuffer) -> Void)? func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { dispatchPrecondition(condition: .onQueue(AppConstants.audioSampleQueue)) self.sampleCallback?(sampleBuffer) } } private func findMic() throws -> AVCaptureDevice { let microphones = AVCaptureDevice.DiscoverySession(deviceTypes: [.microphone], mediaType: .audio, position: .unspecified).devices if let mic = microphones.first { return mic } throw MicrophoneSampleVendorError.micNotFound } private extension AVCaptureDevice { func asInput() throws -> AVCaptureDeviceInput { do { return try AVCaptureDeviceInput(device: self) } catch { throw MicrophoneSampleVendorError.micNotUsableAsCaptureDevice } } } private extension AVCaptureSession { func addMicInput(_ micInput: AVCaptureDeviceInput) throws { if self.canAddInput(micInput) { self.addInput(micInput) } else { throw MicrophoneSampleVendorError.captureSessionRejectedMic } } func addAudioOutput(_ audioOutput: AVCaptureAudioDataOutput) throws { if self.canAddOutput(audioOutput) { self.addOutput(audioOutput) } else { throw MicrophoneSampleVendorError.captureSessionRejectedMic } } } ================================================ FILE: Demos/Transcriber/Transcriber/ModelContext+Extensions.swift ================================================ // // ModelContext+Extensions.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftData extension ModelContext { /// Deletes all models in `AppConstants.swiftDataModels` from SwiftData. /// Use this during development to return to a clean slate. func reset() { do { for model in AppConstants.swiftDataModels { try self.delete(model: model) } } catch { AppLogger.error("Failed to reset swift data for all models. Error: \(error.localizedDescription)") } } } ================================================ FILE: Demos/Transcriber/Transcriber/NoRecordingsView.swift ================================================ // // NoRecordingsView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct NoRecordingsView: View { var body: some View { VStack{ Image(systemName: "waveform") .font(.largeTitle) .foregroundColor(.secondary) .padding(.bottom, 8) Text("No recordings") .font(.headline) Text("Tap the record button below to start transcribing.") .multilineTextAlignment(.center) .frame(maxWidth:240) .foregroundColor(.secondary) .font(.subheadline) } .frame(maxHeight:.infinity) .foregroundColor(.primary) .padding(.bottom, 48) } } #Preview { NoRecordingsView() } ================================================ FILE: Demos/Transcriber/Transcriber/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Transcriber/Transcriber/RecordingRowView.swift ================================================ // // RecordingRowView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct RecordingRowView: View { let recording: TranscribedAudioRecording @State private var startAnimation = false var body: some View { HStack(spacing:0){ Text(recording.transcript) .font(.body) Spacer() Button{ recording.play() }label:{ HStack(spacing:6){ Image(systemName: "play.circle.fill") .font(.system(size: 15, weight:.semibold, design: .rounded)) Text("\(recording.audioRecording.duration)s") .font(.system(size: 11, weight: .regular, design: .monospaced)) } } .buttonStyle(TranscriptionButtonStyle()) } .padding(.vertical, 8) .opacity(startAnimation ? 1 : 0) .offset(y:startAnimation ? 0 : -10) .onAppear{ withAnimation(.smooth.delay(0.2)){ startAnimation = true } } } } #Preview { RecordingRowView(recording: previewRecording()) .padding() } private func previewRecording() -> TranscribedAudioRecording { let audioRecording = AudioRecording(localUrl: URL(fileURLWithPath: "/dev/null"), duration: "1.2s") return TranscribedAudioRecording( audioRecording: audioRecording, transcript: "hello world", createdAt: Date() ) } ================================================ FILE: Demos/Transcriber/Transcriber/TranscribedAudioRecording.swift ================================================ // // TranscribedAudioRecording.swift // Transcriber // // Created by Lou Zell // import AVFoundation import Foundation import SwiftData /// Encapsulates a transcribed audio recording @Model final class TranscribedAudioRecording { @Relationship(deleteRule: .cascade) var audioRecording: AudioRecording let transcript: String let createdAt: Date @Transient var player: AVAudioPlayer? init(audioRecording: AudioRecording, transcript: String, createdAt: Date) { self.audioRecording = audioRecording self.transcript = transcript self.createdAt = createdAt } func play() { guard let resolvedURL = self.audioRecording.resolvedURL else { AppLogger.error("The audio recording model does not have an associated audio file") return } AppLogger.info("Playing file at \(resolvedURL), which exists? \(FileManager.default.fileExists(atPath: resolvedURL.path))") Task.detached { do { try AVAudioSession.sharedInstance().setCategory(.playback) self.player = try AVAudioPlayer(contentsOf: resolvedURL) self.player?.play() } catch { AppLogger.error("Could not play audio file. Error: \(error.localizedDescription)") } } } } ================================================ FILE: Demos/Transcriber/Transcriber/TranscriberApp.swift ================================================ // // TranscriberApp.swift // Transcriber // // Created by Lou Zell // import SwiftUI @main @MainActor struct TranscriberApp: App { @State var transcriberManager = TranscriberManager() var body: some Scene { WindowGroup { TranscriberView(transcriberManager: transcriberManager) } } } ================================================ FILE: Demos/Transcriber/Transcriber/TranscriberDataLoader.swift ================================================ // // TranscriberDataLoader.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import AIProxy /// Interfaces with OpenAI to convert a recording into a transcript final actor TranscriberDataLoader { /// Run the OpenAI transcriber on an audio recording /// - Parameter recording: the audio recording to transcribe /// - Returns: a transcript of the recording created by OpenAI's Whisper model func run(onRecording recording: AudioRecording) async -> String { do { let requestBody = OpenAICreateTranscriptionRequestBody( file: try Data(contentsOf: recording.localUrl), model: "whisper-1" ) let response = try await AppConstants.openAIService.createTranscriptionRequest(body: requestBody) return response.text } catch { AppLogger.error("Could not get transcript from OpenAI: \(error.localizedDescription)") return "Transcription Error" } } } ================================================ FILE: Demos/Transcriber/Transcriber/TranscriberManager.swift ================================================ // // TranscriberManager.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI import SwiftData @MainActor @Observable final class TranscriberManager { private(set) var isRecording = false private let audioRecorder = AudioRecorder() private let transcriber = TranscriberDataLoader() private let modelContext: ModelContext var recordings = [TranscribedAudioRecording]() init() { let context = AppConstants.swiftDataContainer.mainContext self.recordings = fetchPersistedRecordings(context) self.modelContext = context } /// This pollutes the manager a bit. /// I wrote of a better way to do this, here: https://stackoverflow.com/a/77772091/143447 /// - Parameter newValue: The value to set `isRecording` to private func setIsRecording(_ newValue: Bool) { withAnimation(.smooth(duration: 0.75)) { self.isRecording = newValue } } /// Start recording an audio file func startRecording() async { self.setIsRecording(await self.audioRecorder.start()) if !self.isRecording { AppLogger.error("Could not start the audio recorder") } } /// Stop recording the audio file and transcribe it to text with Whisper /// - Parameter duration: Annotate the audio file with this duration. func stopRecording(duration: String) async { if let recording = await self.audioRecorder.stopRecording(duration: duration) { let transcript = await self.transcriber.run(onRecording: recording) let transcribed = TranscribedAudioRecording(audioRecording: recording, transcript: transcript, createdAt: Date()) self.modelContext.insert(transcribed) self.recordings = fetchPersistedRecordings(self.modelContext) } self.setIsRecording(false) } /// Removes a recording from persistent storage and deletes the associated audio file from disk /// - Parameter index: the index in `recordings` to delete func deleteRecording(at index: Int) { FileUtils.deleteFile(at: self.recordings[index].audioRecording.localUrl) self.modelContext.delete(self.recordings[index]) self.recordings = fetchPersistedRecordings(self.modelContext) } } private func fetchPersistedRecordings(_ modelContext: ModelContext) -> [TranscribedAudioRecording] { do { let descriptor = FetchDescriptor( sortBy: [SortDescriptor(\TranscribedAudioRecording.createdAt, order: .reverse)] ) return try modelContext.fetch(descriptor) } catch { AppLogger.error("Could not fetch audio recordings with SwiftData") return [] } } ================================================ FILE: Demos/Transcriber/Transcriber/TranscriberView.swift ================================================ // // TranscribeView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftData import SwiftUI import AVFoundation @MainActor struct TranscriberView: View { let transcriberManager: TranscriberManager var isRecording: Bool { transcriberManager.isRecording } @State private var showDot = false @State private var isPulsing = false @State private var isProcessing = false @State var isTimerRunning = false @State private var startTime = Date() @State private var timerString = "0:00" @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() // With vibration private let startSFX: SystemSoundID = 1113 private let stopSFX: SystemSoundID = 1114 private let deviceWidth = UIScreen.main.bounds.width private let deviceHeight = UIScreen.main.bounds.height private var initialX: Double { deviceWidth / 2.0 } private var midY: Double { -deviceHeight / 2.0 + 80 } var body: some View { ZStack(alignment:.bottom){ if transcriberManager.recordings.count > 0 { List{ ForEach(transcriberManager.recordings) { recording in RecordingRowView(recording: recording) } .onDelete { indexSet in if let index = indexSet.first { self.transcriberManager.deleteRecording(at: index) } } } .listStyle(.plain) } else { NoRecordingsView() } /// Overlay when recording if isRecording { ZStack{ VStack(spacing:4){ Text(isProcessing ? "Processing" : "Recording") .font(.title2) .fontWeight(.bold) .foregroundColor(.primary) Text(isProcessing ? "This may take a second" : "\(self.timerString)s") .font(.system(size: 13, weight: .medium, design: .monospaced)) .foregroundColor(.secondary) .onReceive(timer) { _ in if self.isTimerRunning { timerString = String(format: "%.2f", (Date().timeIntervalSince( self.startTime))) } } } .offset(y:-140) } .frame(maxWidth: .infinity, maxHeight:.infinity) .ignoresSafeArea() .background(.ultraThinMaterial) .transition(.opacity) } GeometryReader { geometry in /// Gooey button effect Canvas { context, size in let circle0 = context.resolveSymbol(id: 0)! let circle1 = context.resolveSymbol(id: 1)! context.addFilter(.alphaThreshold(min: 0.25, color: .primary)) context.addFilter(.blur(radius: 15)) context.drawLayer {context in context.draw(circle0, at: CGPoint(x:initialX, y:geometry.size.height - 80)) context.draw(circle1, at: CGPoint(x:initialX, y:geometry.size.height - 80)) } } symbols: { Circle() .frame(width: 80, height: 80) .scaleEffect(isProcessing ? 0.75 : 1, anchor: .center) .tag(0) Circle() .frame(width: 80, height: 80) .tag(1) .scaleEffect(showDot ? 1 : 0.5, anchor: .center) .scaleEffect(isPulsing ? 1.15 : 1, anchor: .center) .offset(y: showDot ? midY : 0) } } Button{ UIImpactFeedbackGenerator(style: .medium).impactOccurred() /// Start recording if !isRecording{ Task { await transcriberManager.startRecording() } AudioServicesPlaySystemSound(startSFX) timerString = "0.00" startTime = Date() // start UI updates self.startTimer() withAnimation(.smooth(duration:0.75)){ showDot = true } withAnimation(.easeInOut(duration: 0.75).repeatForever(autoreverses: true)){ isPulsing = true } isTimerRunning = true } else { /// Stop recording Task { await transcriberManager.stopRecording(duration: self.timerString) withAnimation(.bouncy){ isProcessing = false } } AudioServicesPlaySystemSound(stopSFX) self.stopTimer() isTimerRunning = false withAnimation(.smooth(duration: 0.75)){ showDot = false } withAnimation(.default){ isProcessing = true isPulsing = false } } } label: { ZStack{ RoundedRectangle(cornerRadius: isRecording ? 8 : 45, style:.continuous) .fill(.red.gradient) .frame(width:isRecording ? 40 : 85, height:isRecording ? 40 : 85) .opacity(isProcessing ? 0 : 1) if isRecording { ProgressView() .opacity(isProcessing ? 1.0 : 0) .tint(.primary) .colorInvert() } else { Image(systemName: "waveform") .font(.title) .foregroundColor(.white) .transition(.scale) } } } .buttonStyle(RecordButton()) .disabled(isProcessing ? true : false) } } func stopTimer() { self.timer.upstream.connect().cancel() } func startTimer() { self.timer = Timer.publish(every: 00.01, on: .main, in: .common).autoconnect() } } #Preview { TranscriberView(transcriberManager: TranscriberManager()) } ================================================ FILE: Demos/Transcriber/Transcriber.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0DA9CE802B51186E000D047E /* TranscribedAudioRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA9CE7F2B51186E000D047E /* TranscribedAudioRecording.swift */; }; 0DA9CE822B51193D000D047E /* AudioRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA9CE812B51193D000D047E /* AudioRecording.swift */; }; 0DDD239A2B4DE36300B2AE5C /* TranscriberApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD23992B4DE36300B2AE5C /* TranscriberApp.swift */; }; 0DDD239E2B4DE36400B2AE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD239D2B4DE36400B2AE5C /* Assets.xcassets */; }; 0DDD23A12B4DE36400B2AE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23A02B4DE36400B2AE5C /* Preview Assets.xcassets */; }; 0DDD24232B4DEF8E00B2AE5C /* TranscriberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD241B2B4DEF8E00B2AE5C /* TranscriberView.swift */; }; 0DDD24242B4DEF8E00B2AE5C /* MicrophoneSampleVendor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD241C2B4DEF8E00B2AE5C /* MicrophoneSampleVendor.swift */; }; 0DDD24252B4DEF8E00B2AE5C /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD241D2B4DEF8E00B2AE5C /* AudioRecorder.swift */; }; 0DDD24262B4DEF8E00B2AE5C /* TranscriberDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD241E2B4DEF8E00B2AE5C /* TranscriberDataLoader.swift */; }; 0DDD24272B4DEF8E00B2AE5C /* AudioFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD241F2B4DEF8E00B2AE5C /* AudioFileWriter.swift */; }; 0DDD24282B4DEF8E00B2AE5C /* NoRecordingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24202B4DEF8E00B2AE5C /* NoRecordingsView.swift */; }; 0DDD24292B4DEF8E00B2AE5C /* TranscriberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24212B4DEF8E00B2AE5C /* TranscriberManager.swift */; }; 0DDD242A2B4DEF8E00B2AE5C /* RecordingRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24222B4DEF8E00B2AE5C /* RecordingRowView.swift */; }; 0DDD242D2B4DEF9C00B2AE5C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD242B2B4DEF9C00B2AE5C /* AppConstants.swift */; }; 0DDD242E2B4DEF9C00B2AE5C /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD242C2B4DEF9C00B2AE5C /* AppLogger.swift */; }; 0DDD24302B4DEFB100B2AE5C /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD242F2B4DEFB100B2AE5C /* FileUtils.swift */; }; 0DDD24322B4DEFC700B2AE5C /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24312B4DEFC700B2AE5C /* ButtonStyles.swift */; }; 0DDD24742B4DF1DF00B2AE5C /* ModelContext+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24732B4DF1DF00B2AE5C /* ModelContext+Extensions.swift */; }; 2CE7709E2C9B83F500EC6C74 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CE7709D2C9B83F500EC6C74 /* AIProxy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0DA9CE7F2B51186E000D047E /* TranscribedAudioRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribedAudioRecording.swift; sourceTree = ""; }; 0DA9CE812B51193D000D047E /* AudioRecording.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecording.swift; sourceTree = ""; }; 0DDD23962B4DE36300B2AE5C /* Transcriber.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transcriber.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DDD23992B4DE36300B2AE5C /* TranscriberApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscriberApp.swift; sourceTree = ""; }; 0DDD239D2B4DE36400B2AE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0DDD23A02B4DE36400B2AE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0DDD241B2B4DEF8E00B2AE5C /* TranscriberView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranscriberView.swift; sourceTree = ""; }; 0DDD241C2B4DEF8E00B2AE5C /* MicrophoneSampleVendor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrophoneSampleVendor.swift; sourceTree = ""; }; 0DDD241D2B4DEF8E00B2AE5C /* AudioRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioRecorder.swift; sourceTree = ""; }; 0DDD241E2B4DEF8E00B2AE5C /* TranscriberDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranscriberDataLoader.swift; sourceTree = ""; }; 0DDD241F2B4DEF8E00B2AE5C /* AudioFileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioFileWriter.swift; sourceTree = ""; }; 0DDD24202B4DEF8E00B2AE5C /* NoRecordingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRecordingsView.swift; sourceTree = ""; }; 0DDD24212B4DEF8E00B2AE5C /* TranscriberManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranscriberManager.swift; sourceTree = ""; }; 0DDD24222B4DEF8E00B2AE5C /* RecordingRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordingRowView.swift; sourceTree = ""; }; 0DDD242B2B4DEF9C00B2AE5C /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 0DDD242C2B4DEF9C00B2AE5C /* AppLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; }; 0DDD242F2B4DEFB100B2AE5C /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; }; 0DDD24312B4DEFC700B2AE5C /* ButtonStyles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; 0DDD24732B4DF1DF00B2AE5C /* ModelContext+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ModelContext+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0DDD23932B4DE36300B2AE5C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CE7709E2C9B83F500EC6C74 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0DDD238D2B4DE36300B2AE5C = { isa = PBXGroup; children = ( 0DDD23982B4DE36300B2AE5C /* Transcriber */, 0DDD23972B4DE36300B2AE5C /* Products */, ); sourceTree = ""; }; 0DDD23972B4DE36300B2AE5C /* Products */ = { isa = PBXGroup; children = ( 0DDD23962B4DE36300B2AE5C /* Transcriber.app */, ); name = Products; sourceTree = ""; }; 0DDD23982B4DE36300B2AE5C /* Transcriber */ = { isa = PBXGroup; children = ( 0DDD242B2B4DEF9C00B2AE5C /* AppConstants.swift */, 0DDD242C2B4DEF9C00B2AE5C /* AppLogger.swift */, 0DDD241F2B4DEF8E00B2AE5C /* AudioFileWriter.swift */, 0DDD241D2B4DEF8E00B2AE5C /* AudioRecorder.swift */, 0DA9CE812B51193D000D047E /* AudioRecording.swift */, 0DDD24312B4DEFC700B2AE5C /* ButtonStyles.swift */, 0DDD242F2B4DEFB100B2AE5C /* FileUtils.swift */, 0DDD241C2B4DEF8E00B2AE5C /* MicrophoneSampleVendor.swift */, 0DDD24732B4DF1DF00B2AE5C /* ModelContext+Extensions.swift */, 0DDD24202B4DEF8E00B2AE5C /* NoRecordingsView.swift */, 0DDD24222B4DEF8E00B2AE5C /* RecordingRowView.swift */, 0DA9CE7F2B51186E000D047E /* TranscribedAudioRecording.swift */, 0DDD23992B4DE36300B2AE5C /* TranscriberApp.swift */, 0DDD241E2B4DEF8E00B2AE5C /* TranscriberDataLoader.swift */, 0DDD24212B4DEF8E00B2AE5C /* TranscriberManager.swift */, 0DDD241B2B4DEF8E00B2AE5C /* TranscriberView.swift */, 0DDD239D2B4DE36400B2AE5C /* Assets.xcassets */, 0DDD239F2B4DE36400B2AE5C /* Preview Content */, ); path = Transcriber; sourceTree = ""; }; 0DDD239F2B4DE36400B2AE5C /* Preview Content */ = { isa = PBXGroup; children = ( 0DDD23A02B4DE36400B2AE5C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0DDD23952B4DE36300B2AE5C /* Transcriber */ = { isa = PBXNativeTarget; buildConfigurationList = 0DDD23A42B4DE36400B2AE5C /* Build configuration list for PBXNativeTarget "Transcriber" */; buildPhases = ( 0DDD23922B4DE36300B2AE5C /* Sources */, 0DDD23932B4DE36300B2AE5C /* Frameworks */, 0DDD23942B4DE36300B2AE5C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Transcriber; packageProductDependencies = ( 2CE7709D2C9B83F500EC6C74 /* AIProxy */, ); productName = Transcriber; productReference = 0DDD23962B4DE36300B2AE5C /* Transcriber.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0DDD238E2B4DE36300B2AE5C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1510; TargetAttributes = { 0DDD23952B4DE36300B2AE5C = { CreatedOnToolsVersion = 15.1; }; }; }; buildConfigurationList = 0DDD23912B4DE36300B2AE5C /* Build configuration list for PBXProject "Transcriber" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0DDD238D2B4DE36300B2AE5C; packageReferences = ( 2CE7709C2C9B83F500EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 0DDD23972B4DE36300B2AE5C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0DDD23952B4DE36300B2AE5C /* Transcriber */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0DDD23942B4DE36300B2AE5C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD23A12B4DE36400B2AE5C /* Preview Assets.xcassets in Resources */, 0DDD239E2B4DE36400B2AE5C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0DDD23922B4DE36300B2AE5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD24282B4DEF8E00B2AE5C /* NoRecordingsView.swift in Sources */, 0DDD24292B4DEF8E00B2AE5C /* TranscriberManager.swift in Sources */, 0DDD24232B4DEF8E00B2AE5C /* TranscriberView.swift in Sources */, 0DDD24302B4DEFB100B2AE5C /* FileUtils.swift in Sources */, 0DA9CE802B51186E000D047E /* TranscribedAudioRecording.swift in Sources */, 0DDD24242B4DEF8E00B2AE5C /* MicrophoneSampleVendor.swift in Sources */, 0DDD242A2B4DEF8E00B2AE5C /* RecordingRowView.swift in Sources */, 0DDD24252B4DEF8E00B2AE5C /* AudioRecorder.swift in Sources */, 0DDD24742B4DF1DF00B2AE5C /* ModelContext+Extensions.swift in Sources */, 0DDD24322B4DEFC700B2AE5C /* ButtonStyles.swift in Sources */, 0DDD239A2B4DE36300B2AE5C /* TranscriberApp.swift in Sources */, 0DDD24262B4DEF8E00B2AE5C /* TranscriberDataLoader.swift in Sources */, 0DDD24272B4DEF8E00B2AE5C /* AudioFileWriter.swift in Sources */, 0DDD242E2B4DEF9C00B2AE5C /* AppLogger.swift in Sources */, 0DA9CE822B51193D000D047E /* AudioRecording.swift in Sources */, 0DDD242D2B4DEF9C00B2AE5C /* AppConstants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0DDD23A22B4DE36400B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0DDD23A32B4DE36400B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 0DDD23A52B4DE36400B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Transcriber/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSMicrophoneUsageDescription = "The microphone is required to create audio recordings"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.transcriber; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0DDD23A62B4DE36400B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Transcriber/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSMicrophoneUsageDescription = "The microphone is required to create audio recordings"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.transcriber; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0DDD23912B4DE36300B2AE5C /* Build configuration list for PBXProject "Transcriber" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23A22B4DE36400B2AE5C /* Debug */, 0DDD23A32B4DE36400B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0DDD23A42B4DE36400B2AE5C /* Build configuration list for PBXNativeTarget "Transcriber" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23A52B4DE36400B2AE5C /* Debug */, 0DDD23A62B4DE36400B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CE7709C2C9B83F500EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CE7709D2C9B83F500EC6C74 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CE7709C2C9B83F500EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DDD238E2B4DE36300B2AE5C /* Project object */; } ================================================ FILE: Demos/Translator/Translator/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import AIProxy enum AppConstants { #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Translator/Translator/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapTranslator") ================================================ FILE: Demos/Translator/Translator/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Translator/Translator/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "translate.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Translator/Translator/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Translator/Translator/BottomTranslateView.swift ================================================ // // BottomTranslateView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct BottomTranslateView: View { @Binding var processing:Bool @Binding var translatedText:String var body: some View { VStack{ VStack(alignment:.leading, spacing:8){ Text("Spanish") .font(.callout) .foregroundColor(.secondary) if processing { ProgressView() .frame(maxWidth: .infinity, maxHeight:.infinity) } else{ Text(translatedText) .font(.title2) } } .frame(maxWidth: .infinity, maxHeight:.infinity, alignment:.topLeading) HStack(spacing:0){ Button(){ /// copy result } label:{ Image(systemName: "square.on.square") .font(.title2) } .frame(width:44, height:44) } .frame(maxWidth: .infinity, alignment:.leading) } .frame(maxWidth: .infinity, maxHeight:.infinity) .padding(16) .background( RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(Color(.tertiarySystemBackground)) .shadow(color:.black.opacity(0.14), radius: 1) ) } } #Preview { BottomTranslateView(processing: .constant(false), translatedText: .constant("")) } ================================================ FILE: Demos/Translator/Translator/ButtonStyles.swift ================================================ // // ButtonStyles.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct ButtonStyles: View { var body: some View { ZStack{ Color(.systemGroupedBackground) .ignoresSafeArea() VStack(spacing:48){ Button{ /// do something }label:{ HStack(spacing:6){ Image(systemName: "play.circle.fill") .font(.system(size: 15, weight:.semibold, design: .rounded)) Text("10s") .font(.system(size: 11, weight: .regular, design: .monospaced)) .fontDesign(.monospaced) } } .buttonStyle(TranscriptionButtonStyle()) Button(){ /// do something } label:{ Label("Chunky Button", systemImage: "sparkles") } .buttonStyle(ChunkyButtonStyle(offsetSize: 10.0)) Button{ /// do something } label: { ZStack{ RoundedRectangle(cornerRadius: 45, style:.continuous) .fill(.red.gradient) .frame(width:85, height:85) Image(systemName: "waveform") .font(.title) .foregroundColor(.white) } } .buttonStyle(RecordButton()) Button(){ /// do something }label:{ HStack(spacing:4){ Text("Translate") Image(systemName: "arrow.forward") } } .buttonStyle(TranslateButton()) } .padding() } } } struct ChunkyButtonStyle: ButtonStyle { var offsetSize = 10.0 func makeBody(configuration: Configuration) -> some View { configuration.label .font(.system(size: 24, weight: .bold, design: .rounded)) .foregroundColor(.black) .padding() .offset(y: configuration.isPressed ? offsetSize-2 : 0) .background( GeometryReader{ geo in RoundedRectangle(cornerRadius: 17) .strokeBorder(Color.black, lineWidth: 4) .background( RoundedRectangle(cornerRadius: 17) .fill(.white) ) .offset(y:offsetSize) .overlay( RoundedRectangle(cornerRadius: 17) .fill(.white) .strokeBorder(Color.black, lineWidth: 4) .background(RoundedRectangle(cornerRadius: 17).fill(Color.white)) .offset(y: configuration.isPressed ? offsetSize : 0) ) } ) } } struct RecordButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .frame(maxWidth:80, maxHeight: 80) .shadow(color:.black.opacity(0.28), radius: 10, y:8) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) .animation(.easeOut(duration: 0.2), value: configuration.isPressed) .padding(.bottom, 40) } } struct TranslateButton: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.white) .padding(.vertical, 12) .padding(.horizontal, 16) .font(.system(size: 13, weight: .bold, design: .rounded)) .background(.teal.gradient) .clipShape(Capsule()) .brightness(configuration.isPressed ? -0.1 : 0.0) .scaleEffect(configuration.isPressed ? 0.9 : 1, anchor: .center) } } struct TranscriptionButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .foregroundColor(.secondary) .padding(.leading, 4) .padding(.trailing, 8) .padding(.vertical, 4) .background( Capsule().fill(.secondary.opacity(configuration.isPressed ? 0.28 : 0.14)) ) .scaleEffect(configuration.isPressed ? 0.9 : 1.0) } } #Preview { ButtonStyles() } ================================================ FILE: Demos/Translator/Translator/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Translator/Translator/TopTranslateView.swift ================================================ // // TopTranslateView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct TopTranslateView: View { @Binding var newText:String @Binding var translatedText:String @State private var showButton: Bool = false var translate: () -> Void var body: some View { VStack(alignment:.leading){ Text("English") .font(.callout) .foregroundColor(.secondary) TextField("Type something...", text: $newText, axis: .vertical) .font(.title2) .lineLimit(...2) .textFieldStyle(.plain) .frame(maxHeight: .infinity, alignment:.topLeading) .onChange(of: newText) { _, newValue in withAnimation(.bouncy){ if !newValue.isEmpty { showButton = true } else { showButton = false } } } if showButton { HStack(alignment:.bottom){ Button{ newText = "" translatedText = "" showButton = false } label:{ Text("Clear") } Spacer() Button{ self.translate() }label:{ HStack(spacing:4){ Text("Translate") Image(systemName: "arrow.forward") } } .buttonStyle(TranslateButton()) } .transition(.opacity) } } .frame(maxWidth: .infinity) .padding(16) .background( RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(Color(.tertiarySystemBackground)) .shadow(color:.black.opacity(0.14), radius: 1) ) } } #Preview { TopTranslateView(newText: .constant(""), translatedText: .constant(""), translate: {}) } ================================================ FILE: Demos/Translator/Translator/TranslateView.swift ================================================ // // TranslateView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI @MainActor struct TranslateView: View { @State private var newText:String = "" @State private var translatedText:String = "" @State private var processing:Bool = false private let prompt = "The response is an exact translation from english to spanish. You don't respond with any english." var body: some View { ZStack{ Color(.systemGroupedBackground) .ignoresSafeArea() VStack{ TopTranslateView( newText: $newText, translatedText: $translatedText, translate: { self.translate() } ) BottomTranslateView( processing: $processing, translatedText: $translatedText ) } .padding() } } func translate(){ withAnimation(.smooth){ processing = true } Task { translatedText = await TranslationDataLoader.run(on: self.newText) withAnimation(.smooth){ processing = false } } } } #Preview { TranslateView() } ================================================ FILE: Demos/Translator/Translator/TranslationDataLoader.swift ================================================ // // Translator.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation private let prompt = "The response is an exact translation from english to spanish. You don't respond with any english." /// Interfaces with OpenAI to translate input text from english to spanish struct TranslationDataLoader { private init() { fatalError("Translator is a namespace only") } /// Translate `input` from english to spanish /// - Parameter input: the english input /// - Returns: the spanish translation static func run(on input: String) async -> String { do { let response = try await AppConstants.openAIService.chatCompletionRequest(body: .init( model: "gpt-4o", messages: [ .system(content: .text(prompt)), .user(content: .text(input)) ] )) if let text = response.choices.first?.message.content { return text } } catch { AppLogger.error("Could not translate using gpt4o: \(error)") } return "Translation failed!" } } ================================================ FILE: Demos/Translator/Translator/TranslatorApp.swift ================================================ // // TranslatorApp.swift // Translator // // Created by Lou Zell // import SwiftUI @main struct TranslatorApp: App { var body: some Scene { WindowGroup { TranslateView() } } } ================================================ FILE: Demos/Translator/Translator.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 56; objects = { /* Begin PBXBuildFile section */ 0DDD23802B4DE34F00B2AE5C /* TranslatorApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD237F2B4DE34F00B2AE5C /* TranslatorApp.swift */; }; 0DDD23842B4DE35000B2AE5C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23832B4DE35000B2AE5C /* Assets.xcassets */; }; 0DDD23872B4DE35000B2AE5C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDD23862B4DE35000B2AE5C /* Preview Assets.xcassets */; }; 0DDD243C2B4DF06A00B2AE5C /* TopTranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24372B4DF06A00B2AE5C /* TopTranslateView.swift */; }; 0DDD243D2B4DF06A00B2AE5C /* BottomTranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24382B4DF06A00B2AE5C /* BottomTranslateView.swift */; }; 0DDD243F2B4DF06A00B2AE5C /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD243A2B4DF06A00B2AE5C /* TranslateView.swift */; }; 0DDD24422B4DF07600B2AE5C /* AppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24402B4DF07600B2AE5C /* AppLogger.swift */; }; 0DDD24432B4DF07600B2AE5C /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24412B4DF07600B2AE5C /* AppConstants.swift */; }; 0DDD24482B4DF0DD00B2AE5C /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDD24472B4DF0DD00B2AE5C /* ButtonStyles.swift */; }; 2CE7708D2C9B37A600EC6C74 /* AIProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 2CE7708C2C9B37A600EC6C74 /* AIProxy */; }; 2CE770922C9B459F00EC6C74 /* TranslationDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE770912C9B459800EC6C74 /* TranslationDataLoader.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0DDD237C2B4DE34F00B2AE5C /* Translator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Translator.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DDD237F2B4DE34F00B2AE5C /* TranslatorApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslatorApp.swift; sourceTree = ""; }; 0DDD23832B4DE35000B2AE5C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0DDD23862B4DE35000B2AE5C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 0DDD24372B4DF06A00B2AE5C /* TopTranslateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopTranslateView.swift; sourceTree = ""; }; 0DDD24382B4DF06A00B2AE5C /* BottomTranslateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomTranslateView.swift; sourceTree = ""; }; 0DDD243A2B4DF06A00B2AE5C /* TranslateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = ""; }; 0DDD24402B4DF07600B2AE5C /* AppLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLogger.swift; sourceTree = ""; }; 0DDD24412B4DF07600B2AE5C /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = ""; }; 0DDD24472B4DF0DD00B2AE5C /* ButtonStyles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = ""; }; 2CE770912C9B459800EC6C74 /* TranslationDataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationDataLoader.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 0DDD23792B4DE34F00B2AE5C /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2CE7708D2C9B37A600EC6C74 /* AIProxy in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0DDD23732B4DE34F00B2AE5C = { isa = PBXGroup; children = ( 0DDD237E2B4DE34F00B2AE5C /* Translator */, 0DDD237D2B4DE34F00B2AE5C /* Products */, ); sourceTree = ""; }; 0DDD237D2B4DE34F00B2AE5C /* Products */ = { isa = PBXGroup; children = ( 0DDD237C2B4DE34F00B2AE5C /* Translator.app */, ); name = Products; sourceTree = ""; }; 0DDD237E2B4DE34F00B2AE5C /* Translator */ = { isa = PBXGroup; children = ( 0DDD24412B4DF07600B2AE5C /* AppConstants.swift */, 0DDD24402B4DF07600B2AE5C /* AppLogger.swift */, 0DDD24382B4DF06A00B2AE5C /* BottomTranslateView.swift */, 0DDD24472B4DF0DD00B2AE5C /* ButtonStyles.swift */, 0DDD24372B4DF06A00B2AE5C /* TopTranslateView.swift */, 0DDD243A2B4DF06A00B2AE5C /* TranslateView.swift */, 2CE770912C9B459800EC6C74 /* TranslationDataLoader.swift */, 0DDD237F2B4DE34F00B2AE5C /* TranslatorApp.swift */, 0DDD23832B4DE35000B2AE5C /* Assets.xcassets */, 0DDD23852B4DE35000B2AE5C /* Preview Content */, ); path = Translator; sourceTree = ""; }; 0DDD23852B4DE35000B2AE5C /* Preview Content */ = { isa = PBXGroup; children = ( 0DDD23862B4DE35000B2AE5C /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 0DDD237B2B4DE34F00B2AE5C /* Translator */ = { isa = PBXNativeTarget; buildConfigurationList = 0DDD238A2B4DE35000B2AE5C /* Build configuration list for PBXNativeTarget "Translator" */; buildPhases = ( 0DDD23782B4DE34F00B2AE5C /* Sources */, 0DDD23792B4DE34F00B2AE5C /* Frameworks */, 0DDD237A2B4DE34F00B2AE5C /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Translator; packageProductDependencies = ( 2CE7708C2C9B37A600EC6C74 /* AIProxy */, ); productName = Translator; productReference = 0DDD237C2B4DE34F00B2AE5C /* Translator.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 0DDD23742B4DE34F00B2AE5C /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1510; LastUpgradeCheck = 1510; TargetAttributes = { 0DDD237B2B4DE34F00B2AE5C = { CreatedOnToolsVersion = 15.1; }; }; }; buildConfigurationList = 0DDD23772B4DE34F00B2AE5C /* Build configuration list for PBXProject "Translator" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 0DDD23732B4DE34F00B2AE5C; packageReferences = ( 2CE7708B2C9B37A600EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */, ); productRefGroup = 0DDD237D2B4DE34F00B2AE5C /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 0DDD237B2B4DE34F00B2AE5C /* Translator */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 0DDD237A2B4DE34F00B2AE5C /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD23872B4DE35000B2AE5C /* Preview Assets.xcassets in Resources */, 0DDD23842B4DE35000B2AE5C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 0DDD23782B4DE34F00B2AE5C /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 0DDD243C2B4DF06A00B2AE5C /* TopTranslateView.swift in Sources */, 0DDD243F2B4DF06A00B2AE5C /* TranslateView.swift in Sources */, 2CE770922C9B459F00EC6C74 /* TranslationDataLoader.swift in Sources */, 0DDD23802B4DE34F00B2AE5C /* TranslatorApp.swift in Sources */, 0DDD24432B4DF07600B2AE5C /* AppConstants.swift in Sources */, 0DDD24482B4DF0DD00B2AE5C /* ButtonStyles.swift in Sources */, 0DDD243D2B4DF06A00B2AE5C /* BottomTranslateView.swift in Sources */, 0DDD24422B4DF07600B2AE5C /* AppLogger.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 0DDD23882B4DE35000B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 0DDD23892B4DE35000B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.2; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; name = Release; }; 0DDD238B2B4DE35000B2AE5C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Translator/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.translator; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 0DDD238C2B4DE35000B2AE5C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Translator/Preview Content\""; DEVELOPMENT_TEAM = 9557YJ9D37; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.aiproxy.bootstrap.translator; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 0DDD23772B4DE34F00B2AE5C /* Build configuration list for PBXProject "Translator" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD23882B4DE35000B2AE5C /* Debug */, 0DDD23892B4DE35000B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 0DDD238A2B4DE35000B2AE5C /* Build configuration list for PBXNativeTarget "Translator" */ = { isa = XCConfigurationList; buildConfigurations = ( 0DDD238B2B4DE35000B2AE5C /* Debug */, 0DDD238C2B4DE35000B2AE5C /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 2CE7708B2C9B37A600EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/lzell/AIProxySwift"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 2CE7708C2C9B37A600EC6C74 /* AIProxy */ = { isa = XCSwiftPackageProductDependency; package = 2CE7708B2C9B37A600EC6C74 /* XCRemoteSwiftPackageReference "AIProxySwift" */; productName = AIProxy; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DDD23742B4DE34F00B2AE5C /* Project object */; } ================================================ FILE: Demos/Trivia/Trivia/AppConstants.swift ================================================ // // AppConstants.swift // AIProxyBootstrap // // Created by Lou Zell // import AIProxy enum AppConstants { #error( """ Uncomment one of the methods below. To build and run on device you must follow the AIProxy integration guide. Please see https://www.aiproxy.pro/docs/integration-guide.html") """ ) /* Uncomment for BYOK use cases */ static let openAIService = AIProxy.openAIDirectService( unprotectedAPIKey: "your-openai-key" ) /* Uncomment for all other production use cases */ // let openAIService = AIProxy.openAIService( // partialKey: "partial-key-from-your-developer-dashboard", // serviceURL: "service-url-from-your-developer-dashboard" // ) } ================================================ FILE: Demos/Trivia/Trivia/AppLogger.swift ================================================ // // AppLogger.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import OSLog /// Log levels available: /// /// AppLogger.debug /// AppLogger.info /// AppLogger.warning /// AppLogger.error /// AppLogger.critical /// /// Flip on metadata logging in Xcode's console to show which source line the log occurred from. /// /// See my reddit post for a video instructions: /// https://www.reddit.com/r/SwiftUI/comments/15lsdtk/how_to_use_the_oslog_logger/ let AppLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "UnknownApp", category: "AIProxyBootstrapTrivia") ================================================ FILE: Demos/Trivia/Trivia/Assets.xcassets/AccentColor.colorset/Contents.json ================================================ { "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Trivia/Trivia/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "filename" : "trivia.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Trivia/Trivia/Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Trivia/Trivia/Preview Content/Preview Assets.xcassets/Contents.json ================================================ { "info" : { "author" : "xcode", "version" : 1 } } ================================================ FILE: Demos/Trivia/Trivia/TriviaAnswerPicker.swift ================================================ // // TriviaAnswerPicker.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI struct TriviaAnswerPicker: View { /// Data model that holds the trivia question, potential answers, and correct answer index let questionModel: TriviaQuestionModel /// This question position in the stack of trivia cards let questionNumber: Int /// Number of questions in the stack of trivia cards let questionOf: Int /// The argument passed to this closure is the guessed answer index for comparison with `questionModel.correctAnswerIndex` let didTapAnswer: (Int) -> Void var body: some View { VStack(alignment:.leading, spacing:36) { VStack(alignment:.leading, spacing:16){ Text("Question \(questionNumber) of \(questionOf)") .font(.system(size: 15, weight:.medium, design: .rounded)) .foregroundColor(.secondary) Text(questionModel.question) .font(.system(size: 20, weight:.medium, design: .rounded)) .fixedSize(horizontal: false, vertical: true) } .frame(maxWidth: .infinity, alignment:.leading) VStack(alignment:.leading, spacing:8){ ForEach(questionModel.labeledAnswers) { labeledAnswer in Text(labeledAnswer.text) .fixedSize(horizontal: false, vertical: true) } } .font(.system(size: 17, weight:.medium, design: .rounded)) .frame(maxWidth: .infinity, alignment:.leading) VStack{ HStack(spacing:8) { CardButton(systemImageName: "a.circle.fill", tint: .blue) { didTapAnswer(0) } CardButton(systemImageName: "b.circle.fill", tint: .mint) { didTapAnswer(1) } } HStack { CardButton(systemImageName: "c.circle.fill", tint: .green) { didTapAnswer(2) } CardButton(systemImageName: "d.circle.fill", tint: .indigo) { didTapAnswer(3) } } } .buttonStyle(.bordered) .font(.title) .frame(maxWidth:.infinity, alignment:.leading) } .padding(16) } } private struct CardButton: View { let systemImageName: String let tint: Color let action: () -> Void var body: some View { Button(action: action) { Image(systemName: systemImageName) .frame(maxWidth: .infinity) } .tint(tint) .controlSize(.large) } } ================================================ FILE: Demos/Trivia/Trivia/TriviaApp.swift ================================================ // // TriviaApp.swift // Trivia // // Created by Lou Zell // import SwiftUI @main struct TriviaApp: App { var body: some Scene { WindowGroup { TriviaView() } } } ================================================ FILE: Demos/Trivia/Trivia/TriviaCardData.swift ================================================ // // TriviaQuestion.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI @MainActor @Observable /// UI model for TriviaCardView final class TriviaCardData: Identifiable { /// Position of the card, with 0 meaning that the card is on top and 1 meaning directly below the top card, etc. let position: Int /// Data model for the card contents var triviaQuestionModel: TriviaQuestionModel? /// Networker to load card contents from OpenAI private let triviaFetcher: TriviaDataLoader /// Creates a UI model for TriviaCardView /// - Parameters: /// - triviaFetcher: Loads card contents from OpenAI /// - position: Position of the card, with 0 being the top of the stack init(triviaFetcher: TriviaDataLoader, position: Int) { self.triviaFetcher = triviaFetcher self.position = position } /// Loads the trivia question's data model asynchronously func load() async { self.triviaQuestionModel = try! await self.triviaFetcher.getNextQuestion() } } ================================================ FILE: Demos/Trivia/Trivia/TriviaCardView.swift ================================================ // // QuizView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import Foundation import SwiftUI @MainActor struct TriviaCardView: View { let triviaCardData: TriviaCardData @Binding var triviaManager: TriviaManager? @State var attempts: Int = 0 @State var isCorrect = false private var questionNumber: Int { triviaCardData.position + 1 } private var totalQuestions: Int { triviaManager?.triviaCards.count ?? 0 } var body: some View{ ZStack { if let model = triviaCardData.triviaQuestionModel { ZStack { TriviaAnswerPicker( questionModel: model, questionNumber: questionNumber, questionOf: totalQuestions ) { guessIndex in checkAnswer(forQuestion: model, withGuessedIndex: guessIndex) } if self.isCorrect { Rectangle() .fill(.black.opacity(0.4)) .frame(width: .infinity, height: .infinity) .transition(.opacity) Image(systemName: "checkmark.circle.fill") .font(.system(size: 64)) .foregroundColor(.green) .background(.white) .clipShape(Circle()) .transition(.scale(0.5).combined(with: .opacity)) } } } else { VStack(spacing:16) { ProgressView() Text("Generating questions") .font(.system(size: 15, weight:.regular, design:.rounded)) .foregroundColor(.secondary) } .frame(maxHeight:.infinity) } } .frame(maxWidth: .infinity, maxHeight:480, alignment:.top) .background(Color(.systemBackground)) .cornerRadius(14) .shadow(color: .black.opacity(0.14), radius: 1, x: 0, y: 1) .modifier(Shake(animatableData: CGFloat(attempts))) } private func checkAnswer(forQuestion question: TriviaQuestionModel, withGuessedIndex guessedIndex: Int) { triviaManager?.trackGuess(ofQuestion: question) if (question.correctAnswerIndex == guessedIndex) { withAnimation(.bouncy){ isCorrect = true } Task { try await Task.sleep(for: .seconds(1)) withAnimation(.bouncy) { triviaManager?.progress() } } } else { withAnimation(.default) { attempts += 1 } } } } private struct Shake: GeometryEffect { var amount: CGFloat = 10 var shakesPerUnit = 3 var animatableData: CGFloat func effectValue(size: CGSize) -> ProjectionTransform { ProjectionTransform(CGAffineTransform(translationX: amount * sin(animatableData * .pi * CGFloat(shakesPerUnit)), y: 0)) } } ================================================ FILE: Demos/Trivia/Trivia/TriviaDataLoader.swift ================================================ // // TriviaFetcher.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import AIProxy // It's important to add the 'produce JSON' instruction to the system prompt. // See the note at https://platform.openai.com/docs/api-reference/chat/create#chat-create-response_format private let prompt = """ You are a trivia bot that produces JSON. You ask hard questions with four possible answers. Specifying the index of the correct answer in the key `correct_answer_index`. Example response: { question: "xyz", answers: ["a", "b", "c", "d"], correct_answer_index: 2 } """ /// Loads trivia data from openai final actor TriviaDataLoader { /// The topic of trivia let topic: String /// Create the TriviaDataLoader responsible for fetching trivia data from OpenAI /// - Parameter topic: The topic of trivia init(topic: String) { self.topic = topic } /// We store past questions, and send them back to openai on subsequent requests. /// This prevents chat from asking the same questions private var pastQuestions = [String]() deinit { AppLogger.debug("TriviaFetcher is being freed") } /// Fetches the next trivia question from OpenAI over the network /// - Returns: A TriviaQuestionModel containing one question and multiple choice answers func getNextQuestion() async throws -> TriviaQuestionModel { var messages = prompt if self.pastQuestions.count > 0 { let pastQuestionsList = self.pastQuestions.joined(separator: "\n\n") messages += "\nDo not repeat any of these questions: \(pastQuestionsList)" } let requestBody = OpenAIChatCompletionRequestBody( model: "gpt-4o", messages: [ .system(content: .text("Ask me a question about: \(topic)")), .user(content: .text(messages)) ], responseFormat: .jsonObject ) let response = try await AppConstants.openAIService.chatCompletionRequest(body: requestBody) guard let text = response.choices.first?.message.content else { throw TriviaFetcherError.couldNotFetchQuestion } let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase AppLogger.info("Received from openai: \(text)") let model = try decoder.decode(TriviaQuestionModel.self, from: text.data(using: .utf8)!) self.pastQuestions.append(model.question) return model } } enum TriviaFetcherError: Error { case couldNotFetchQuestion } struct TriviaQuestionModel: Decodable, Hashable { struct LabeledAnswer: Identifiable { let id = UUID() let text: String } let question: String let answers: [String] let correctAnswerIndex: Int var labeledAnswers: [LabeledAnswer] { return zip(["A", "B", "C", "D"], self.answers).map { LabeledAnswer(text: "\($0). \($1)") } } } ================================================ FILE: Demos/Trivia/Trivia/TriviaFormView.swift ================================================ // // TriviaFormView.swift // AIProxyBootstrap // // Created by Todd Hamilton // import SwiftUI struct TriviaFormView:View{ enum FocusedField { case topic } /// Topic entered by the user in a SwiftUI text field @State private var topic = "" @Binding var triviaManager: TriviaManager? @FocusState private var focusedField: FocusedField? var body: some View{ VStack(spacing:24){ VStack{ ZStack{ Image(systemName: "doc.questionmark.fill") .foregroundColor(.blue) .rotationEffect(.degrees(-15)) Image(systemName: "doc.questionmark.fill") .foregroundColor(.teal) .rotationEffect(.degrees(10)) Image(systemName: "doc.questionmark") .foregroundColor(.white) Image(systemName: "doc.questionmark.fill") .overlay { LinearGradient( colors: [.orange, .red, .purple], startPoint: .topLeading, endPoint: .bottomTrailing ) .mask( Image(systemName: "doc.questionmark.fill") .font(.system(size: 72)) ) } } .font(.system(size: 72)) .padding(.vertical, 8) Text("Trivia Generator") .font(.system(size: 36, weight:.bold, design: .rounded)) .multilineTextAlignment(.center) Text("Type a trivia theme below") .font(.system(size: 17, weight:.medium, design: .rounded)) .foregroundColor(.secondary) } VStack{ TextField("Ex. 80's movies...", text: $topic, axis: .vertical) .focused($focusedField, equals: .topic) .font(.system(size: 17, weight:.medium, design: .rounded)) .lineLimit(...3) .textFieldStyle(.plain) .padding() .background(.white) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .fill(.clear) .stroke(.separator) ) .onAppear { focusedField = .topic } Button{ withAnimation(){ triviaManager = TriviaManager(topic: topic, numCards: 5) } }label:{ Label("Generate", systemImage: "sparkles") .frame(maxWidth:.infinity) .font(.system(size: 17, weight:.bold, design: .rounded)) .fontWeight(.bold) } .buttonStyle(.borderedProminent) .controlSize(.large) } } .padding() } } ================================================ FILE: Demos/Trivia/Trivia/TriviaManager.swift ================================================ // // TriviaManager.swift // AIProxyBootstrap // // Created by Lou Zell // import Foundation import SwiftUI @MainActor @Observable final class TriviaManager { /// The topic of trivia let topic: String /// The number of cards for the user to solve let numCards: Int /// All trivia cards let triviaCards: [TriviaCardData] /// Observable of remaining cards that the user hasn't yet solved var remainingCards: [TriviaCardData] { return Array(self.triviaCards.suffix(from: self.currentCardIndex)) } /// Number of questions that were answered correctly on the first guess var numCorrectOnFirstGuess: Int { return self.guessTracker.filter { $0.value == 1 }.count } private var currentCardIndex: Int private let triviaDataLoader: TriviaDataLoader /// Tracks number of guesses before the right answer was reached. /// The key is the the question, the value is the number of guesses private var guessTracker = [TriviaQuestionModel: Int]() /// Creates a UI model for the TriviaView view /// - Parameters: /// - topic: The topic of trivia /// - numCards: The number of cards to display in the UI init(topic: String, numCards: Int) { let triviaFetcher = TriviaDataLoader(topic: topic) self.topic = topic self.numCards = numCards self.currentCardIndex = 0 self.triviaDataLoader = triviaFetcher self.triviaCards = (0..