Alt text + Hugging Face AI
This commit is contained in:
parent
3187e95eca
commit
d4fe9ce42f
|
@ -7,6 +7,8 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
B9029FC22B81259400AA9B68 /* Secret.plist in Resources */ = {isa = PBXBuildFile; fileRef = B9029FC12B81259400AA9B68 /* Secret.plist */; };
|
||||||
|
B9029FC42B8125CE00AA9B68 /* HuggingFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */; };
|
||||||
B915C4422B6F908C00042DDB /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B915C4412B6F908C00042DDB /* ProfileView.swift */; };
|
B915C4422B6F908C00042DDB /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B915C4412B6F908C00042DDB /* ProfileView.swift */; };
|
||||||
B93757112B7FB8D400652F91 /* AltClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93757102B7FB8D400652F91 /* AltClients.swift */; };
|
B93757112B7FB8D400652F91 /* AltClients.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93757102B7FB8D400652F91 /* AltClients.swift */; };
|
||||||
B93ADFCB2B7625CD00FF9172 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93ADFCA2B7625CD00FF9172 /* DiscoveryView.swift */; };
|
B93ADFCB2B7625CD00FF9172 /* DiscoveryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B93ADFCA2B7625CD00FF9172 /* DiscoveryView.swift */; };
|
||||||
|
@ -117,6 +119,8 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
B9029FC12B81259400AA9B68 /* Secret.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Secret.plist; sourceTree = "<group>"; };
|
||||||
|
B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFace.swift; sourceTree = "<group>"; };
|
||||||
B915C4412B6F908C00042DDB /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
B915C4412B6F908C00042DDB /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
B93757102B7FB8D400652F91 /* AltClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltClients.swift; sourceTree = "<group>"; };
|
B93757102B7FB8D400652F91 /* AltClients.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltClients.swift; sourceTree = "<group>"; };
|
||||||
B93ADFCA2B7625CD00FF9172 /* DiscoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryView.swift; sourceTree = "<group>"; };
|
B93ADFCA2B7625CD00FF9172 /* DiscoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoveryView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -298,6 +302,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
B9FB94A02B2EF23100D81C07 /* Info.plist */,
|
||||||
|
B9029FC12B81259400AA9B68 /* Secret.plist */,
|
||||||
B9FB945A2B2DEECE00D81C07 /* ThreadedApp.swift */,
|
B9FB945A2B2DEECE00D81C07 /* ThreadedApp.swift */,
|
||||||
B9EBE8572B474FD600FB594D /* AppDelegate.swift */,
|
B9EBE8572B474FD600FB594D /* AppDelegate.swift */,
|
||||||
B9FB946E2B2DF3BB00D81C07 /* Components */,
|
B9FB946E2B2DF3BB00D81C07 /* Components */,
|
||||||
|
@ -328,6 +333,7 @@
|
||||||
B9D9C6BF2B6A56D500C26A41 /* Notifications */,
|
B9D9C6BF2B6A56D500C26A41 /* Notifications */,
|
||||||
B9FB946F2B2DF3CD00D81C07 /* Navigator.swift */,
|
B9FB946F2B2DF3CD00D81C07 /* Navigator.swift */,
|
||||||
B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */,
|
B9FB94A12B2EF24A00D81C07 /* AppInfo.swift */,
|
||||||
|
B9029FC32B8125CE00AA9B68 /* HuggingFace.swift */,
|
||||||
B98BC74C2B46CFCE00595441 /* UserPreferences.swift */,
|
B98BC74C2B46CFCE00595441 /* UserPreferences.swift */,
|
||||||
B9FB94872B2E223E00D81C07 /* Emoji.swift */,
|
B9FB94872B2E223E00D81C07 /* Emoji.swift */,
|
||||||
B9FB949A2B2EF09A00D81C07 /* Client.swift */,
|
B9FB949A2B2EF09A00D81C07 /* Client.swift */,
|
||||||
|
@ -525,6 +531,7 @@
|
||||||
B9DC69302B79378400E625B9 /* ThreadedPlus.storekit in Resources */,
|
B9DC69302B79378400E625B9 /* ThreadedPlus.storekit in Resources */,
|
||||||
B9FB94642B2DEECF00D81C07 /* Preview Assets.xcassets in Resources */,
|
B9FB94642B2DEECF00D81C07 /* Preview Assets.xcassets in Resources */,
|
||||||
B9FB94612B2DEECF00D81C07 /* Assets.xcassets in Resources */,
|
B9FB94612B2DEECF00D81C07 /* Assets.xcassets in Resources */,
|
||||||
|
B9029FC22B81259400AA9B68 /* Secret.plist in Resources */,
|
||||||
B9FB94902B2E2B0E00D81C07 /* Localizable.xcstrings in Resources */,
|
B9FB94902B2E2B0E00D81C07 /* Localizable.xcstrings in Resources */,
|
||||||
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */,
|
B9CFC43B2B4F08C9004CFCB7 /* LaunchStoryboard.storyboard in Resources */,
|
||||||
B97BCE2A2B3ED2C80044756D /* LICENSE in Resources */,
|
B97BCE2A2B3ED2C80044756D /* LICENSE in Resources */,
|
||||||
|
@ -582,6 +589,7 @@
|
||||||
B97491E32B6E96700098BC48 /* SymbolWidth.swift in Sources */,
|
B97491E32B6E96700098BC48 /* SymbolWidth.swift in Sources */,
|
||||||
B9DC692B2B78E60300E625B9 /* PostsView.swift in Sources */,
|
B9DC692B2B78E60300E625B9 /* PostsView.swift in Sources */,
|
||||||
B9FB94BC2B2F035500D81C07 /* Tag.swift in Sources */,
|
B9FB94BC2B2F035500D81C07 /* Tag.swift in Sources */,
|
||||||
|
B9029FC42B8125CE00AA9B68 /* HuggingFace.swift in Sources */,
|
||||||
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */,
|
B9BED51A2B5D662D00C9B715 /* ShareSheetController.swift in Sources */,
|
||||||
B93757112B7FB8D400652F91 /* AltClients.swift in Sources */,
|
B93757112B7FB8D400652F91 /* AltClients.swift in Sources */,
|
||||||
B9BED5162B5D5E6500C9B715 /* PostInteractor.swift in Sources */,
|
B9BED5162B5D5E6500C9B715 /* PostInteractor.swift in Sources */,
|
||||||
|
|
|
@ -1,3 +1,54 @@
|
||||||
//Made by Lumaa
|
//Made by Lumaa
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
final class HuggingFace: ObservableObject {
|
||||||
|
static var token: String = ""
|
||||||
|
static let altGenUrl: URL = URL(string: "https://api-inference.huggingface.co/models/Salesforce/blip-image-captioning-large")!
|
||||||
|
|
||||||
|
var lastImgGeneration: String? = nil
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.lastImgGeneration = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getToken() -> String? {
|
||||||
|
guard let path = Bundle.main.path(forResource: "Secret", ofType: "plist") else { return nil }
|
||||||
|
let url = URL(fileURLWithPath: path)
|
||||||
|
let data = try! Data(contentsOf: url)
|
||||||
|
guard let plist = try! PropertyListSerialization.propertyList(from: data, options: .mutableContainers, format: nil) as? [String: String] else { return nil }
|
||||||
|
Self.token = plist["AI_Token"] ?? ""
|
||||||
|
return Self.token
|
||||||
|
}
|
||||||
|
|
||||||
|
func altGeneration(image: UIImage) -> String? {
|
||||||
|
if let imageData = image.jpegData(compressionQuality: 0.5) {
|
||||||
|
let base64Image = imageData.base64EncodedString()
|
||||||
|
let parameters = ["image": base64Image]
|
||||||
|
|
||||||
|
let headers = ["Authorization": "Bearer \(Self.token)"]
|
||||||
|
var request = URLRequest(url: Self.altGenUrl)
|
||||||
|
request.httpMethod = "POST"
|
||||||
|
request.allHTTPHeaderFields = headers
|
||||||
|
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
|
||||||
|
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
var jsonResponse: [[String: Any]]?
|
||||||
|
|
||||||
|
URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||||
|
defer { semaphore.signal() }
|
||||||
|
if let data = data {
|
||||||
|
jsonResponse = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
|
||||||
|
print(jsonResponse?[0]["generated_text"] ?? "idfk")
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
|
||||||
|
semaphore.wait()
|
||||||
|
return (jsonResponse?[0]["generated_text"] as! String)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -983,6 +983,86 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"posting.alt.apply" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Apply"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Appliquer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"posting.alt.cancel" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Annuler"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"posting.alt.generate" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Generate using an AI"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Générer en utilisant une IA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"posting.alt.header" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Alt text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Texte Alt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"posting.alt.prompt" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Describe the image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Décrivez l'image"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"setting.appearence" : {
|
"setting.appearence" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import SwiftUI
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
|
||||||
|
|
||||||
|
private var huggingFace: HuggingFace = HuggingFace()
|
||||||
@State private var preferences: UserPreferences = .defaultPreferences
|
@State private var preferences: UserPreferences = .defaultPreferences
|
||||||
@StateObject private var uniNavigator = UniversalNavigator()
|
@StateObject private var uniNavigator = UniversalNavigator()
|
||||||
@StateObject private var accountManager: AccountManager = AccountManager.shared
|
@StateObject private var accountManager: AccountManager = AccountManager.shared
|
||||||
|
@ -50,6 +51,7 @@ struct ContentView: View {
|
||||||
.environment(uniNavigator)
|
.environment(uniNavigator)
|
||||||
.environment(accountManager)
|
.environment(accountManager)
|
||||||
.environment(appDelegate)
|
.environment(appDelegate)
|
||||||
|
.environment(huggingFace)
|
||||||
.environmentObject(preferences)
|
.environmentObject(preferences)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -64,6 +66,8 @@ struct ContentView: View {
|
||||||
await recognizeAccount()
|
await recognizeAccount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = HuggingFace.getToken()
|
||||||
}
|
}
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
// Open internal URL.
|
// Open internal URL.
|
||||||
|
|
|
@ -20,10 +20,12 @@ struct PostingView: View {
|
||||||
|
|
||||||
@State private var selectingPhotos: Bool = false
|
@State private var selectingPhotos: Bool = false
|
||||||
@State private var mediaContainers: [MediaContainer] = []
|
@State private var mediaContainers: [MediaContainer] = []
|
||||||
|
@State private var mediaAttributes: [StatusData.MediaAttribute] = []
|
||||||
@State private var selectedPhotos: [PhotosPickerItem] = []
|
@State private var selectedPhotos: [PhotosPickerItem] = []
|
||||||
@State private var player: AVPlayer?
|
@State private var player: AVPlayer?
|
||||||
|
|
||||||
@State private var selectingEmoji: Bool = false
|
@State private var selectingEmoji: Bool = false
|
||||||
|
@State private var makingAlt: MediaContainer? = nil
|
||||||
|
|
||||||
@State private var loadingContent: Bool = false
|
@State private var loadingContent: Bool = false
|
||||||
@State private var postingStatus: Bool = false
|
@State private var postingStatus: Bool = false
|
||||||
|
@ -44,6 +46,11 @@ struct PostingView: View {
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
.presentationBackgroundInteraction(.enabled(upThrough: .height(200))) // Allow users to move the cursor while adding emojis
|
.presentationBackgroundInteraction(.enabled(upThrough: .height(200))) // Allow users to move the cursor while adding emojis
|
||||||
}
|
}
|
||||||
|
.sheet(item: $makingAlt) { container in
|
||||||
|
AltTextView(container: container, mediaContainers: $mediaContainers, mediaAttributes: $mediaAttributes)
|
||||||
|
.presentationDetents([.height(235), .medium])
|
||||||
|
.presentationDragIndicator(.visible)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
loading
|
loading
|
||||||
.background(Color.appBackground)
|
.background(Color.appBackground)
|
||||||
|
@ -76,7 +83,7 @@ struct PostingView: View {
|
||||||
.foregroundStyle(Color(uiColor: UIColor.label))
|
.foregroundStyle(Color(uiColor: UIColor.label))
|
||||||
|
|
||||||
if !mediaContainers.isEmpty {
|
if !mediaContainers.isEmpty {
|
||||||
mediasView(containers: mediaContainers) //TODO: ALT text
|
mediasView(containers: mediaContainers)
|
||||||
}
|
}
|
||||||
|
|
||||||
editorButtons
|
editorButtons
|
||||||
|
@ -199,13 +206,13 @@ struct PostingView: View {
|
||||||
private let containerHeight: CGFloat = 450
|
private let containerHeight: CGFloat = 450
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func mediasView(containers: [MediaContainer]) -> some View {
|
private func mediasView(containers: [MediaContainer], actions: Bool = true) -> some View {
|
||||||
ViewThatFits {
|
ViewThatFits {
|
||||||
hMedias(containers)
|
hMedias(containers, actions: actions)
|
||||||
.frame(maxHeight: containerHeight)
|
.frame(maxHeight: containerHeight)
|
||||||
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
hMedias(containers)
|
hMedias(containers, actions: actions)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: containerHeight)
|
.frame(maxHeight: containerHeight)
|
||||||
.scrollClipDisabled()
|
.scrollClipDisabled()
|
||||||
|
@ -213,7 +220,7 @@ struct PostingView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func hMedias(_ containers: [MediaContainer]) -> some View {
|
private func hMedias(_ containers: [MediaContainer], actions: Bool) -> some View {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
||||||
ForEach(containers) { container in
|
ForEach(containers) { container in
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
|
@ -237,15 +244,31 @@ struct PostingView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
Button {
|
if actions {
|
||||||
deleteAction(container: container)
|
Button {
|
||||||
} label: {
|
deleteAction(container: container)
|
||||||
Image(systemName: "xmark")
|
} label: {
|
||||||
.font(.subheadline)
|
Image(systemName: "xmark")
|
||||||
.padding(10)
|
.font(.subheadline)
|
||||||
.background(Material.ultraThick)
|
.padding(10)
|
||||||
.clipShape(Circle())
|
.background(Material.ultraThick)
|
||||||
.padding(5)
|
.clipShape(Circle())
|
||||||
|
.padding(5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay(alignment: .topLeading) {
|
||||||
|
if actions && container.mediaAttachment != nil {
|
||||||
|
Button {
|
||||||
|
makingAlt = container
|
||||||
|
} label: {
|
||||||
|
Text(String("ALT"))
|
||||||
|
.font(.subheadline.smallCaps())
|
||||||
|
.padding(7.5)
|
||||||
|
.background(Material.ultraThick)
|
||||||
|
.clipShape(Capsule())
|
||||||
|
.padding(5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,7 +397,6 @@ struct PostingView: View {
|
||||||
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
mediaContainers.removeAll { removedIDs.contains($0.id) }
|
||||||
|
|
||||||
let newPickerItems = selectedPhotos.filter { !oldValue.contains($0) }
|
let newPickerItems = selectedPhotos.filter { !oldValue.contains($0) }
|
||||||
print("newPickerItems: \(newPickerItems.count)")
|
|
||||||
if !newPickerItems.isEmpty {
|
if !newPickerItems.isEmpty {
|
||||||
loadingContent = true
|
loadingContent = true
|
||||||
Task {
|
Task {
|
||||||
|
@ -564,25 +586,6 @@ struct PostingView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDescription(container: PostingView.MediaContainer, description: String) async {
|
|
||||||
guard let client = accountManager.getClient(), let attachment = container.mediaAttachment else { return }
|
|
||||||
if let index = indexOf(container: container) {
|
|
||||||
do {
|
|
||||||
let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id,
|
|
||||||
json: .init(description: description)))
|
|
||||||
mediaContainers[index] = MediaContainer(id: container.id, image: nil, movieTransferable: nil, gifTransferable: nil, mediaAttachment: media, error: nil)
|
|
||||||
} catch { print(error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mediaAttributes: [StatusData.MediaAttribute] = []
|
|
||||||
mutating func editDescription(container: PostingView.MediaContainer, description: String) async {
|
|
||||||
guard let attachment = container.mediaAttachment else { return }
|
|
||||||
if indexOf(container: container) != nil {
|
|
||||||
mediaAttributes.append(StatusData.MediaAttribute(id: attachment.id, description: description, thumbnail: nil, focus: nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func uploadMedia(data: Data, mimeType: String) async throws -> MediaAttachment? {
|
private func uploadMedia(data: Data, mimeType: String) async throws -> MediaAttachment? {
|
||||||
guard let client = accountManager.getClient() else { return nil }
|
guard let client = accountManager.getClient() else { return nil }
|
||||||
return try await client.mediaUpload(endpoint: Media.medias, version: .v2, method: "POST", mimeType: mimeType, filename: "file", data: data)
|
return try await client.mediaUpload(endpoint: Media.medias, version: .v2, method: "POST", mimeType: mimeType, filename: "file", data: data)
|
||||||
|
@ -650,4 +653,143 @@ extension PostingView {
|
||||||
let mediaAttachment: MediaAttachment?
|
let mediaAttachment: MediaAttachment?
|
||||||
let error: Error?
|
let error: Error?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AltTextView: View {
|
||||||
|
@Environment(AccountManager.self) private var accountManager: AccountManager
|
||||||
|
@Environment(HuggingFace.self) private var huggingFace: HuggingFace
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
var container: MediaContainer
|
||||||
|
@Binding var mediaContainers: [MediaContainer]
|
||||||
|
@Binding var mediaAttributes: [StatusData.MediaAttribute]
|
||||||
|
|
||||||
|
@State private var tasking: Bool = false
|
||||||
|
@State private var applying: Bool = false
|
||||||
|
@State private var alt: String = ""
|
||||||
|
@FocusState private var altFocused: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
TextField(String(""), text: $alt, prompt: Text("posting.alt.prompt"), axis: .vertical)
|
||||||
|
.labelsHidden()
|
||||||
|
.keyboardType(.asciiCapable)
|
||||||
|
.focused($altFocused)
|
||||||
|
|
||||||
|
Button {
|
||||||
|
tasking = true
|
||||||
|
let img = container.image
|
||||||
|
if img == nil, let media = container.mediaAttachment {
|
||||||
|
guard media.supportedType == .image else { return }
|
||||||
|
downloadImage(from: media.url ?? URL(string: "https://cdn.pixabay.com/photo/2023/08/28/20/32/flower-8220018_1280.jpg")!) { image in
|
||||||
|
if let uiimage = image {
|
||||||
|
alt = huggingFace.altGeneration(image: uiimage) ?? ""
|
||||||
|
tasking = false
|
||||||
|
} else {
|
||||||
|
print("Couldn't download image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alt = huggingFace.altGeneration(image: img!) ?? ""
|
||||||
|
tasking = false
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if !tasking {
|
||||||
|
Label("posting.alt.generate", systemImage: "printer")
|
||||||
|
.foregroundStyle(Color.blue)
|
||||||
|
} else {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
|
.foregroundStyle(Color(uiColor: UIColor.label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tint(tasking ? Color(uiColor: UIColor.label) : Color.blue)
|
||||||
|
.disabled(tasking)
|
||||||
|
}
|
||||||
|
.navigationTitle(Text("posting.alt.header"))
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
Button {
|
||||||
|
applying = true
|
||||||
|
if let mediaAttachment = container.mediaAttachment {
|
||||||
|
if let str = mediaAttachment.description, !str.isEmpty {
|
||||||
|
Task {
|
||||||
|
await editDescription(container: container, description: alt)
|
||||||
|
applying = false
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Task {
|
||||||
|
await addDescription(container: container, description: alt)
|
||||||
|
applying = false
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if applying {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
|
} else {
|
||||||
|
Text("posting.alt.apply")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button {
|
||||||
|
dismiss()
|
||||||
|
} label: {
|
||||||
|
Text("posting.alt.cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
altFocused = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func downloadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
|
||||||
|
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
|
guard let data = data, error == nil else {
|
||||||
|
completion(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(UIImage(data: data))
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addDescription(container: MediaContainer, description: String) async {
|
||||||
|
guard let client = accountManager.getClient(), let attachment = container.mediaAttachment else { return }
|
||||||
|
if let index = indexOf(container: container) {
|
||||||
|
do {
|
||||||
|
let media: MediaAttachment = try await client.put(endpoint: Media.media(id: attachment.id,
|
||||||
|
json: .init(description: description)))
|
||||||
|
mediaContainers[index] = MediaContainer(
|
||||||
|
id: container.id,
|
||||||
|
image: nil,
|
||||||
|
movieTransferable: nil,
|
||||||
|
gifTransferable: nil,
|
||||||
|
mediaAttachment: media,
|
||||||
|
error: nil
|
||||||
|
)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func editDescription(container: MediaContainer, description: String) async {
|
||||||
|
guard let attachment = container.mediaAttachment else { return }
|
||||||
|
if indexOf(container: container) != nil {
|
||||||
|
mediaAttributes.append(StatusData.MediaAttribute(id: attachment.id, description: description, thumbnail: nil, focus: nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func indexOf(container: MediaContainer) -> Int? {
|
||||||
|
mediaContainers.firstIndex(where: { $0.id == container.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue