From 5ef6c5e342d1d49948e143e005ea132a27c0d2f9 Mon Sep 17 00:00:00 2001 From: Maxime Marinel Date: Mon, 22 Jan 2024 08:07:46 +0100 Subject: [PATCH 1/4] Stash --- .gitignore | 2 + .swift-version | 2 +- App/Configuration.storekit | 33 ---- App/Entity/Entry.swift | 1 + App/Features/AI/ChatAssistant.swift | 56 +++++++ App/Features/AI/SynthesisEntryView.swift | 57 +++++++ App/Features/AI/TagSuggestionView.swift | 78 +++++++++ App/Features/Entry/EntriesView.swift | 4 + App/Features/Entry/EntryView.swift | 14 ++ App/Features/Entry/Picture/ImageCache.swift | 4 +- App/Features/Router/Route.swift | 3 + .../Router/RouteSwiftUIExtension.swift | 12 +- App/Features/Sync/AppSync.swift | 20 +-- .../WallabagPlusProtectedModifier.swift | 25 +++ .../WallabagPlus/WallabagPlusStore.swift | 60 +++++++ .../WallabagPlusSubscribeView.swift | 39 +++++ .../WallabagPlus/WallabagPlusView.swift | 56 +++++++ App/Info.plist | 6 + App/Lib/DependencyInjection.swift | 13 ++ App/Lib/WallabagError.swift | 4 +- App/PropertyWrapper/BundleKey.swift | 2 +- App/WallabagApp.swift | 10 +- App/WallabagStoreKit.storekit | 124 ++++++++++++++ Dangerfile.swift | 1 + .../Features/RetrieveMode/RetrieveMode.swift | 16 +- .../Endpoint/WallabagEntryEndpoint.swift | 18 +-- .../WallabagKit/Endpoint/WallabagOAuth.swift | 4 +- bagit/ShareExtensionError.swift | 8 +- fastlane/SnapshotHelper.swift | 4 +- openapi-generator-config.yaml | 7 + openapi.yaml | 119 ++++++++++++++ wallabag.xcodeproj/project.pbxproj | 153 ++++++++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 60 ++++--- .../xcshareddata/xcschemes/bagit.xcscheme | 2 +- .../xcshareddata/xcschemes/wallabag.xcscheme | 4 +- .../xcshareddata/swiftpm/Package.resolved | 92 ++++++++++- 36 files changed, 984 insertions(+), 129 deletions(-) delete mode 100644 App/Configuration.storekit create mode 100644 App/Features/AI/ChatAssistant.swift create mode 100644 App/Features/AI/SynthesisEntryView.swift create mode 100644 App/Features/AI/TagSuggestionView.swift create mode 100644 App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift create mode 100644 App/Features/WallabagPlus/WallabagPlusStore.swift create mode 100644 App/Features/WallabagPlus/WallabagPlusSubscribeView.swift create mode 100644 App/Features/WallabagPlus/WallabagPlusView.swift create mode 100644 App/WallabagStoreKit.storekit create mode 100644 openapi-generator-config.yaml create mode 100644 openapi.yaml diff --git a/.gitignore b/.gitignore index 5d35aee6..be42ae07 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,5 @@ fastlane/metadata/trade_representative_contact_information vendor/ .bundle/ + +Config.xcconfig \ No newline at end of file diff --git a/.swift-version b/.swift-version index 3659ea2f..95ee81a4 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.8 +5.9 diff --git a/App/Configuration.storekit b/App/Configuration.storekit deleted file mode 100644 index 089c7f6c..00000000 --- a/App/Configuration.storekit +++ /dev/null @@ -1,33 +0,0 @@ -{ - "identifier" : "98370847", - "nonRenewingSubscriptions" : [ - - ], - "products" : [ - { - "displayPrice" : "0.99", - "familyShareable" : false, - "internalID" : "FE8C892D", - "localizations" : [ - { - "description" : "tips1", - "displayName" : "tips1", - "locale" : "en_US" - } - ], - "productID" : "tips1", - "referenceName" : "tips1", - "type" : "Consumable" - } - ], - "settings" : { - - }, - "subscriptionGroups" : [ - - ], - "version" : { - "major" : 1, - "minor" : 1 - } -} diff --git a/App/Entity/Entry.swift b/App/Entity/Entry.swift index bc0cf6fa..ff7756e7 100644 --- a/App/Entity/Entry.swift +++ b/App/Entity/Entry.swift @@ -2,6 +2,7 @@ import CoreData import CoreSpotlight import Foundation import SharedLib + // import MobileCoreServices import WallabagKit diff --git a/App/Features/AI/ChatAssistant.swift b/App/Features/AI/ChatAssistant.swift new file mode 100644 index 00000000..4d8c7e74 --- /dev/null +++ b/App/Features/AI/ChatAssistant.swift @@ -0,0 +1,56 @@ +import Foundation +import HTTPTypes +import OpenAPIRuntime +import OpenAPIURLSession + +protocol ChatAssistantProtocol { + func generateSynthesis(content: String) async throws -> String + func generateTags(content: String) async throws -> [String] +} + +struct ChatAssistant: ChatAssistantProtocol { + var client: Client { + get throws { + try Client( + serverURL: Servers.server1(), + transport: URLSessionTransport(), + middlewares: [AuthenticationMiddleware()] + ) + } + } + + var locale = Locale.current.identifier + + func generateSynthesis(content: String) async throws -> String { + let response = try await client.wallabagSynthesis(body: .json(.init(body: content, language: locale))) + + return try response.ok.body.json.content ?? "" + } + + func generateTags(content: String) async throws -> [String] { + let response = try await client.wallabagTags(body: .json(.init(body: content, language: locale))) + + return try response.ok.body.json.tags ?? [] + } +} + +private struct AuthenticationMiddleware: ClientMiddleware { + @BundleKey("GPTBACK_KEY") + private var gptBackKey + + func intercept( + _ request: HTTPTypes.HTTPRequest, + body: OpenAPIRuntime.HTTPBody?, + baseURL: URL, + operationID _: String, + next: @Sendable (HTTPTypes.HTTPRequest, OpenAPIRuntime.HTTPBody?, URL) async throws -> ( + HTTPTypes.HTTPResponse, + OpenAPIRuntime.HTTPBody? + ) + ) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?) { + var request = request + request.headerFields[.authorization] = "Bearer \(gptBackKey)" + + return try await next(request, body, baseURL) + } +} diff --git a/App/Features/AI/SynthesisEntryView.swift b/App/Features/AI/SynthesisEntryView.swift new file mode 100644 index 00000000..207bf38c --- /dev/null +++ b/App/Features/AI/SynthesisEntryView.swift @@ -0,0 +1,57 @@ +// +// SynthesisEntryView.swift +// wallabag +// +// Created by maxime marinel on 11/12/2023. +// + +import Factory +import SwiftUI + +final class SynthesisEntryViewModel: ObservableObject { + @Injected(\.chatAssistant) private var chatAssistant + @Published var synthesis = "" + @Published var isLoading = false + + @MainActor + func generateSynthesis(from entry: Entry) async throws { + defer { + isLoading = false + } + isLoading = true + + guard let content = entry.content?.withoutHTML else { return } + + synthesis = try await chatAssistant.generateSynthesis(content: content) + } +} + +struct SynthesisEntryView: View { + @StateObject private var viewModel = SynthesisEntryViewModel() + let entry: Entry + + var body: some View { + ScrollView { + if viewModel.isLoading { + Text("Your assistant is working") + ProgressView() + } else { + Text(viewModel.synthesis) + .padding() + .fontDesign(.serif) + } + } + .navigationTitle("Synthesis") + .task { + do { + try await viewModel.generateSynthesis(from: entry) + } catch { + print(error) + } + } + } +} + +// #Preview { +// SynthesisEntryView(entry: .) +// } diff --git a/App/Features/AI/TagSuggestionView.swift b/App/Features/AI/TagSuggestionView.swift new file mode 100644 index 00000000..a9c75756 --- /dev/null +++ b/App/Features/AI/TagSuggestionView.swift @@ -0,0 +1,78 @@ +import Factory +import SwiftUI + +final class TagSuggestionViewModel: ObservableObject { + @Injected(\.chatAssistant) private var chatAssistant + @Published var suggestions: [String] = [] + @Published var isLoading = false + + @MainActor + func generateSynthesis(from entry: Entry) async throws { + defer { + isLoading = false + } + isLoading = true + + guard let content = entry.content?.withoutHTML else { return } + + suggestions = try await chatAssistant.generateTags(content: content) + } +} + +struct TagSuggestionView: View { + @StateObject private var viewModel = TagSuggestionViewModel() + @State private var selection = Set() + + let entry: Entry + + var body: some View { + VStack { + if viewModel.isLoading { + Text("Your assistant is working") + ProgressView() + } else { + List { + ForEach(viewModel.suggestions, id: \.self) { suggestion in + Button(action: { + if selection.contains(suggestion) { + selection.remove(suggestion) + } else { + selection.insert(suggestion) + } + }, label: { + HStack { + Text(suggestion) + .padding() + .fontDesign(.serif) + Spacer() + if selection.contains(suggestion) { + Image(systemName: "checkmark.circle.fill") + } else { + Image(systemName: "circle") + } + } + }) + } + } + .listStyle(.plain) + if !selection.isEmpty { + Button(action: {}, label: { + Text("Add \(selection.count.formatted()) tags") + }) + } + } + } + .navigationTitle("Tag suggestion") + .task { + do { + try await viewModel.generateSynthesis(from: entry) + } catch { + print(error) + } + } + } +} + +// #Preview { +// SynthesisEntryView(entry: .) +// } diff --git a/App/Features/Entry/EntriesView.swift b/App/Features/Entry/EntriesView.swift index 3b5befc1..41268ed1 100644 --- a/App/Features/Entry/EntriesView.swift +++ b/App/Features/Entry/EntriesView.swift @@ -46,6 +46,10 @@ struct EntriesView: View { Label("Don", systemImage: "heart") }) Divider() + NavigationLink(value: RoutePath.wallabagPlus) { + Label("wallabag Plus", systemImage: "hands.and.sparkles") + } + Divider() Button(action: { router.path.append(RoutePath.setting) }, label: { diff --git a/App/Features/Entry/EntryView.swift b/App/Features/Entry/EntryView.swift index 2be5793a..4b5aaa6a 100644 --- a/App/Features/Entry/EntryView.swift +++ b/App/Features/Entry/EntryView.swift @@ -52,6 +52,20 @@ struct EntryView: View { FontSizeSelectorView() .buttonStyle(.plain) } + ToolbarItem(placement: toolbarPlacement) { + Menu(content: { + NavigationLink(value: RoutePath.synthesis(entry), label: { + Text("Synthesis") + }) + NavigationLink(value: RoutePath.tags(entry), label: { + Text("Suggest tag") + }) + }, label: { + Label("Help assistant", systemImage: "hands.and.sparkles") + .foregroundColor(.primary) + .labelStyle(.iconOnly) + }) + } } .actionSheet(isPresented: $showDeleteConfirm) { ActionSheet( diff --git a/App/Features/Entry/Picture/ImageCache.swift b/App/Features/Entry/Picture/ImageCache.swift index 656e9be6..cecf5e86 100644 --- a/App/Features/Entry/Picture/ImageCache.swift +++ b/App/Features/Entry/Picture/ImageCache.swift @@ -60,8 +60,8 @@ import Foundation let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] do { let files = try FileManager.default.contentsOfDirectory(atPath: url.path) - try files.forEach { - try FileManager.default.removeItem(atPath: url.appendingPathComponent($0).path) + for file in files { + try FileManager.default.removeItem(atPath: url.appendingPathComponent(file).path) } } catch { print("Error in cache purge") diff --git a/App/Features/Router/Route.swift b/App/Features/Router/Route.swift index 65f71e92..35de8d71 100644 --- a/App/Features/Router/Route.swift +++ b/App/Features/Router/Route.swift @@ -5,7 +5,10 @@ enum RoutePath: Hashable { case registration case addEntry case entry(Entry) + case synthesis(Entry) + case tags(Entry) case tips case about case setting + case wallabagPlus } diff --git a/App/Features/Router/RouteSwiftUIExtension.swift b/App/Features/Router/RouteSwiftUIExtension.swift index 49944905..0a93200f 100644 --- a/App/Features/Router/RouteSwiftUIExtension.swift +++ b/App/Features/Router/RouteSwiftUIExtension.swift @@ -16,14 +16,22 @@ extension View { AddEntryView() case let .entry(entry): EntryView(entry: entry) + case let .synthesis(entry): + SynthesisEntryView(entry: entry) + .wallabagPlusProtected() + case let .tags(entry): + TagSuggestionView(entry: entry) + .wallabagPlusProtected() case .about: AboutView() case .tips: TipView() case .setting: SettingView() - default: - Text("test") + case .wallabagPlus: + WallabagPlusView() + case .registration: + RegistrationView() } } } diff --git a/App/Features/Sync/AppSync.swift b/App/Features/Sync/AppSync.swift index 4535f230..66eb25ca 100644 --- a/App/Features/Sync/AppSync.swift +++ b/App/Features/Sync/AppSync.swift @@ -58,12 +58,12 @@ extension AppSync { } private func handleEntries(_ wallabagEntries: [WallabagEntry]) { - wallabagEntries.forEach { wallabagEntry in - self.entriesSynced.append(wallabagEntry.id) - if let entry = try? self.backgroundContext.fetch(Entry.fetchOneById(wallabagEntry.id)).first { - self.update(entry, with: wallabagEntry) + for wallabagEntry in wallabagEntries { + entriesSynced.append(wallabagEntry.id) + if let entry = try? backgroundContext.fetch(Entry.fetchOneById(wallabagEntry.id)).first { + update(entry, with: wallabagEntry) } else { - self.insert(wallabagEntry) + insert(wallabagEntry) } } } @@ -122,15 +122,15 @@ extension AppSync { private func synchronizeTags() async { do { - try await fetchTags().forEach { wallabagTag in - if let tag = try? self.backgroundContext.fetch(Tag.fetchOneById(wallabagTag.id)).first { - self.tags[tag.id] = tag + try await for wallabagTag in fetchTags() { + if let tag = try? backgroundContext.fetch(Tag.fetchOneById(wallabagTag.id)).first { + tags[tag.id] = tag } else { - let tag = Tag(context: self.backgroundContext) + let tag = Tag(context: backgroundContext) tag.id = wallabagTag.id tag.label = wallabagTag.label tag.slug = wallabagTag.slug - self.tags[wallabagTag.id] = tag + tags[wallabagTag.id] = tag } } } catch _ {} diff --git a/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift b/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift new file mode 100644 index 00000000..8452b3f4 --- /dev/null +++ b/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct WallabagPlusProtectedModifier: ViewModifier { + @EnvironmentObject private var wallabagPlusStore: WallabagPlusStore + + func body(content: Content) -> some View { + VStack { + if wallabagPlusStore.proUnlocked { + content + } else { + Image("logo") + .resizable() + .scaledToFit() + Text("Sorry, this feature require you subscribe to wallabag Plus") + .fontDesign(.rounded) + } + } + } +} + +extension View { + func wallabagPlusProtected() -> some View { + modifier(WallabagPlusProtectedModifier()) + } +} diff --git a/App/Features/WallabagPlus/WallabagPlusStore.swift b/App/Features/WallabagPlus/WallabagPlusStore.swift new file mode 100644 index 00000000..fe8ed77a --- /dev/null +++ b/App/Features/WallabagPlus/WallabagPlusStore.swift @@ -0,0 +1,60 @@ +// +// WallabagPlusStore.swift +// wallabag +// +// Created by maxime marinel on 11/01/2024. +// + +import Foundation +import StoreKit + +final class WallabagPlusStore: ObservableObject { + private var obs: Task? + + let groupID = "21433277" + + @Published var proUnlocked = false + + init() { + obs = observeTransactionUpdates() + restorePurchase() + } + + deinit { + obs?.cancel() + } + + private func observeTransactionUpdates() -> Task { + Task(priority: .background) { + for await verification in Transaction.updates { + await finish(verification) + } + } + } + + func restorePurchase() { + Task { + if let result = await Transaction.latest(for: "wallabagplus") { + await finish(result) + } + } + } + + @MainActor + func finish(_ result: VerificationResult) async { + switch result { + case .unverified: + proUnlocked = false + case let .verified(signedType): + await signedType.finish() + let status = await signedType.subscriptionStatus + proUnlocked = status?.state == .subscribed + } + } + + func handleStatues(_ statuses: [Product.SubscriptionInfo.Status]) async { + guard let transtaction = statuses.last?.transaction else { return } + + await finish(transtaction) + } +} diff --git a/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift b/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift new file mode 100644 index 00000000..21940587 --- /dev/null +++ b/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift @@ -0,0 +1,39 @@ +// +// WallabagPlusSubscribeView.swift +// wallabag +// +// Created by maxime marinel on 11/01/2024. +// + +import StoreKit +import SwiftUI + +struct WallabagPlusSubscribeView: View { + @BundleKey("PRIVACY_URL") + private var privacyURL + + @BundleKey("TERMS_OF_USE_URL") + private var termsOfUseURL + + @EnvironmentObject var wallabagPlusStore: WallabagPlusStore + + var body: some View { + SubscriptionStoreView(groupID: wallabagPlusStore.groupID, visibleRelationships: .all) + .subscriptionStoreButtonLabel(.multiline) + .storeButton(.visible, for: .restorePurchases) + .subscriptionStorePolicyDestination(url: privacyURL.url!, for: .privacyPolicy) + .subscriptionStorePolicyDestination(url: termsOfUseURL.url!, for: .termsOfService) + .subscriptionStoreControlStyle(.prominentPicker) + .onInAppPurchaseCompletion { _, result in + if case let .success(.success(transaction)) = result { + Task { + await wallabagPlusStore.finish(transaction) + } + } + } + } +} + +#Preview { + WallabagPlusSubscribeView() +} diff --git a/App/Features/WallabagPlus/WallabagPlusView.swift b/App/Features/WallabagPlus/WallabagPlusView.swift new file mode 100644 index 00000000..7a8703d5 --- /dev/null +++ b/App/Features/WallabagPlus/WallabagPlusView.swift @@ -0,0 +1,56 @@ +import SwiftUI + +struct WallabagPlusView: View { + @State private var showSubscriptionView = false + + var body: some View { + VStack(alignment: .leading) { + GroupBox("What is wallabag Plus ?") { + Text("Wallabag plus offert premium feature powered by AI") + .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading) + } + GroupBox("wallabag Plus is available on my instance ?") { + Text("No, wallabag plus is a premium feature only available on your iOS devices") + .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading) + Text("Wallabag Plus is not affiliate with any other service.") + .font(.footnote) + } + GroupBox("What is included with wallabag Plus") { + Grid(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + Image(systemName: "checkmark.circle.fill") + Text("Generate synthesis") + } + GridRow { + Image(systemName: "checkmark.circle.fill") + Text("Suggest tag from entry") + } + } + .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading) + .padding() + } + GroupBox("Privacy") { + Text("When you use wallabag Plus, your content will be sent to openAI") + .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading) + } + Spacer() + + Button(action: { + showSubscriptionView = true + }, label: { + Text("Subscribe") + }) + .buttonStyle(.borderedProminent) + .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .center) + } + .padding() + .fullScreenCover(isPresented: $showSubscriptionView, content: { + WallabagPlusSubscribeView() + }) + .navigationTitle("Wallabag Plus") + } +} + +#Preview { + WallabagPlusView() +} diff --git a/App/Info.plist b/App/Info.plist index 243fa577..e5402546 100644 --- a/App/Info.plist +++ b/App/Info.plist @@ -6,6 +6,10 @@ DOCUMENTATION_URL https://doc.wallabag.org/en/apps/ios.html + PRIVACY_URL + https://www.district-web.fr/wallabag-reader/privacy + TERMS_OF_USE_URL + https://www.district-web.fr/wallabag-reader/privacy CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -20,6 +24,8 @@ $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) + GPTBACK_KEY + $(GPTBACK_KEY) CFBundleVersion 319 LSRequiresIPhoneOS diff --git a/App/Lib/DependencyInjection.swift b/App/Lib/DependencyInjection.swift index 4b8d86b9..c83760a9 100644 --- a/App/Lib/DependencyInjection.swift +++ b/App/Lib/DependencyInjection.swift @@ -79,4 +79,17 @@ extension Container { return kit }.scope(.singleton) } + + var chatAssistant: Factory { + self { + ChatAssistant() + } + } + + var wallabagPlusStore: Factory { + self { + WallabagPlusStore() + } + .singleton + } } diff --git a/App/Lib/WallabagError.swift b/App/Lib/WallabagError.swift index a7db405f..71b3d2c2 100644 --- a/App/Lib/WallabagError.swift +++ b/App/Lib/WallabagError.swift @@ -9,13 +9,13 @@ extension WallabagError: LocalizedError { var localizedDescription: String { switch self { case let .syncError(error): - return "\(error)" + "\(error)" case let .wallabagKitError(error): switch error { // case let .jsonError(json): // return json.errorDescription default: - return error.localizedDescription + error.localizedDescription } } } diff --git a/App/PropertyWrapper/BundleKey.swift b/App/PropertyWrapper/BundleKey.swift index 1a322d56..ba90f4ac 100644 --- a/App/PropertyWrapper/BundleKey.swift +++ b/App/PropertyWrapper/BundleKey.swift @@ -1,7 +1,7 @@ import Foundation @propertyWrapper -class BundleKey { +struct BundleKey { let key: String var wrappedValue: String { diff --git a/App/WallabagApp.swift b/App/WallabagApp.swift index e046f211..c4291ebb 100644 --- a/App/WallabagApp.swift +++ b/App/WallabagApp.swift @@ -13,6 +13,7 @@ struct WallabagApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate #endif + @InjectedObject(\.wallabagPlusStore) private var wallabagPlusStore @Injected(\.appState) private var appState @Injected(\.router) private var router #if os(iOS) @@ -35,8 +36,15 @@ struct WallabagApp: App { .environmentObject(errorHandler) .environmentObject(appSync) .environmentObject(appSetting) + .environmentObject(wallabagPlusStore) .environment(\.managedObjectContext, coreData.viewContext) - }.onChange(of: scenePhase) { state in + .subscriptionStatusTask(for: wallabagPlusStore.groupID) { task in + _ = await task.map { statues in + await wallabagPlusStore.handleStatues(statues) + } + } + } + .onChange(of: scenePhase) { state in if state == .active { appState.initSession() #if os(iOS) diff --git a/App/WallabagStoreKit.storekit b/App/WallabagStoreKit.storekit new file mode 100644 index 00000000..434edbbe --- /dev/null +++ b/App/WallabagStoreKit.storekit @@ -0,0 +1,124 @@ +{ + "identifier" : "2657AEEA", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + { + "displayPrice" : "0.99", + "familyShareable" : false, + "internalID" : "1247187745", + "localizations" : [ + { + "description" : "Cette application est développée sur le temps libre, elle est gratuite et la restera ainsi.Mais vous pouvez contribuer financièrement en faisant un don chaque fois que vous souhaitez soutenir le projet.", + "displayName" : "Don", + "locale" : "fr" + }, + { + "description" : "Diese Anwendung ist auf freie Zeit entwickelt, es ist kostenlos und wird so bleiben. Aber Sie können finanziell beitragen, indem Sie eine Spende machen, wann immer Sie das Projekt unterstützen möchte", + "displayName" : "Geschenk", + "locale" : "de" + }, + { + "description" : "This application is developed on free time, it is free and will remain so. But you can contribute financially by making a donation whenever you want to support the project.", + "displayName" : "Tips", + "locale" : "en_US" + } + ], + "productID" : "tips1", + "referenceName" : "tips1", + "type" : "Consumable" + } + ], + "settings" : { + "_applicationInternalID" : "1170800946", + "_developerTeamID" : "G97URPCGB8", + "_failTransactionsEnabled" : false, + "_lastSynchronizedDate" : 726670179.27365196, + "_locale" : "en_US", + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "current" : null, + "enabled" : false, + "name" : "Load Products" + }, + { + "current" : null, + "enabled" : false, + "name" : "Purchase" + }, + { + "current" : null, + "enabled" : false, + "name" : "Verification" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Store Sync" + }, + { + "current" : null, + "enabled" : false, + "name" : "Subscription Status" + }, + { + "current" : null, + "enabled" : false, + "name" : "App Transaction" + }, + { + "current" : null, + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "current" : null, + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ] + }, + "subscriptionGroups" : [ + { + "id" : "21433277", + "localizations" : [ + + ], + "name" : "Wallabag", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "2.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "6475806829", + "introductoryOffer" : null, + "localizations" : [ + + ], + "productID" : "wallabagplus", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Wallabag plus", + "subscriptionGroupID" : "21433277", + "type" : "RecurringSubscription" + } + ] + } + ], + "version" : { + "major" : 3, + "minor" : 0 + } +} diff --git a/Dangerfile.swift b/Dangerfile.swift index 3f3db838..649618d7 100644 --- a/Dangerfile.swift +++ b/Dangerfile.swift @@ -1,2 +1,3 @@ import Danger + let danger = Danger() diff --git a/SharedLib/Sources/SharedLib/Features/RetrieveMode/RetrieveMode.swift b/SharedLib/Sources/SharedLib/Features/RetrieveMode/RetrieveMode.swift index d985c739..b11e55a8 100644 --- a/SharedLib/Sources/SharedLib/Features/RetrieveMode/RetrieveMode.swift +++ b/SharedLib/Sources/SharedLib/Features/RetrieveMode/RetrieveMode.swift @@ -24,26 +24,26 @@ public enum RetrieveMode: String, CaseIterable { public var settingCase: String { switch self { case .allArticles: - return "allArticles" + "allArticles" case .archivedArticles: - return "archivedArticles" + "archivedArticles" case .unarchivedArticles: - return "unarchivedArticles" + "unarchivedArticles" case .starredArticles: - return "starredArticles" + "starredArticles" } } public func predicate() -> NSPredicate { switch self { case .unarchivedArticles: - return NSPredicate(format: "isArchived == NO") + NSPredicate(format: "isArchived == NO") case .starredArticles: - return NSPredicate(format: "isStarred == YES") + NSPredicate(format: "isStarred == YES") case .archivedArticles: - return NSPredicate(format: "isArchived == YES") + NSPredicate(format: "isArchived == YES") case .allArticles: - return NSPredicate(value: true) + NSPredicate(value: true) } } } diff --git a/WallabagKit/Sources/WallabagKit/Endpoint/WallabagEntryEndpoint.swift b/WallabagKit/Sources/WallabagKit/Endpoint/WallabagEntryEndpoint.swift index 9d95d0bb..d7be311a 100644 --- a/WallabagKit/Sources/WallabagKit/Endpoint/WallabagEntryEndpoint.swift +++ b/WallabagKit/Sources/WallabagKit/Endpoint/WallabagEntryEndpoint.swift @@ -12,15 +12,15 @@ public enum WallabagEntryEndpoint: WallabagKitEndpoint { public func method() -> HttpMethod { switch self { case .get: - return .get + .get case .add, .addTag: - return .post + .post case .delete, .deleteTag: - return .delete + .delete case .update: - return .patch + .patch case .reload: - return .patch + .patch } } @@ -54,15 +54,15 @@ public enum WallabagEntryEndpoint: WallabagKitEndpoint { switch self { case let .add(url): // swiftlint:disable:next force_try - return try! JSONSerialization.data(withJSONObject: ["url": url], options: .prettyPrinted) + try! JSONSerialization.data(withJSONObject: ["url": url], options: .prettyPrinted) case let .update(_, parameters): // swiftlint:disable:next force_try - return try! JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) + try! JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) case let .addTag(tag, _): // swiftlint:disable:next force_try - return try! JSONSerialization.data(withJSONObject: ["tags": tag], options: .prettyPrinted) + try! JSONSerialization.data(withJSONObject: ["tags": tag], options: .prettyPrinted) default: - return "".data(using: .utf8)! + "".data(using: .utf8)! } } diff --git a/WallabagKit/Sources/WallabagKit/Endpoint/WallabagOAuth.swift b/WallabagKit/Sources/WallabagKit/Endpoint/WallabagOAuth.swift index 05fd6948..bfd39006 100644 --- a/WallabagKit/Sources/WallabagKit/Endpoint/WallabagOAuth.swift +++ b/WallabagKit/Sources/WallabagKit/Endpoint/WallabagOAuth.swift @@ -8,7 +8,7 @@ enum WallabagOauth: WallabagKitEndpoint { func endpoint() -> String { switch self { case .request: - return "/oauth/v2/token" + "/oauth/v2/token" } } @@ -16,7 +16,7 @@ enum WallabagOauth: WallabagKitEndpoint { switch self { case let .request(clientId, clientSecret, username, password): // swiftlint:disable:next force_try - return try! JSONSerialization.data(withJSONObject: [ + try! JSONSerialization.data(withJSONObject: [ "grant_type": "password", "client_id": clientId, "client_secret": clientSecret, diff --git a/bagit/ShareExtensionError.swift b/bagit/ShareExtensionError.swift index 9487463b..c2c12929 100644 --- a/bagit/ShareExtensionError.swift +++ b/bagit/ShareExtensionError.swift @@ -9,13 +9,13 @@ enum ShareExtensionError: Error, LocalizedError { var localizedDescription: String { switch self { case .unregistredApp: - return "App not registred or configured" + "App not registred or configured" case .authError: - return "Error during auth" + "Error during auth" case .retrievingURL: - return "Error retrieve url from extension" + "Error retrieve url from extension" case .duringAdding: - return "Error during pushing to your wallabag server" + "Error during pushing to your wallabag server" } } } diff --git a/fastlane/SnapshotHelper.swift b/fastlane/SnapshotHelper.swift index 2d3505b9..1e0c142b 100644 --- a/fastlane/SnapshotHelper.swift +++ b/fastlane/SnapshotHelper.swift @@ -44,9 +44,9 @@ enum SnapshotError: Error, CustomDebugStringConvertible { var debugDescription: String { switch self { case .cannotFindSimulatorHomeDirectory: - return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." + "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable." case .cannotRunOnPhysicalDevice: - return "Can't use Snapshot on a physical device." + "Can't use Snapshot on a physical device." } } } diff --git a/openapi-generator-config.yaml b/openapi-generator-config.yaml new file mode 100644 index 00000000..2f510677 --- /dev/null +++ b/openapi-generator-config.yaml @@ -0,0 +1,7 @@ +generate: + - types + - client + +filter: + tags: + - wallabag diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 00000000..a01709cb --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,119 @@ +openapi: 3.1.0 +info: + title: Back GPT + version: 1.0.0 +servers: + - url: http://localhost:8080 + - url: https://gpt.district-web.com +tags: + - name: wallabag + description: Everything about wallabag + - name: opinion + description: Everything about opinion +paths: + /wallabag/synthesis: + post: + tags: + - wallabag + summary: Generate synthesis from entry + operationId: wallabagSynthesis + requestBody: + description: Generate synthesis from entry + content: + application/json: + schema: + $ref: '#/components/schemas/WallabagSynthesisQuery' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/WallabagSynthesisResponse' + /wallabag/tags: + post: + tags: + - wallabag + summary: Generate tags from entry + operationId: wallabagTags + requestBody: + description: Generate tags from entry + content: + application/json: + schema: + $ref: '#/components/schemas/WallabagTagQuery' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/WallabagTagResponse' + /opinion/review: + post: + tags: + - opinion + summary: Generate response from entry + operationId: opinionReview + requestBody: + description: Generate response from entry + content: + application/json: + schema: + $ref: '#/components/schemas/OpinionReview' + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/OpinionReview' +components: + schemas: + WallabagSynthesisQuery: + type: object + properties: + body: + type: string + examples: ['My content'] + language: + type: string + examples: [FR] + WallabagSynthesisResponse: + type: object + properties: + content: + type: string + examples: ['My content'] + WallabagTagQuery: + type: object + properties: + body: + type: string + examples: ['My content'] + language: + type: string + examples: [FR] + WallabagTagResponse: + type: object + properties: + tags: + type: array + items: + type: string + examples: ['My content'] + OpinionReview: + type: object + properties: + content: + type: string + examples: ['My content'] + securitySchemes: + bearerAuth: + type: http + scheme: bearer +security: + - bearerAuth: [] diff --git a/wallabag.xcodeproj/project.pbxproj b/wallabag.xcodeproj/project.pbxproj index 499789e0..ec4b2940 100644 --- a/wallabag.xcodeproj/project.pbxproj +++ b/wallabag.xcodeproj/project.pbxproj @@ -78,16 +78,28 @@ 09644D1025C9872F000FFDA1 /* BundleKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644D0F25C9872F000FFDA1 /* BundleKey.swift */; }; 09644D1825C98755000FFDA1 /* AppDelegateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644D1725C98755000FFDA1 /* AppDelegateExtension.swift */; }; 09644D2025C98782000FFDA1 /* wallabagStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 09644D1E25C98782000FFDA1 /* wallabagStore.xcdatamodeld */; }; + 097DD8FA2B4C0DB8005D3930 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 097DD8F92B4C0DB8005D3930 /* Factory */; }; + 097DD8FC2B4C0E02005D3930 /* OpenAPIRuntime in Frameworks */ = {isa = PBXBuildFile; productRef = 097DD8FB2B4C0E02005D3930 /* OpenAPIRuntime */; }; + 097DD8FE2B4C0E08005D3930 /* OpenAPIURLSession in Frameworks */ = {isa = PBXBuildFile; productRef = 097DD8FD2B4C0E08005D3930 /* OpenAPIURLSession */; }; + 097DD9042B4C0EE0005D3930 /* openapi-generator-config.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 097DD9022B4C0E78005D3930 /* openapi-generator-config.yaml */; }; + 097DD9052B4C0EE0005D3930 /* openapi.yaml in Resources */ = {isa = PBXBuildFile; fileRef = 097DD9012B4C0E62005D3930 /* openapi.yaml */; }; 097F81EB25CB18BA006C85F6 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097F81DF25CB187B006C85F6 /* Router.swift */; }; 09800D8F29B9DEFE00DAB403 /* SharedLib in Frameworks */ = {isa = PBXBuildFile; productRef = 09800D8E29B9DEFE00DAB403 /* SharedLib */; }; 098BDD0829BF04E3003DF719 /* RouteSwiftUIExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098BDD0729BF04E3003DF719 /* RouteSwiftUIExtension.swift */; }; 098CF4D629CD830E00DEAE50 /* ServerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098CF4D529CD830E00DEAE50 /* ServerViewModelTests.swift */; }; + 09952D112B566EAB0059D24F /* TagSuggestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09952D102B566EAB0059D24F /* TagSuggestionView.swift */; }; + 099CD1292B4FCC6F0029E94A /* WallabagPlusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099CD1282B4FCC6F0029E94A /* WallabagPlusView.swift */; }; + 099CD12C2B4FCDB90029E94A /* WallabagPlusSubscribeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099CD12B2B4FCDB90029E94A /* WallabagPlusSubscribeView.swift */; }; + 099CD1312B501D950029E94A /* WallabagPlusStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099CD1302B501D950029E94A /* WallabagPlusStore.swift */; }; + 099CD1372B516DA30029E94A /* WallabagPlusProtectedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099CD1362B516DA30029E94A /* WallabagPlusProtectedModifier.swift */; }; 09AD75A12624F93D00708A1E /* article.html in Resources */ = {isa = PBXBuildFile; fileRef = 09AD759D2624F93D00708A1E /* article.html */; }; 09AD75A22624F93D00708A1E /* main.css in Resources */ = {isa = PBXBuildFile; fileRef = 09AD759E2624F93D00708A1E /* main.css */; }; 09AD75A32624F93D00708A1E /* ratatouille.css in Resources */ = {isa = PBXBuildFile; fileRef = 09AD759F2624F93D00708A1E /* ratatouille.css */; }; 09AD75A42624F93D00708A1E /* justify.css in Resources */ = {isa = PBXBuildFile; fileRef = 09AD75A02624F93D00708A1E /* justify.css */; }; 09AD75BE2624FEEE00708A1E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 09AD75C02624FEEE00708A1E /* Localizable.strings */; }; - 09AE6ECC29B13AAE0025DF93 /* WallabagKit in Frameworks */ = {isa = PBXBuildFile; productRef = 09AE6ECB29B13AAE0025DF93 /* WallabagKit */; }; + 09AE6ECE29B13ABC0025DF93 /* WallabagKit in Frameworks */ = {isa = PBXBuildFile; productRef = 09AE6ECD29B13ABC0025DF93 /* WallabagKit */; }; + 09B8C3A62B27AD2D002AEA2C /* ChatAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B8C3A52B27AD2D002AEA2C /* ChatAssistant.swift */; }; + 09B8C3A82B27AF62002AEA2C /* SynthesisEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */; }; 09AEBEA029B1D2C500050BBE /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 09AEBE9F29B1D2C500050BBE /* Factory */; }; 09BCBBD4282BD46A00B234CB /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BCBBD3282BD46A00B234CB /* RouterTests.swift */; }; 09BE0AF42A9F45E900193FBF /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BE0AF32A9F45E900193FBF /* View+Extension.swift */; }; @@ -208,9 +220,17 @@ 09644D0F25C9872F000FFDA1 /* BundleKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleKey.swift; sourceTree = ""; }; 09644D1725C98755000FFDA1 /* AppDelegateExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateExtension.swift; sourceTree = ""; }; 09644D1F25C98782000FFDA1 /* wallabagStore.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = wallabagStore.xcdatamodel; sourceTree = ""; }; + 097DD9012B4C0E62005D3930 /* openapi.yaml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = openapi.yaml; sourceTree = ""; }; + 097DD9022B4C0E78005D3930 /* openapi-generator-config.yaml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = "openapi-generator-config.yaml"; sourceTree = ""; }; 097F81DF25CB187B006C85F6 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; 098BDD0729BF04E3003DF719 /* RouteSwiftUIExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteSwiftUIExtension.swift; sourceTree = ""; }; 098CF4D529CD830E00DEAE50 /* ServerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerViewModelTests.swift; sourceTree = ""; }; + 09952D102B566EAB0059D24F /* TagSuggestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagSuggestionView.swift; sourceTree = ""; }; + 099CD1282B4FCC6F0029E94A /* WallabagPlusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallabagPlusView.swift; sourceTree = ""; }; + 099CD12B2B4FCDB90029E94A /* WallabagPlusSubscribeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallabagPlusSubscribeView.swift; sourceTree = ""; }; + 099CD12E2B5019F40029E94A /* WallabagStoreKit.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = WallabagStoreKit.storekit; sourceTree = ""; }; + 099CD1302B501D950029E94A /* WallabagPlusStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallabagPlusStore.swift; sourceTree = ""; }; + 099CD1362B516DA30029E94A /* WallabagPlusProtectedModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallabagPlusProtectedModifier.swift; sourceTree = ""; }; 09AD75962624F4E800708A1E /* wallabag.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = wallabag.entitlements; sourceTree = ""; }; 09AD759D2624F93D00708A1E /* article.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = article.html; sourceTree = ""; }; 09AD759E2624F93D00708A1E /* main.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = main.css; sourceTree = ""; }; @@ -228,13 +248,15 @@ 09AD75D32624FF5600708A1E /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 09AD75D42624FF5F00708A1E /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 09AD75D52624FF6900708A1E /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + 09B8C3A02B27AB5F002AEA2C /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 09B8C3A52B27AD2D002AEA2C /* ChatAssistant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAssistant.swift; sourceTree = ""; }; + 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynthesisEntryView.swift; sourceTree = ""; }; 09BCBBD3282BD46A00B234CB /* RouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = ""; }; 09BE0AF32A9F45E900193FBF /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; 09BFB28425C8348E00E12B4D /* wallabag.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wallabag.app; sourceTree = BUILT_PRODUCTS_DIR; }; 09BFB28725C8348E00E12B4D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81505C5A2B5DC21F003B5CDE /* WallabagIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallabagIntent.swift; sourceTree = ""; }; 81505C5C2B5DC23C003B5CDE /* AddEntryIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEntryIntent.swift; sourceTree = ""; }; - 9C1103B626A0852F00E50F26 /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -258,10 +280,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 09AEBEA029B1D2C500050BBE /* Factory in Frameworks */, 09644B0F25C94C6B000FFDA1 /* HTMLEntities in Frameworks */, 09C34C7E29B1399100B5C927 /* WallabagKit in Frameworks */, + 097DD8FC2B4C0E02005D3930 /* OpenAPIRuntime in Frameworks */, 09C34C7C29B1398B00B5C927 /* SharedLib in Frameworks */, + 097DD8FE2B4C0E08005D3930 /* OpenAPIURLSession in Frameworks */, + 097DD8FA2B4C0DB8005D3930 /* Factory in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -346,6 +370,8 @@ 09644B8425C98161000FFDA1 /* Features */ = { isa = PBXGroup; children = ( + 099CD1272B4FCC570029E94A /* WallabagPlus */, + 09B8C3A42B27ACB4002AEA2C /* AI */, 09644C9D25C98615000FFDA1 /* MainView.swift */, 09644C8525C985C4000FFDA1 /* About */, 09644C8D25C985D9000FFDA1 /* BugReport */, @@ -610,6 +636,17 @@ path = Server; sourceTree = ""; }; + 099CD1272B4FCC570029E94A /* WallabagPlus */ = { + isa = PBXGroup; + children = ( + 099CD1282B4FCC6F0029E94A /* WallabagPlusView.swift */, + 099CD12B2B4FCDB90029E94A /* WallabagPlusSubscribeView.swift */, + 099CD1302B501D950029E94A /* WallabagPlusStore.swift */, + 099CD1362B516DA30029E94A /* WallabagPlusProtectedModifier.swift */, + ); + path = WallabagPlus; + sourceTree = ""; + }; 09AD759C2624F93D00708A1E /* html-ressources */ = { isa = PBXGroup; children = ( @@ -621,6 +658,16 @@ path = "html-ressources"; sourceTree = ""; }; + 09B8C3A42B27ACB4002AEA2C /* AI */ = { + isa = PBXGroup; + children = ( + 09B8C3A52B27AD2D002AEA2C /* ChatAssistant.swift */, + 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */, + 09952D102B566EAB0059D24F /* TagSuggestionView.swift */, + ); + path = AI; + sourceTree = ""; + }; 09BCBBD2282BD44100B234CB /* Router */ = { isa = PBXGroup; children = ( @@ -641,6 +688,9 @@ isa = PBXGroup; children = ( 81505C592B5DC209003B5CDE /* Intents */, + 097DD9022B4C0E78005D3930 /* openapi-generator-config.yaml */, + 097DD9012B4C0E62005D3930 /* openapi.yaml */, + 09B8C3A02B27AB5F002AEA2C /* Config.xcconfig */, 09AD75962624F4E800708A1E /* wallabag.entitlements */, 09644ACF25C94825000FFDA1 /* bagit */, 09644B1525C94CA6000FFDA1 /* Frameworks */, @@ -664,7 +714,6 @@ isa = PBXGroup; children = ( 09BFB28725C8348E00E12B4D /* Info.plist */, - 9C1103B626A0852F00E50F26 /* Configuration.storekit */, 09644B5D25C98116000FFDA1 /* AppDelegate.swift */, 09644B5625C9810A000FFDA1 /* WallabagApp.swift */, 097F824C25CB1B17006C85F6 /* Entity */, @@ -676,6 +725,7 @@ 09644BE425C98343000FFDA1 /* PropertyWrapper */, 09644D1E25C98782000FFDA1 /* wallabagStore.xcdatamodeld */, 0951C61729CC2EB000D8E8C6 /* Assets.xcassets */, + 099CD12E2B5019F40029E94A /* WallabagStoreKit.storekit */, ); path = App; sourceTree = ""; @@ -744,6 +794,7 @@ buildRules = ( ); dependencies = ( + 097DD9002B4C0E2A005D3930 /* PBXTargetDependency */, 09644AD725C94825000FFDA1 /* PBXTargetDependency */, ); name = wallabag; @@ -751,7 +802,9 @@ 09644B0E25C94C6B000FFDA1 /* HTMLEntities */, 09C34C7B29B1398B00B5C927 /* SharedLib */, 09C34C7D29B1399100B5C927 /* WallabagKit */, - 09AEBE9F29B1D2C500050BBE /* Factory */, + 097DD8F92B4C0DB8005D3930 /* Factory */, + 097DD8FB2B4C0E02005D3930 /* OpenAPIRuntime */, + 097DD8FD2B4C0E08005D3930 /* OpenAPIURLSession */, ); productName = "wallabag (iOS)"; productReference = 09BFB28425C8348E00E12B4D /* wallabag.app */; @@ -763,8 +816,9 @@ 09BFB27525C8348E00E12B4D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1510; TargetAttributes = { 094AA03A2629EB1F006E5605 = { CreatedOnToolsVersion = 12.4; @@ -800,7 +854,10 @@ mainGroup = 09BFB27425C8348E00E12B4D; packageReferences = ( 09644B0D25C94C6B000FFDA1 /* XCRemoteSwiftPackageReference "swift-html-entities" */, - 09AEBE9E29B1D2C500050BBE /* XCRemoteSwiftPackageReference "Factory" */, + 097DD8F52B4C0CCC005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-generator" */, + 097DD8F62B4C0D3A005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-runtime" */, + 097DD8F72B4C0D54005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-urlsession" */, + 097DD8F82B4C0DB8005D3930 /* XCRemoteSwiftPackageReference "Factory" */, ); productRefGroup = 09BFB28525C8348E00E12B4D /* Products */; projectDirPath = ""; @@ -836,7 +893,9 @@ buildActionMask = 2147483647; files = ( 09AD75A32624F93D00708A1E /* ratatouille.css in Resources */, + 097DD9052B4C0EE0005D3930 /* openapi.yaml in Resources */, 09AD75A12624F93D00708A1E /* article.html in Resources */, + 097DD9042B4C0EE0005D3930 /* openapi-generator-config.yaml in Resources */, 09AD75BE2624FEEE00708A1E /* Localizable.strings in Resources */, 09AD75A42624F93D00708A1E /* justify.css in Resources */, 0951C61829CC2EB000D8E8C6 /* Assets.xcassets in Resources */, @@ -905,6 +964,7 @@ 09644C8F25C985E8000FFDA1 /* BugReportView.swift in Sources */, 09644BFB25C983F8000FFDA1 /* Tag.swift in Sources */, 09644CD625C986C0000FFDA1 /* ClientIdClientSecretView.swift in Sources */, + 09952D112B566EAB0059D24F /* TagSuggestionView.swift in Sources */, 09644B9725C9819F000FFDA1 /* PlayerView.swift in Sources */, 09644CF225C986EE000FFDA1 /* SearchViewModel.swift in Sources */, 09644BA325C981B4000FFDA1 /* ErrorViewModel.swift in Sources */, @@ -919,13 +979,17 @@ 09644C7125C985A9000FFDA1 /* FontSizeSelectorView.swift in Sources */, 09644C7D25C985B8000FFDA1 /* ImageCache.swift in Sources */, 09644CC225C98674000FFDA1 /* PasteBoardView.swift in Sources */, + 099CD12C2B4FCDB90029E94A /* WallabagPlusSubscribeView.swift in Sources */, + 09B8C3A82B27AF62002AEA2C /* SynthesisEntryView.swift in Sources */, 09644CC325C98674000FFDA1 /* PasteBoardViewModel.swift in Sources */, 09644C6D25C985A9000FFDA1 /* StarEntryButton.swift in Sources */, 09644C6125C98596000FFDA1 /* EntriesListView.swift in Sources */, + 099CD1372B516DA30029E94A /* WallabagPlusProtectedModifier.swift in Sources */, 09644CF325C986EE000FFDA1 /* SearchView.swift in Sources */, 09564E0D2851C57200D39E95 /* SettingView.swift in Sources */, 09644C5F25C98596000FFDA1 /* EntryRowView.swift in Sources */, 09644CE925C986CC000FFDA1 /* ServerViewModel.swift in Sources */, + 099CD1312B501D950029E94A /* WallabagPlusStore.swift in Sources */, 09644CD725C986C0000FFDA1 /* ClientIdClientSecretViewModel.swift in Sources */, 09644CCB25C9869D000FFDA1 /* RegistrationView.swift in Sources */, 09644CFE25C9870C000FFDA1 /* TagRow.swift in Sources */, @@ -935,6 +999,7 @@ 09644BB725C98232000FFDA1 /* AppSetting.swift in Sources */, 09644C7F25C985B8000FFDA1 /* ImageDownloaderPublisher.swift in Sources */, 09644CE825C986CC000FFDA1 /* ServerView.swift in Sources */, + 09B8C3A62B27AD2D002AEA2C /* ChatAssistant.swift in Sources */, 09644CFD25C9870C000FFDA1 /* TagListFor.swift in Sources */, 09644BFA25C983F8000FFDA1 /* Podcast.swift in Sources */, 09644BAE25C98213000FFDA1 /* AppSync.swift in Sources */, @@ -942,6 +1007,7 @@ 09644BA225C981B4000FFDA1 /* ErrorView.swift in Sources */, 09644B9825C9819F000FFDA1 /* PlayerPublisher.swift in Sources */, 09644C7C25C985B8000FFDA1 /* ImageDownloader.swift in Sources */, + 099CD1292B4FCC6F0029E94A /* WallabagPlusView.swift in Sources */, 09644D0825C9871A000FFDA1 /* TipView.swift in Sources */, 09644BDD25C9833E000FFDA1 /* CoreData.swift in Sources */, 09644D0925C9871A000FFDA1 /* TipViewModel.swift in Sources */, @@ -976,6 +1042,10 @@ target = 09644ACD25C94825000FFDA1 /* bagit */; targetProxy = 09644AD625C94825000FFDA1 /* PBXContainerItemProxy */; }; + 097DD9002B4C0E2A005D3930 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = 097DD8FF2B4C0E2A005D3930 /* OpenAPIGenerator */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1109,6 +1179,7 @@ }; 09BFB2B025C8348E00E12B4D /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 09B8C3A02B27AB5F002AEA2C /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -1160,7 +1231,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 7.0.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1172,6 +1243,7 @@ }; 09BFB2B125C8348E00E12B4D /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 09B8C3A02B27AB5F002AEA2C /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -1217,7 +1289,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MARKETING_VERSION = 7.0.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; @@ -1239,7 +1311,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1269,7 +1341,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = App/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1337,12 +1409,36 @@ minimumVersion = 3.0.200; }; }; - 09AEBE9E29B1D2C500050BBE /* XCRemoteSwiftPackageReference "Factory" */ = { + 097DD8F52B4C0CCC005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-generator" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "git@github.com:hmlongco/Factory.git"; + repositoryURL = "https://github.com/apple/swift-openapi-generator"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + minimumVersion = 1.1.0; + }; + }; + 097DD8F62B4C0D3A005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-runtime" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-openapi-runtime"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.1.0; + }; + }; + 097DD8F72B4C0D54005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-urlsession" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-openapi-urlsession"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; + 097DD8F82B4C0DB8005D3930 /* XCRemoteSwiftPackageReference "Factory" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/hmlongco/Factory"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.3.1; }; }; /* End XCRemoteSwiftPackageReference section */ @@ -1353,6 +1449,30 @@ package = 09644B0D25C94C6B000FFDA1 /* XCRemoteSwiftPackageReference "swift-html-entities" */; productName = HTMLEntities; }; + 097DD8F92B4C0DB8005D3930 /* Factory */ = { + isa = XCSwiftPackageProductDependency; + package = 097DD8F82B4C0DB8005D3930 /* XCRemoteSwiftPackageReference "Factory" */; + productName = Factory; + }; + 097DD8FB2B4C0E02005D3930 /* OpenAPIRuntime */ = { + isa = XCSwiftPackageProductDependency; + package = 097DD8F62B4C0D3A005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-runtime" */; + productName = OpenAPIRuntime; + }; + 097DD8FD2B4C0E08005D3930 /* OpenAPIURLSession */ = { + isa = XCSwiftPackageProductDependency; + package = 097DD8F72B4C0D54005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-urlsession" */; + productName = OpenAPIURLSession; + }; + 097DD8FF2B4C0E2A005D3930 /* OpenAPIGenerator */ = { + isa = XCSwiftPackageProductDependency; + package = 097DD8F52B4C0CCC005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-generator" */; + productName = "plugin:OpenAPIGenerator"; + }; + 09800D8C29B9DE9E00DAB403 /* SharedLib */ = { + isa = XCSwiftPackageProductDependency; + productName = SharedLib; + }; 09800D8E29B9DEFE00DAB403 /* SharedLib */ = { isa = XCSwiftPackageProductDependency; productName = SharedLib; @@ -1361,10 +1481,9 @@ isa = XCSwiftPackageProductDependency; productName = WallabagKit; }; - 09AEBE9F29B1D2C500050BBE /* Factory */ = { + 09AE6ECD29B13ABC0025DF93 /* WallabagKit */ = { isa = XCSwiftPackageProductDependency; - package = 09AEBE9E29B1D2C500050BBE /* XCRemoteSwiftPackageReference "Factory" */; - productName = Factory; + productName = WallabagKit; }; 09C34C7B29B1398B00B5C927 /* SharedLib */ = { isa = XCSwiftPackageProductDependency; diff --git a/wallabag.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/wallabag.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e1c7a8dd..6793343c 100644 --- a/wallabag.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/wallabag.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,34 +1,32 @@ { - "object": { - "pins": [ - { - "package": "HTMLEntities", - "repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git", - "state": { - "branch": null, - "revision": "2b14531d0c36dbb7c1c45a4d38db9c2e7898a307", - "version": "3.0.200" - } - }, - { - "package": "Swinject", - "repositoryURL": "https://github.com/Swinject/Swinject", - "state": { - "branch": null, - "revision": "8a76d2c74bafbb455763487cc6a08e91bad1f78b", - "version": "2.7.1" - } - }, - { - "package": "WallabagKit", - "repositoryURL": "https://github.com/wallabag/WallabagKit.git", - "state": { - "branch": "master", - "revision": "6f83586b893f876e447ae68d1f7c0508bdc8a3bb", - "version": null - } + "pins" : [ + { + "identity" : "factory", + "kind" : "remoteSourceControl", + "location" : "git@github.com:hmlongco/Factory.git", + "state" : { + "revision" : "8ca11a7bd1ede031e8e6d7a912bb116e2e43961b", + "version" : "2.3.1" } - ] - }, - "version": 1 + }, + { + "identity" : "openaiswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/astrokin/OpenAISwift.git", + "state" : { + "branch" : "fix_400", + "revision" : "db66252954f64c59111abb1932866039a64ef029" + } + }, + { + "identity" : "swift-html-entities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/IBM-Swift/swift-html-entities.git", + "state" : { + "revision" : "2b14531d0c36dbb7c1c45a4d38db9c2e7898a307", + "version" : "3.0.200" + } + } + ], + "version" : 2 } diff --git a/wallabag.xcodeproj/xcshareddata/xcschemes/bagit.xcscheme b/wallabag.xcodeproj/xcshareddata/xcschemes/bagit.xcscheme index b917b70f..e73e487f 100644 --- a/wallabag.xcodeproj/xcshareddata/xcschemes/bagit.xcscheme +++ b/wallabag.xcodeproj/xcshareddata/xcschemes/bagit.xcscheme @@ -1,6 +1,6 @@ + identifier = "../App/WallabagStoreKit.storekit"> Date: Mon, 22 Jan 2024 13:28:38 +0100 Subject: [PATCH 2/4] Prepare chat assistant --- .../{ => Synthesis}/SynthesisEntryView.swift | 18 ------- .../Synthesis/SynthesisEntryViewModel.swift | 27 +++++++++++ .../AI/{ => Tag}/TagSuggestionView.swift | 47 ++++++++----------- .../AI/Tag/TagSuggestionViewModel.swift | 41 ++++++++++++++++ App/Features/Sync/AppSync.swift | 2 +- wallabag.xcodeproj/project.pbxproj | 28 ++++++++++- 6 files changed, 115 insertions(+), 48 deletions(-) rename App/Features/AI/{ => Synthesis}/SynthesisEntryView.swift (62%) create mode 100644 App/Features/AI/Synthesis/SynthesisEntryViewModel.swift rename App/Features/AI/{ => Tag}/TagSuggestionView.swift (57%) create mode 100644 App/Features/AI/Tag/TagSuggestionViewModel.swift diff --git a/App/Features/AI/SynthesisEntryView.swift b/App/Features/AI/Synthesis/SynthesisEntryView.swift similarity index 62% rename from App/Features/AI/SynthesisEntryView.swift rename to App/Features/AI/Synthesis/SynthesisEntryView.swift index 207bf38c..64c2c82d 100644 --- a/App/Features/AI/SynthesisEntryView.swift +++ b/App/Features/AI/Synthesis/SynthesisEntryView.swift @@ -8,24 +8,6 @@ import Factory import SwiftUI -final class SynthesisEntryViewModel: ObservableObject { - @Injected(\.chatAssistant) private var chatAssistant - @Published var synthesis = "" - @Published var isLoading = false - - @MainActor - func generateSynthesis(from entry: Entry) async throws { - defer { - isLoading = false - } - isLoading = true - - guard let content = entry.content?.withoutHTML else { return } - - synthesis = try await chatAssistant.generateSynthesis(content: content) - } -} - struct SynthesisEntryView: View { @StateObject private var viewModel = SynthesisEntryViewModel() let entry: Entry diff --git a/App/Features/AI/Synthesis/SynthesisEntryViewModel.swift b/App/Features/AI/Synthesis/SynthesisEntryViewModel.swift new file mode 100644 index 00000000..71c85a09 --- /dev/null +++ b/App/Features/AI/Synthesis/SynthesisEntryViewModel.swift @@ -0,0 +1,27 @@ +// +// SynthesisEntryViewModel.swift +// wallabag +// +// Created by maxime marinel on 22/01/2024. +// + +import Factory +import Foundation + +final class SynthesisEntryViewModel: ObservableObject { + @Injected(\.chatAssistant) private var chatAssistant + @Published var synthesis = "" + @Published var isLoading = false + + @MainActor + func generateSynthesis(from entry: Entry) async throws { + defer { + isLoading = false + } + isLoading = true + + guard let content = entry.content?.withoutHTML else { return } + + synthesis = try await chatAssistant.generateSynthesis(content: content) + } +} diff --git a/App/Features/AI/TagSuggestionView.swift b/App/Features/AI/Tag/TagSuggestionView.swift similarity index 57% rename from App/Features/AI/TagSuggestionView.swift rename to App/Features/AI/Tag/TagSuggestionView.swift index a9c75756..8efdeeca 100644 --- a/App/Features/AI/TagSuggestionView.swift +++ b/App/Features/AI/Tag/TagSuggestionView.swift @@ -1,27 +1,9 @@ import Factory import SwiftUI -final class TagSuggestionViewModel: ObservableObject { - @Injected(\.chatAssistant) private var chatAssistant - @Published var suggestions: [String] = [] - @Published var isLoading = false - - @MainActor - func generateSynthesis(from entry: Entry) async throws { - defer { - isLoading = false - } - isLoading = true - - guard let content = entry.content?.withoutHTML else { return } - - suggestions = try await chatAssistant.generateTags(content: content) - } -} - struct TagSuggestionView: View { + @Environment(\.dismiss) private var dismiss @StateObject private var viewModel = TagSuggestionViewModel() - @State private var selection = Set() let entry: Entry @@ -34,10 +16,10 @@ struct TagSuggestionView: View { List { ForEach(viewModel.suggestions, id: \.self) { suggestion in Button(action: { - if selection.contains(suggestion) { - selection.remove(suggestion) + if viewModel.tagSelections.contains(suggestion) { + viewModel.tagSelections.remove(suggestion) } else { - selection.insert(suggestion) + viewModel.tagSelections.insert(suggestion) } }, label: { HStack { @@ -45,7 +27,7 @@ struct TagSuggestionView: View { .padding() .fontDesign(.serif) Spacer() - if selection.contains(suggestion) { + if viewModel.tagSelections.contains(suggestion) { Image(systemName: "checkmark.circle.fill") } else { Image(systemName: "circle") @@ -55,17 +37,28 @@ struct TagSuggestionView: View { } } .listStyle(.plain) - if !selection.isEmpty { - Button(action: {}, label: { - Text("Add \(selection.count.formatted()) tags") + if !viewModel.tagSelections.isEmpty { + Button(action: { + Task { + try? await viewModel.addTags(to: entry) + dismiss() + } + }, label: { + if viewModel.addingTags { + ProgressView() + } else { + Text("Add \(viewModel.tagSelections.count.formatted()) tags") + } }) + .buttonStyle(.borderedProminent) + .disabled(viewModel.addingTags) } } } .navigationTitle("Tag suggestion") .task { do { - try await viewModel.generateSynthesis(from: entry) + try await viewModel.generateTags(from: entry) } catch { print(error) } diff --git a/App/Features/AI/Tag/TagSuggestionViewModel.swift b/App/Features/AI/Tag/TagSuggestionViewModel.swift new file mode 100644 index 00000000..5cbb57d3 --- /dev/null +++ b/App/Features/AI/Tag/TagSuggestionViewModel.swift @@ -0,0 +1,41 @@ +// +// TagSuggestionViewModel.swift +// wallabag +// +// Created by maxime marinel on 22/01/2024. +// + +import Factory +import Foundation + +final class TagSuggestionViewModel: ObservableObject { + @Injected(\.wallabagSession) private var wallabagSession + @Injected(\.chatAssistant) private var chatAssistant + @Published var suggestions: [String] = [] + @Published var isLoading = false + @Published var addingTags = false + @Published var tagSelections = Set() + + @MainActor + func generateTags(from entry: Entry) async throws { + defer { + isLoading = false + } + isLoading = true + + guard let content = entry.content?.withoutHTML else { return } + + suggestions = try await chatAssistant.generateTags(content: content) + } + + @MainActor + func addTags(to entry: Entry) async throws { + defer { + addingTags = false + } + addingTags = true + for tag in tagSelections { + wallabagSession.add(tag: tag, for: entry) + } + } +} diff --git a/App/Features/Sync/AppSync.swift b/App/Features/Sync/AppSync.swift index 66eb25ca..82ab9bcd 100644 --- a/App/Features/Sync/AppSync.swift +++ b/App/Features/Sync/AppSync.swift @@ -122,7 +122,7 @@ extension AppSync { private func synchronizeTags() async { do { - try await for wallabagTag in fetchTags() { + for wallabagTag in try await fetchTags() { if let tag = try? backgroundContext.fetch(Tag.fetchOneById(wallabagTag.id)).first { tags[tag.id] = tag } else { diff --git a/wallabag.xcodeproj/project.pbxproj b/wallabag.xcodeproj/project.pbxproj index ec4b2940..f3ca76a0 100644 --- a/wallabag.xcodeproj/project.pbxproj +++ b/wallabag.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 094AA03E2629EB1F006E5605 /* wallabagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094AA03D2629EB1F006E5605 /* wallabagTests.swift */; }; 094AA05A2629EB60006E5605 /* ImageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094A9FED2629E6BB006E5605 /* ImageCacheTests.swift */; }; 094AA0692629EB98006E5605 /* ImageDownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094AA00F2629E739006E5605 /* ImageDownloaderTests.swift */; }; + 094BE2B52B5E94F900DFBF5A /* SynthesisEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094BE2B42B5E94F900DFBF5A /* SynthesisEntryViewModel.swift */; }; + 094BE2B92B5E952C00DFBF5A /* TagSuggestionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094BE2B82B5E952C00DFBF5A /* TagSuggestionViewModel.swift */; }; 0951C61829CC2EB000D8E8C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0951C61729CC2EB000D8E8C6 /* Assets.xcassets */; }; 09564E0D2851C57200D39E95 /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09564E0C2851C57200D39E95 /* SettingView.swift */; }; 09644AD125C94825000FFDA1 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09644AD025C94825000FFDA1 /* ShareViewController.swift */; }; @@ -152,6 +154,8 @@ 094AA03B2629EB1F006E5605 /* wallabagTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = wallabagTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 094AA03D2629EB1F006E5605 /* wallabagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = wallabagTests.swift; sourceTree = ""; }; 094AA03F2629EB1F006E5605 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 094BE2B42B5E94F900DFBF5A /* SynthesisEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynthesisEntryViewModel.swift; sourceTree = ""; }; + 094BE2B82B5E952C00DFBF5A /* TagSuggestionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagSuggestionViewModel.swift; sourceTree = ""; }; 0951C61729CC2EB000D8E8C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 09564E0C2851C57200D39E95 /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; 09644ACE25C94825000FFDA1 /* bagit.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = bagit.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -329,6 +333,24 @@ path = wallabagTests; sourceTree = ""; }; + 094BE2B22B5E94EA00DFBF5A /* Synthesis */ = { + isa = PBXGroup; + children = ( + 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */, + 094BE2B42B5E94F900DFBF5A /* SynthesisEntryViewModel.swift */, + ); + path = Synthesis; + sourceTree = ""; + }; + 094BE2B62B5E951F00DFBF5A /* Tag */ = { + isa = PBXGroup; + children = ( + 09952D102B566EAB0059D24F /* TagSuggestionView.swift */, + 094BE2B82B5E952C00DFBF5A /* TagSuggestionViewModel.swift */, + ); + path = Tag; + sourceTree = ""; + }; 09644ACF25C94825000FFDA1 /* bagit */ = { isa = PBXGroup; children = ( @@ -661,9 +683,9 @@ 09B8C3A42B27ACB4002AEA2C /* AI */ = { isa = PBXGroup; children = ( + 094BE2B62B5E951F00DFBF5A /* Tag */, + 094BE2B22B5E94EA00DFBF5A /* Synthesis */, 09B8C3A52B27AD2D002AEA2C /* ChatAssistant.swift */, - 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */, - 09952D102B566EAB0059D24F /* TagSuggestionView.swift */, ); path = AI; sourceTree = ""; @@ -974,6 +996,7 @@ 09644B7E25C98152000FFDA1 /* AppState.swift in Sources */, 09644C6E25C985A9000FFDA1 /* DeleteEntryButton.swift in Sources */, 09644B8C25C98176000FFDA1 /* Route.swift in Sources */, + 094BE2B52B5E94F900DFBF5A /* SynthesisEntryViewModel.swift in Sources */, 09644BAD25C98213000FFDA1 /* CoreDataSync.swift in Sources */, 09644BDE25C9833E000FFDA1 /* WallabagSession.swift in Sources */, 09644C7125C985A9000FFDA1 /* FontSizeSelectorView.swift in Sources */, @@ -1000,6 +1023,7 @@ 09644C7F25C985B8000FFDA1 /* ImageDownloaderPublisher.swift in Sources */, 09644CE825C986CC000FFDA1 /* ServerView.swift in Sources */, 09B8C3A62B27AD2D002AEA2C /* ChatAssistant.swift in Sources */, + 094BE2B92B5E952C00DFBF5A /* TagSuggestionViewModel.swift in Sources */, 09644CFD25C9870C000FFDA1 /* TagListFor.swift in Sources */, 09644BFA25C983F8000FFDA1 /* Podcast.swift in Sources */, 09644BAE25C98213000FFDA1 /* AppSync.swift in Sources */, From 5c22249cfd612deb438124de06bc240e63eeaf79 Mon Sep 17 00:00:00 2001 From: Maxime Marinel Date: Mon, 22 Jan 2024 13:39:45 +0100 Subject: [PATCH 3/4] Rebase --- wallabag.xcodeproj/project.pbxproj | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/wallabag.xcodeproj/project.pbxproj b/wallabag.xcodeproj/project.pbxproj index f3ca76a0..6fa735e0 100644 --- a/wallabag.xcodeproj/project.pbxproj +++ b/wallabag.xcodeproj/project.pbxproj @@ -99,10 +99,9 @@ 09AD75A32624F93D00708A1E /* ratatouille.css in Resources */ = {isa = PBXBuildFile; fileRef = 09AD759F2624F93D00708A1E /* ratatouille.css */; }; 09AD75A42624F93D00708A1E /* justify.css in Resources */ = {isa = PBXBuildFile; fileRef = 09AD75A02624F93D00708A1E /* justify.css */; }; 09AD75BE2624FEEE00708A1E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 09AD75C02624FEEE00708A1E /* Localizable.strings */; }; - 09AE6ECE29B13ABC0025DF93 /* WallabagKit in Frameworks */ = {isa = PBXBuildFile; productRef = 09AE6ECD29B13ABC0025DF93 /* WallabagKit */; }; 09B8C3A62B27AD2D002AEA2C /* ChatAssistant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B8C3A52B27AD2D002AEA2C /* ChatAssistant.swift */; }; 09B8C3A82B27AF62002AEA2C /* SynthesisEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B8C3A72B27AF62002AEA2C /* SynthesisEntryView.swift */; }; - 09AEBEA029B1D2C500050BBE /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 09AEBE9F29B1D2C500050BBE /* Factory */; }; + 09B977CD2B5E984900512851 /* WallabagKit in Frameworks */ = {isa = PBXBuildFile; productRef = 09AE6ECB29B13AAE0025DF93 /* WallabagKit */; }; 09BCBBD4282BD46A00B234CB /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BCBBD3282BD46A00B234CB /* RouterTests.swift */; }; 09BE0AF42A9F45E900193FBF /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BE0AF32A9F45E900193FBF /* View+Extension.swift */; }; 09C34C7C29B1398B00B5C927 /* SharedLib in Frameworks */ = {isa = PBXBuildFile; productRef = 09C34C7B29B1398B00B5C927 /* SharedLib */; }; @@ -275,7 +274,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 09AE6ECC29B13AAE0025DF93 /* WallabagKit in Frameworks */, + 09B977CD2B5E984900512851 /* WallabagKit in Frameworks */, 09800D8F29B9DEFE00DAB403 /* SharedLib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -709,11 +708,11 @@ 09BFB27425C8348E00E12B4D = { isa = PBXGroup; children = ( - 81505C592B5DC209003B5CDE /* Intents */, 097DD9022B4C0E78005D3930 /* openapi-generator-config.yaml */, 097DD9012B4C0E62005D3930 /* openapi.yaml */, 09B8C3A02B27AB5F002AEA2C /* Config.xcconfig */, 09AD75962624F4E800708A1E /* wallabag.entitlements */, + 81505C592B5DC209003B5CDE /* Intents */, 09644ACF25C94825000FFDA1 /* bagit */, 09644B1525C94CA6000FFDA1 /* Frameworks */, 09BFB28625C8348E00E12B4D /* App */, @@ -1493,10 +1492,6 @@ package = 097DD8F52B4C0CCC005D3930 /* XCRemoteSwiftPackageReference "swift-openapi-generator" */; productName = "plugin:OpenAPIGenerator"; }; - 09800D8C29B9DE9E00DAB403 /* SharedLib */ = { - isa = XCSwiftPackageProductDependency; - productName = SharedLib; - }; 09800D8E29B9DEFE00DAB403 /* SharedLib */ = { isa = XCSwiftPackageProductDependency; productName = SharedLib; @@ -1505,10 +1500,6 @@ isa = XCSwiftPackageProductDependency; productName = WallabagKit; }; - 09AE6ECD29B13ABC0025DF93 /* WallabagKit */ = { - isa = XCSwiftPackageProductDependency; - productName = WallabagKit; - }; 09C34C7B29B1398B00B5C927 /* SharedLib */ = { isa = XCSwiftPackageProductDependency; productName = SharedLib; From e77853d98a68327a718d8e108dc4aa2e31b7e428 Mon Sep 17 00:00:00 2001 From: Maxime Marinel Date: Tue, 23 Jan 2024 13:23:59 +0100 Subject: [PATCH 4/4] Update server + txt + format --- App/Features/AI/ChatAssistant.swift | 2 +- App/Features/WallabagPlus/WallabagPlusView.swift | 2 +- Intents/AddEntryIntent.swift | 5 ++--- Intents/WallabagIntent.swift | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/App/Features/AI/ChatAssistant.swift b/App/Features/AI/ChatAssistant.swift index 4d8c7e74..5d533bd2 100644 --- a/App/Features/AI/ChatAssistant.swift +++ b/App/Features/AI/ChatAssistant.swift @@ -12,7 +12,7 @@ struct ChatAssistant: ChatAssistantProtocol { var client: Client { get throws { try Client( - serverURL: Servers.server1(), + serverURL: Servers.server2(), transport: URLSessionTransport(), middlewares: [AuthenticationMiddleware()] ) diff --git a/App/Features/WallabagPlus/WallabagPlusView.swift b/App/Features/WallabagPlus/WallabagPlusView.swift index 7a8703d5..7d87bd3f 100644 --- a/App/Features/WallabagPlus/WallabagPlusView.swift +++ b/App/Features/WallabagPlus/WallabagPlusView.swift @@ -30,7 +30,7 @@ struct WallabagPlusView: View { .padding() } GroupBox("Privacy") { - Text("When you use wallabag Plus, your content will be sent to openAI") + Text("When you use wallabag Plus, your entry will be sent to openAI") .frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading) } Spacer() diff --git a/Intents/AddEntryIntent.swift b/Intents/AddEntryIntent.swift index 6a9be310..3cf5aa66 100644 --- a/Intents/AddEntryIntent.swift +++ b/Intents/AddEntryIntent.swift @@ -2,10 +2,9 @@ import AppIntents import WallabagKit struct AddEntryIntent: WallabagIntent { - static var title: LocalizedStringResource = "Add Entry" - static var description: IntentDescription = IntentDescription("Add entry to your instance") + static var description: IntentDescription = .init("Add entry to your instance") @Parameter(title: "Url") var url: URL @@ -18,7 +17,7 @@ struct AddEntryIntent: WallabagIntent { _ = try await kit.send(to: WallabagEntryEndpoint.add(url: url.absoluteString)) .receive(on: DispatchQueue.main) .values - .first(where: {(_: WallabagEntry) in true}) + .first(where: { (_: WallabagEntry) in true }) return .result() } } diff --git a/Intents/WallabagIntent.swift b/Intents/WallabagIntent.swift index cfbd8449..2d861f59 100644 --- a/Intents/WallabagIntent.swift +++ b/Intents/WallabagIntent.swift @@ -1,6 +1,6 @@ -import WallabagKit import AppIntents import SharedLib +import WallabagKit protocol WallabagIntent: AppIntent {}