Browse Source

图生图后台生成图片开发完毕

100Years 1 tháng trước cách đây
mục cha
commit
785f8a22f1
41 tập tin đã thay đổi với 1737 bổ sung418 xóa
  1. 56 4
      AIEmoji.xcodeproj/project.pbxproj
  2. 22 0
      AIEmoji/Assets.xcassets/Common/generated_loading.imageset/Contents.json
  3. BIN
      AIEmoji/Assets.xcassets/Common/generated_loading.imageset/generated_loading@2x.png
  4. BIN
      AIEmoji/Assets.xcassets/Common/generated_loading.imageset/generated_loading@3x.png
  5. 22 0
      AIEmoji/Assets.xcassets/Common/refresh_white.imageset/Contents.json
  6. BIN
      AIEmoji/Assets.xcassets/Common/refresh_white.imageset/refresh_white@2x.png
  7. BIN
      AIEmoji/Assets.xcassets/Common/refresh_white.imageset/refresh_white@3x.png
  8. 1 1
      AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+ChatDelegate.swift
  9. 127 0
      AIEmoji/Business/Data/TSBaseHistoryManager.swift
  10. 73 106
      AIEmoji/Business/Data/TSUserDefaultData.swift
  11. 5 1
      AIEmoji/Business/General/Ex/Notification+Ex.swift
  12. 169 0
      AIEmoji/Business/General/TSAppBtnView/TSAppBtnView.swift
  13. 2 2
      AIEmoji/Business/General/TSBigIconBrowseVC/TSBigIconBrowseVC.swift
  14. 2 2
      AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift
  15. 1 1
      AIEmoji/Business/TSAILIstVC/TSAIChangeEmoteVC/TSAIChangeEmoteVC.swift
  16. 1 0
      AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseCell.swift
  17. 2 1
      AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVC.swift
  18. 2 0
      AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVM.swift
  19. 2 2
      AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIListPhotoGeneratorBaseVC.swift
  20. 2 2
      AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateVC.swift
  21. 10 0
      AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateViewModel.swift
  22. 35 1
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift
  23. 70 6
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiItemCell.swift
  24. 2 2
      AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoBrowseVC.swift
  25. 1 1
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPBrowseVC/TSPTPBrowseVC.swift
  26. 106 37
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPGeneratorVC/TSPTPGeneratorVC.swift
  27. 45 13
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPGeneratorVC/VM/TSPTPGeneratorVM.swift
  28. 119 49
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/TSPTPInputVC.swift
  29. 22 68
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/VM/TSPTPInputVM.swift
  30. 111 0
      AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/View/TSImageGenerateView.swift
  31. 14 2
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift
  32. 1 1
      AIEmoji/Business/TSTextGeneralPictureVC/TSTextPicGennerateVC/TSTextPicGennerateVC.swift
  33. 1 1
      AIEmoji/Business/TSWallpaperVC/TSDiyKeyboardVC/TSWallpaperVC.swift
  34. 25 0
      AIEmoji/Business/VIewTool/TSGeneratorloadingView.swift
  35. 0 104
      AIEmoji/Business/VIewTool/TSViewTool.swift
  36. 3 0
      AIEmoji/Common/Purchase/TSPurchaseManager.swift
  37. 122 0
      AIEmoji/Common/Tool/OperationQueue/TSBaseOperation.swift
  38. 106 0
      AIEmoji/Common/Tool/OperationQueue/TSBaseOperationQueue.swift
  39. 213 0
      AIEmoji/Common/Tool/OperationQueue/TSGenerateBaseOperation/TSGenerateBaseOperation.swift
  40. 231 0
      AIEmoji/Common/Tool/OperationQueue/TSGenerateBaseOperation/TSGeneratePosterOperation.swift
  41. 11 11
      AIEmoji/Res/photo_to_photo_style.json

+ 56 - 4
AIEmoji.xcodeproj/project.pbxproj

@@ -97,6 +97,13 @@
 		A82D60812DB7A1E600596190 /* activePhoto.gif in Resources */ = {isa = PBXBuildFile; fileRef = A82D60802DB7A1E600596190 /* activePhoto.gif */; };
 		A82D60832DB87D1A00596190 /* TSAIExpandChangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D60822DB87D1900596190 /* TSAIExpandChangeView.swift */; };
 		A82D608B2DB9CE7E00596190 /* MXParallaxHeader+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D608A2DB9CE7A00596190 /* MXParallaxHeader+Ex.swift */; };
+		A82D60942DB9D45900596190 /* TSGenerateBaseOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D608C2DB9D45900596190 /* TSGenerateBaseOperation.swift */; };
+		A82D60952DB9D45900596190 /* TSGeneratePosterOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D608D2DB9D45900596190 /* TSGeneratePosterOperation.swift */; };
+		A82D60972DB9D45900596190 /* TSBaseOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D60912DB9D45900596190 /* TSBaseOperationQueue.swift */; };
+		A82D60982DB9D45900596190 /* TSBaseOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D60922DB9D45900596190 /* TSBaseOperation.swift */; };
+		A82D609B2DB9D83600596190 /* TSBaseHistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D609A2DB9D83500596190 /* TSBaseHistoryManager.swift */; };
+		A82D609E2DBA0FEA00596190 /* TSAppBtnView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D609D2DBA0FE300596190 /* TSAppBtnView.swift */; };
+		A82D60A02DBA1B0500596190 /* TSImageGenerateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A82D609F2DBA1B0400596190 /* TSImageGenerateView.swift */; };
 		A83404C82D9BEC0E00C140E4 /* UIFont+TSEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83404C72D9BEC0700C140E4 /* UIFont+TSEx.swift */; };
 		A83404CC2D9BEED800C140E4 /* Poppins-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A83404CB2D9BEED800C140E4 /* Poppins-BlackItalic.ttf */; };
 		A83404D12D9D16FA00C140E4 /* TSAIPhotoGeneratorBaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83404D02D9D16F800C140E4 /* TSAIPhotoGeneratorBaseVC.swift */; };
@@ -337,6 +344,13 @@
 		A82D60802DB7A1E600596190 /* activePhoto.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = activePhoto.gif; sourceTree = "<group>"; };
 		A82D60822DB87D1900596190 /* TSAIExpandChangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIExpandChangeView.swift; sourceTree = "<group>"; };
 		A82D608A2DB9CE7A00596190 /* MXParallaxHeader+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MXParallaxHeader+Ex.swift"; sourceTree = "<group>"; };
+		A82D608C2DB9D45900596190 /* TSGenerateBaseOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateBaseOperation.swift; sourceTree = "<group>"; };
+		A82D608D2DB9D45900596190 /* TSGeneratePosterOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneratePosterOperation.swift; sourceTree = "<group>"; };
+		A82D60912DB9D45900596190 /* TSBaseOperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperationQueue.swift; sourceTree = "<group>"; };
+		A82D60922DB9D45900596190 /* TSBaseOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseOperation.swift; sourceTree = "<group>"; };
+		A82D609A2DB9D83500596190 /* TSBaseHistoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseHistoryManager.swift; sourceTree = "<group>"; };
+		A82D609D2DBA0FE300596190 /* TSAppBtnView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAppBtnView.swift; sourceTree = "<group>"; };
+		A82D609F2DBA1B0400596190 /* TSImageGenerateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSImageGenerateView.swift; sourceTree = "<group>"; };
 		A83404C72D9BEC0700C140E4 /* UIFont+TSEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+TSEx.swift"; sourceTree = "<group>"; };
 		A83404CB2D9BEED800C140E4 /* Poppins-BlackItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-BlackItalic.ttf"; sourceTree = "<group>"; };
 		A83404D02D9D16F800C140E4 /* TSAIPhotoGeneratorBaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIPhotoGeneratorBaseVC.swift; sourceTree = "<group>"; };
@@ -1120,6 +1134,33 @@
 			path = MXParallaxHeader;
 			sourceTree = "<group>";
 		};
+		A82D60902DB9D45900596190 /* TSGenerateBaseOperation */ = {
+			isa = PBXGroup;
+			children = (
+				A82D608C2DB9D45900596190 /* TSGenerateBaseOperation.swift */,
+				A82D608D2DB9D45900596190 /* TSGeneratePosterOperation.swift */,
+			);
+			path = TSGenerateBaseOperation;
+			sourceTree = "<group>";
+		};
+		A82D60932DB9D45900596190 /* OperationQueue */ = {
+			isa = PBXGroup;
+			children = (
+				A82D60902DB9D45900596190 /* TSGenerateBaseOperation */,
+				A82D60912DB9D45900596190 /* TSBaseOperationQueue.swift */,
+				A82D60922DB9D45900596190 /* TSBaseOperation.swift */,
+			);
+			path = OperationQueue;
+			sourceTree = "<group>";
+		};
+		A82D609C2DBA0FDD00596190 /* TSAppBtnView */ = {
+			isa = PBXGroup;
+			children = (
+				A82D609D2DBA0FE300596190 /* TSAppBtnView.swift */,
+			);
+			path = TSAppBtnView;
+			sourceTree = "<group>";
+		};
 		A83404C62D9BEC0300C140E4 /* Ex */ = {
 			isa = PBXGroup;
 			children = (
@@ -1197,6 +1238,7 @@
 		A87587112D81702700286A66 /* Data */ = {
 			isa = PBXGroup;
 			children = (
+				A82D609A2DB9D83500596190 /* TSBaseHistoryManager.swift */,
 				A87587102D81702700286A66 /* TSUserDefaultData.swift */,
 			);
 			path = Data;
@@ -1362,6 +1404,7 @@
 		A8BA76432DA4C927000B6707 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				A82D609F2DBA1B0400596190 /* TSImageGenerateView.swift */,
 				A8BA76462DA4CC6C000B6707 /* TSPTPSelectStyleView.swift */,
 				A8BA76442DA4CB99000B6707 /* TSPTPUploadView.swift */,
 			);
@@ -1577,6 +1620,7 @@
 		A8F774CE2D38EA8C00AA6E93 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
+				A82D60932DB9D45900596190 /* OperationQueue */,
 				A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */,
 				A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */,
 				A8D638342DB10BAB00A96C0E /* CrashReporter.swift */,
@@ -1819,6 +1863,7 @@
 		A8F776402D3B75EA00AA6E93 /* General */ = {
 			isa = PBXGroup;
 			children = (
+				A82D609C2DBA0FDD00596190 /* TSAppBtnView */,
 				A85E47992D6808B30018D62D /* TSBigIconBrowseVC */,
 				A89EA6BD2D5E03CD000EB181 /* Ex */,
 				A8F776412D3B75EF00AA6E93 /* TSBottomAlertVC.swift */,
@@ -2166,12 +2211,14 @@
 				A85E47922D6728A00018D62D /* TSTextGeneralPictureVM.swift in Sources */,
 				A89EA6B82D5D7EE9000EB181 /* TSAIChatHistoryModel.swift in Sources */,
 				A80E72722D40F86000C64288 /* TSLaunchVC.swift in Sources */,
+				A82D609B2DB9D83600596190 /* TSBaseHistoryManager.swift in Sources */,
 				A8F775352D38FC9A00AA6E93 /* TSViewTool.swift in Sources */,
 				A80E72592D3FA67800C64288 /* TSWallpaperViewModel.swift in Sources */,
 				A89EA67D2D59F1AF000EB181 /* StreamPostRequest.swift in Sources */,
 				A80E722F2D3F3E1400C64288 /* TSDiyCanvasView.swift in Sources */,
 				A8F4134E2DA75E9E001E715A /* TSAboutDataVC.swift in Sources */,
 				A80E72672D409C7D00C64288 /* Template+More.swift in Sources */,
+				A82D609E2DBA0FEA00596190 /* TSAppBtnView.swift in Sources */,
 				A82D608B2DB9CE7E00596190 /* MXParallaxHeader+Ex.swift in Sources */,
 				A89EA6AC2D5B3EFB000EB181 /* TSRealmManager.swift in Sources */,
 				A8BA76772DA68619000B6707 /* TSAIListHistoryBaseVM.swift in Sources */,
@@ -2202,6 +2249,10 @@
 				A83404D12D9D16FA00C140E4 /* TSAIPhotoGeneratorBaseVC.swift in Sources */,
 				A80327BF2D81578900AF7878 /* TSPromptTextView.swift in Sources */,
 				A80EDE022D6F1CCD003CD332 /* TSPTPGeneratorVC.swift in Sources */,
+				A82D60942DB9D45900596190 /* TSGenerateBaseOperation.swift in Sources */,
+				A82D60952DB9D45900596190 /* TSGeneratePosterOperation.swift in Sources */,
+				A82D60972DB9D45900596190 /* TSBaseOperationQueue.swift in Sources */,
+				A82D60982DB9D45900596190 /* TSBaseOperation.swift in Sources */,
 				A8BA76452DA4CB9A000B6707 /* TSPTPUploadView.swift in Sources */,
 				A85E479F2D6859FA0018D62D /* TSRandomTextPicker.swift in Sources */,
 				A8BA76522DA51600000B6707 /* TSPTPImageHintVC.swift in Sources */,
@@ -2210,6 +2261,7 @@
 				A8FB02BA2D3E3BB20031A396 /* TSEmojisCoLItemCell.swift in Sources */,
 				A87587182D81814500286A66 /* TSAIThinkingView.swift in Sources */,
 				A85E47962D672ADA0018D62D /* TSTextPicGennerateVC.swift in Sources */,
+				A82D60A02DBA1B0500596190 /* TSImageGenerateView.swift in Sources */,
 				A80E72562D3F98D700C64288 /* TSDiyKeyboardViewVC.swift in Sources */,
 				A8F775032D38EA8C00AA6E93 /* GlobalImports.swift in Sources */,
 				A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */,
@@ -2326,7 +2378,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 2;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -2342,7 +2394,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.6.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -2365,7 +2417,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 2;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -2381,7 +2433,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.6.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

+ 22 - 0
AIEmoji/Assets.xcassets/Common/generated_loading.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "generated_loading@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "generated_loading@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
AIEmoji/Assets.xcassets/Common/generated_loading.imageset/generated_loading@2x.png


BIN
AIEmoji/Assets.xcassets/Common/generated_loading.imageset/generated_loading@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Common/refresh_white.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "refresh_white@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "refresh_white@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
AIEmoji/Assets.xcassets/Common/refresh_white.imageset/refresh_white@2x.png


BIN
AIEmoji/Assets.xcassets/Common/refresh_white.imageset/refresh_white@3x.png


+ 1 - 1
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController/TSChatViewController+ChatDelegate.swift

@@ -222,7 +222,7 @@ extension TSChatViewController: MessageCellDelegate {
             if case let .attributedText(text) = model.kind{
                 //拷贝文字到截切板
                 UIPasteboard.general.string = text.string
-                kSavePhotoSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
+                kSaveSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
             }
             
         case .refreshMsg:

+ 127 - 0
AIEmoji/Business/Data/TSBaseHistoryManager.swift

@@ -0,0 +1,127 @@
+//
+//  TSBaseHistoryManager.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/4/23.
+//
+
+import ObjectMapper
+// MARK: - 基础历史记录类
+class TSBaseHistoryManager<ModelType: TSBaseModel> {
+    // 子类必须重写的属性
+    var historyKey: String { fatalError("必须重写 historyKey") }
+    var exampleDataKey: String { fatalError("必须重写 exampleDataKey") }
+    var exampleModels: [ModelType] { fatalError("必须重写 exampleModels") }
+
+    func findModelID(modelID: Int)->Int?{
+        fatalError("必须重写 findModelID")
+    }
+
+    func saveModelAfterProcess(){
+        
+    }
+    
+    // 存储属性
+    private var _historyString: String {
+        get { UserDefaults.standard.string(forKey: historyKey) ?? "" }
+        set { UserDefaults.standard.set(newValue, forKey: historyKey) }
+    }
+    
+    private var _listModels: [ModelType]?
+    var listModels: [ModelType] {
+        get {
+            if _listModels == nil { loadModels() }
+            return _listModels ?? []
+        }
+        set {
+            _listModels = newValue
+        }
+    }
+    
+    // MARK: - 公共方法
+    func saveModel(model: ModelType, at index: Int = 0) {
+        listModels.insert(model, at: index)
+        saveHistory()
+        saveModelAfterProcess()
+    }
+    
+    func removeModel(model:ModelType) {
+        self.listModels.removeAll { $0 === model }
+        saveHistory()
+    }
+    
+    func removeModel(index: Int) {
+        guard index >= 0 && index < listModels.count else { return }
+        listModels.remove(at: index)
+        saveHistory()
+    }
+    
+    func removeALLModel() {
+        listModels.removeAll()
+        saveHistory()
+    }
+    
+    func replaceModel(oldID: Int, newModel: ModelType){
+        if let index = findModelID(modelID: oldID) {
+            listModels[index] = newModel
+            dePrint("\(Self.self).listModels Model replaced at index \(index)")
+        } else {
+            listModels.insert(newModel, at: 0)
+            dePrint("\(Self.self).listModels Model not found")
+        }
+        dePrint("\(Self.self).listModels.count=\(listModels.count)")
+        saveHistory()
+    }
+    
+    func replaceAndSaveModel(saveModel:ModelType,compareBlock:(ModelType,ModelType)->Bool){
+        if let index = listModels.firstIndex(where: { model in
+            compareBlock(saveModel,model)
+        }){
+            dePrint("\(Self.self).listModels Model replaced at index \(index)")
+           listModels[index] = saveModel
+            saveHistory()
+        }else{
+            self.saveModel(model: saveModel)
+        }
+    }
+    
+    func dePrintAllModel() {
+        dePrint("=======================结果查询开始======================")
+        dePrint("\(Self.self).listModels.count=\(listModels.count)")
+        for model in listModels {
+            dePrint(model.toJSON())
+        }
+        dePrint("=======================结果查询结束======================")
+    }
+    
+    // MARK: - 私有方法
+    func saveHistory() {
+        if let jsonString = listModels.toJSONString() {
+            _historyString = jsonString
+        }
+    }
+    
+    private func loadModels() {
+        if exampleModels.count > 0 {
+            // 第一次运行时插入示例数据
+            if UserDefaults.standard.string(forKey: exampleDataKey) == nil {
+                insertExampleData()
+                UserDefaults.standard.set("1", forKey: exampleDataKey)
+            }
+        }
+        debugPrint("_listModels 读取前")
+        // 从历史记录加载模型
+        if let models = Mapper<ModelType>().mapArray(JSONString: _historyString) {
+            _listModels = models
+        } else {
+            _listModels = []
+        }
+        debugPrint("_listModels 读取后")
+    }
+    
+    private func insertExampleData() {
+        if let jsonString = exampleModels.toJSONString() {
+            _historyString = jsonString
+        }
+    }
+}

+ 73 - 106
AIEmoji/Business/Data/TSUserDefaultData.swift

@@ -36,128 +36,67 @@ func getUserInfoJsonString()->String {
 
 func kHandleTSHistory(){
 
-    
+    for model in TSPTPHistory.shared.listModels {
+        if model.modelType != .example {
+            switch model.actionStatus {
+            case .running:
+                TSGeneratePTPOperationQueue.shared.creatOperation(uuid: model.uuid).getActionInfo(oldModel: model)
+            default:break
+            }
+        }
+    }
 }
 
-// MARK: - 基础历史记录类
-class TSBaseHistoryManager<ModelType: TSBaseModel> {
-    // 子类必须重写的属性
-    var historyKey: String { fatalError("必须重写 historyKey") }
-    var exampleDataKey: String { fatalError("必须重写 exampleDataKey") }
-    var exampleModels: [ModelType] { fatalError("必须重写 exampleModels") }
 
-    func findModelID(modelID: Int)->Int?{
-        fatalError("必须重写 findModelID")
-    }
-
-    func saveModelAfterProcess(){
-        
-    }
-    
-    // 存储属性
-    private var _historyString: String {
-        get { UserDefaults.standard.string(forKey: historyKey) ?? "" }
-        set { UserDefaults.standard.set(newValue, forKey: historyKey) }
-    }
-    
-    private var _listModels: [ModelType]?
-    var listModels: [ModelType] {
-        get {
-            if _listModels == nil { loadModels() }
-            return _listModels ?? []
-        }
-        set {
-            _listModels = newValue
-        }
-    }
+// MARK: - 图生图
+final class TSPTPHistory: TSBaseHistoryManager<TSActionInfoModel> {
+    static let shared = TSPTPHistory()
+    override var historyKey: String { "photoToPhotoHistoryListString" }
+    override var exampleDataKey: String { "insertPTPExampleData" }
     
-    // MARK: - 公共方法
-    func saveModel(model: ModelType, at index: Int = 0) {
-        listModels.insert(model, at: index)
-        saveHistory()
-        saveModelAfterProcess()
+    override func findModelID(modelID: Int) -> Int? {
+        return listModels.firstIndex(where: {$0.id == modelID})
     }
     
-    func removeModel(model:ModelType) {
-        self.listModels.removeAll { $0 === model }
-        saveHistory()
+    override var exampleModels: [TSActionInfoModel] {
+        [
+            createExampleModel(imageName: "ptp_example_image0"),
+            createExampleModel(imageName: "ptp_example_image1")
+        ]
     }
     
-    func removeModel(index: Int) {
-        guard index >= 0 && index < listModels.count else { return }
-        listModels.remove(at: index)
-        saveHistory()
+    func createExampleModel(imageName:String)->TSActionInfoModel{
+        let model = TSActionInfoModel()
+        model.modelType = .example
+        model.request.prompt = "Example"
+        model.request.promptSort = "Example"
+        model.request.width = 330
+        model.request.height = 440
+        model.response.resultUrl = imageName
+        return model
     }
     
-    func removeALLModel() {
-        listModels.removeAll()
-        saveHistory()
-    }
     
-    func replaceModel(oldID: Int, newModel: ModelType){
-        if let index = findModelID(modelID: oldID) {
-            listModels[index] = newModel
-            dePrint("\(Self.self).listModels Model replaced at index \(index)")
-        } else {
-            listModels.insert(newModel, at: 0)
-            dePrint("\(Self.self).listModels Model not found")
+    func insTestData(){
+        var array:[TSActionInfoModel] = []
+        debugPrint("array.count = \(array.count)")
+        for i in 0...20000 {
+            array.append(createExampleModel(imageName: "ptp_example_image0"))
         }
-        dePrint("\(Self.self).listModels.count=\(listModels.count)")
+        debugPrint("array.count = \(array.count)")
+        
+        debugPrint("listModels.count = \(listModels.count)")
+        listModels.append(contentsOf: array)
+        debugPrint("listModels.count = \(listModels.count)")
+        
+        debugPrint("saveHistory 前")
         saveHistory()
-    }
-    
-    func replaceAndSaveModel(saveModel:ModelType,compareBlock:(ModelType,ModelType)->Bool){
-        if let index = listModels.firstIndex(where: { model in
-            compareBlock(saveModel,model)
-        }){
-            dePrint("\(Self.self).listModels Model replaced at index \(index)")
-           listModels[index] = saveModel
-            saveHistory()
-        }else{
-            self.saveModel(model: saveModel)
-        }
-    }
-    
-    func dePrintAllModel() {
-        dePrint("=======================结果查询开始======================")
-        dePrint("\(Self.self).listModels.count=\(listModels.count)")
-        for model in listModels {
-            dePrint(model.toJSON())
-        }
-        dePrint("=======================结果查询结束======================")
-    }
-    
-    // MARK: - 私有方法
-    private func saveHistory() {
-        if let jsonString = listModels.toJSONString() {
-            _historyString = jsonString
-        }
-    }
-    
-    private func loadModels() {
-        if exampleModels.count > 0 {
-            // 第一次运行时插入示例数据
-            if UserDefaults.standard.string(forKey: exampleDataKey) == nil {
-                insertExampleData()
-                UserDefaults.standard.set("1", forKey: exampleDataKey)
-            }
-        }
-
-        // 从历史记录加载模型
-        if let models = Mapper<ModelType>().mapArray(JSONString: _historyString) {
-            _listModels = models
-        } else {
-            _listModels = []
-        }
-    }
-    
-    private func insertExampleData() {
-        if let jsonString = exampleModels.toJSONString() {
-            _historyString = jsonString
-        }
+        debugPrint("saveHistory 后")
     }
 }
 
+
+
 // MARK: - 新的储存方法
 // MARK: - 变老
 final class TSChangeOldAgeHistory: TSBaseHistoryManager<TSActionInfoModel> {
@@ -260,6 +199,34 @@ final class TSAIPhotoExpandHistory: TSBaseHistoryManager<TSActionInfoModel> {
     override func findModelID(modelID: Int) -> Int? {
         return listModels.firstIndex(where: {$0.id == modelID})
     }
+    
+    func insTestData(){
+        var array:[TSActionInfoModel] = []
+        debugPrint("array.count = \(array.count)")
+        for i in 0...20000 {
+            array.append(createExampleModel(imageName: "ptp_example_image0"))
+        }
+        debugPrint("array.count = \(array.count)")
+        
+        debugPrint("listModels.count = \(listModels.count)")
+        listModels.append(contentsOf: array)
+        debugPrint("listModels.count = \(listModels.count)")
+        
+        debugPrint("saveHistory 前")
+        saveHistory()
+        debugPrint("saveHistory 后")
+    }
+    
+    func createExampleModel(imageName:String)->TSActionInfoModel{
+        let model = TSActionInfoModel()
+        model.modelType = .example
+        model.request.prompt = "Example"
+        model.request.promptSort = "Example"
+        model.request.width = 330
+        model.request.height = 440
+        model.response.resultUrl = imageName
+        return model
+    }
 }
 
 

+ 5 - 1
AIEmoji/Business/General/Ex/Notification+Ex.swift

@@ -9,6 +9,10 @@ import Foundation
 
 extension Notification.Name {
     static let kApplicationWillTerminate = Notification.Name("applicationWillTerminate")
-    
+    static let kVipFreeNumChanged = Notification.Name("kVipFreeNumChanged")   //Vip免费次数发生变化
     static let kRefreshSettingView = Notification.Name("kRefreshSettingView")   //刷新设置按钮
+    
+    
+    static let kBaseOperationQueueCountChanged = Notification.Name("kBaseOperationQueueCountChanged") //任务数量放生变化
+    static let kGeneratePTPOperationChanged = Notification.Name("kGeneratePTPOperationChanged") //生成图生图任务发生变化
 }

+ 169 - 0
AIEmoji/Business/General/TSAppBtnView/TSAppBtnView.swift

@@ -0,0 +1,169 @@
+//
+//  TSAppBtnView.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/4/23.
+//
+
+
+class TSAppBtn: UIButton {
+ 
+}
+
+class TSNormalSubmitBtn: TSAppBtn {
+//    override var isEnabled: Bool {
+//       didSet {
+//           // 根据 isEnabled 的值设置 alpha
+//           self.alpha = isEnabled ? 1.0 : 0.6
+//       }
+//    }
+}
+
+class TSNormalCancelBtn: TSAppBtn {
+
+}
+
+class TSAppBtnView: TSBaseView {
+    enum ViewStyle {
+        case generate   //创造类的按钮
+    }
+    
+    var viewH:CGFloat = 64
+    var style:ViewStyle = .generate
+    var vipFreeNumType:VipFreeNumType = .none{
+        didSet{
+            updateVipView()
+        }
+    }
+    var clickBlock:(()->Void)?
+    var btnFrame:CGRect?
+    var isIconVipBlock:(()->Bool)? //vip 图片显示
+    var isClickVipBlock:(()->Bool)?  //点击是,是否需要弹出 vip
+    //###################################### Button ######################################
+    var button:UIButton = UIButton()
+
+    override func creatUI() {
+
+    }
+    
+    override func dealThings(){
+        
+    }
+
+    func setUpButton(style:ViewStyle,vipFreeNumType:VipFreeNumType = .none,btnFrame:CGRect? = nil,clickBlock: @escaping () -> Void) {
+        self.btnFrame = btnFrame
+        self.style = style
+        self.vipFreeNumType = vipFreeNumType
+        self.clickBlock = clickBlock
+        
+        contentView.removeAllSubViews()
+        
+        switch style {
+        case .generate:
+            setUpGenerate()
+            launchVipLogic()
+        }
+        
+    }
+}
+
+extension TSAppBtnView{
+    
+    func launchVipLogic(){
+        //监听 Vip 变化
+        NotificationCenter.default.addObserver(forName: .kPurchaseDidChanged, object: nil, queue: OperationQueue.main) { [weak self] notification in
+            guard let self = self else { return }
+            updateVipView()
+        }
+        
+        NotificationCenter.default.addObserver(forName: .kVipFreeNumChanged, object: nil, queue: OperationQueue.main) { [weak self] notification in
+            guard let self = self else { return }
+            if let userInfo = notification.userInfo as? [String: VipFreeNumType], let myEnum = userInfo["VipFreeNumType"] {
+                if myEnum == self.vipFreeNumType {
+                    self.updateVipView()
+                }
+            }
+        }
+        
+        updateVipView()
+    }
+
+    func updateVipView() {
+        switch style {
+        case .generate:
+            setVip(vip: isIconVipBlock?() ?? false)
+        }
+    }
+    
+    func setBtnEnabled(isEnabled:Bool) {
+        button.isEnabled = isEnabled
+//        button.alpha = isEnabled ? 1.0 : 0.6
+    }
+    
+    func setVip(vip:Bool) {
+        if vip {
+            button.setImage(UIImage(named: "btnImage_vip"), for: .normal)
+        }else{
+            button.setImage(nil, for: .normal)
+        }
+    }
+    
+}
+//创造按钮
+extension TSAppBtnView{
+
+    func setUpGenerate() {
+        button = kCreateNormalSubmitBtn(
+            title: "Generate".localized,
+            frame: CGRectMake(0, 0, k_ScreenWidth - 32, 48),
+            action: { [weak self]  in
+            guard let self = self else { return }
+            if let vc = WindowHelper.getCurrentViewController() {
+                if kJudgeVip(externalBool: isClickVipBlock?() ?? false, vc: vc) { return }  //判断 vip
+            }
+
+            clickBlock?()
+        })
+        contentView.addSubview(button)
+        button.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.equalTo(button.width)
+            make.height.equalTo(button.height)
+        }
+   
+        
+        viewH = 48
+    }
+}
+//创造按钮
+extension TSAppBtnView{
+    
+    func loadingAnimation(loading:Bool) {
+        if loading {
+            button.setImage(UIImage(named: "generated_loading"), for: .normal)
+            button.imageView?.startRotating()
+        }else {
+            button.imageView?.stopRotating()
+            updateVipView()
+        }
+    }
+    
+}
+//常用提交按钮
+func kCreateNormalSubmitBtn(title:String,frame:CGRect,action: (() -> Void)? = nil) -> UIButton {
+    let btn = TSNormalSubmitBtn()
+    btn.frame = frame
+    btn.setUpButton(title:title,font: UIFont.font(size: 16,weight: .medium),titleColor:.black,corner: frame.height/2,action: action)
+    btn.setTitleImageSpace(spacing: 8)
+
+    var buttonBgImage = UIImage(named: "submit_btn_bg")!
+    buttonBgImage = buttonBgImage.resizableImage(withCapInsets: UIEdgeInsets(top:24, left: 24, bottom: 24, right: 24), resizingMode: .stretch)
+    btn.setBackgroundImage(buttonBgImage, for: .normal)
+    
+    var buttonDisBgImage = UIImage(named: "submit_btn_dis_bg")!
+    buttonDisBgImage = buttonDisBgImage.resizableImage(withCapInsets: UIEdgeInsets(top:24, left: 24, bottom: 24, right: 24), resizingMode: .stretch)
+    btn.setBackgroundImage(buttonDisBgImage, for: .disabled)
+    
+    return btn
+}
+

+ 2 - 2
AIEmoji/Business/General/TSBigIconBrowseVC/TSBigIconBrowseVC.swift

@@ -122,7 +122,7 @@ class TSBigIconBrowseVC: TSBottomAlertVC {
         if let model = currentModel{
             //拷贝文字到截切板
             UIPasteboard.general.string = model.request.prompt
-            kSavePhotoSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
+            kSaveSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
         }else{
             kShowToastDataMissing()
         }
@@ -137,7 +137,7 @@ class TSBigIconBrowseVC: TSBottomAlertVC {
         if let image = currentImage{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView: self.view)
+                    kSaveSuccesswShared.show(atView: self.view)
                 }else{
                     debugPrint(error)
                 }

+ 2 - 2
AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift

@@ -114,7 +114,7 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
   
         if let image = currentImage{
             UIDevice.copyImage(image: image)
-            kSavePhotoSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
+            kSaveSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
 //            closePage()
         }else{
             kShowToastDataMissing()
@@ -130,7 +130,7 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
         if let image = currentImage{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView: self.view)
+                    kSaveSuccesswShared.show(atView: self.view)
 //                    self.closePage()
                 }else{
                     debugPrint(error)

+ 1 - 1
AIEmoji/Business/TSAILIstVC/TSAIChangeEmoteVC/TSAIChangeEmoteVC.swift

@@ -242,7 +242,7 @@ extension TSAIChangeEmoteVC {
                 guard let self = self else { return }
                 if success {
                     viewModel.isSavePhotoMark = true
-                    kSavePhotoSuccesswShared.show(atView:self.view)
+                    kSaveSuccesswShared.show(atView:self.view)
                 }else{
                     debugPrint(error)
                 }

+ 1 - 0
AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseCell.swift

@@ -68,6 +68,7 @@ class TSAIListHistoryBaseCell: TSBaseCollectionCell {
     
     override func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
         super.renderView(with: object, component: component, attributes: attributes)
+        debugPrint("TSAIListHistoryBaseCell renderView")
         if let itemModel = object as? TSGenmojiCoLItemModel{
             videoIconImageView.isHidden = true
             exampleView.isHidden = true

+ 2 - 1
AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVC.swift

@@ -94,8 +94,9 @@ class TSAIListHistoryBaseVC: TSBaseVC {
         collectionComponent.collectionView.snp.makeConstraints { make in
             make.edges.equalToSuperview()
         }
-        
+        debugPrint("777")
         updateView()
+        debugPrint("788")
     }
     
     

+ 2 - 0
AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVM.swift

@@ -11,6 +11,7 @@ class TSAIListHistoryBaseVM {
 
     var colDataArray:[TSComponent] = [TSComponent]()
     lazy var historySeciton: TSGenmojiCoLSectionModel = {
+        debugPrint("123")
         let sectionModel = TSGenmojiCoLSectionModel()
         sectionModel.style = .changeAgeHistory
         sectionModel.name = "History".localized
@@ -20,6 +21,7 @@ class TSAIListHistoryBaseVM {
             itemModel.dataModel = model
             sectionModel.items.append(itemModel)
         }
+        debugPrint("456")
         return sectionModel
     }()
     

+ 2 - 2
AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIListPhotoGeneratorBaseVC.swift

@@ -187,7 +187,7 @@ class TSAIListPhotoGeneratorBaseVC: TSAIPhotoGeneratorBaseVC {
                         guard let self = self else { return }
                         if success {
                             isSavePhotoMark = true
-                            kSavePhotoSuccesswShared.show(atView:self.view)
+                            kSaveSuccesswShared.show(atView:self.view)
                         }else{
                             debugPrint(error)
                         }
@@ -201,7 +201,7 @@ class TSAIListPhotoGeneratorBaseVC: TSAIPhotoGeneratorBaseVC {
                         guard let self = self else { return }
                         if success {
                             isSavePhotoMark = true
-                            kSavePhotoSuccesswShared.show(atView:self.view)
+                            kSaveSuccesswShared.show(atView:self.view)
                         }else{
                             debugPrint(error)
                         }

+ 2 - 2
AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateVC.swift

@@ -90,7 +90,7 @@ class TSGenmojiGennerateVC: TSBottomAlertVC {
     @objc override func clickCancelBtn(){
         if let image = getSuccessImage() {
             UIDevice.copyImage(image: image)
-            kSavePhotoSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
+            kSaveSuccesswShared.show(atView: self.view,text: "Copyed Successfully".localized,showViewBtn:false)
         }
     }
     
@@ -98,7 +98,7 @@ class TSGenmojiGennerateVC: TSBottomAlertVC {
         if let image = getSuccessImage() {
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView:self.view)
+                    kSaveSuccesswShared.show(atView:self.view)
                 }else{
                     debugPrint(error)
                 }

+ 10 - 0
AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateViewModel.swift

@@ -93,6 +93,16 @@ enum TSProgressState  {
             return false
         }
     }
+    
+    
+    var reloadNewData:Bool{
+        switch self {
+        case .success(_),.failed(_),.pending:
+            return true
+        default:
+            return false
+        }
+    }
 }
 
 class TSGenmojiGennerateViewModel {

+ 35 - 1
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift

@@ -37,6 +37,8 @@ class TSActionInfoModel: TSBaseModel {
     
     var videoThumbnailPath:String = ""
     var videoPath:String = ""
+    
+    var uuid:String = UUID().uuidString
     override func mapping(map: ObjectMapper.Map) {
         modelType           <- map["modelType"]
         id           <- map["id"]
@@ -52,6 +54,8 @@ class TSActionInfoModel: TSBaseModel {
         actionStatus = ActionStatus.from(status)
         videoThumbnailPath      <- map["videoThumbnailPath"]
         videoPath      <- map["videoPath"]
+        
+        uuid      <- map["uuid"]
     }
 }
 
@@ -67,6 +71,23 @@ extension TSActionInfoModel {
     var videoURL: URL {
         return videoPath.fillDocumentURL
     }
+    
+    
+    var upImageURLExpired:Bool{
+        if request.imageUrl.count == 0 {
+            return true
+        }
+        
+        if request.imageUrlTimestamp <= 0 {
+            return true
+        }
+        
+        if Date.timestampInt - request.imageUrlTimestamp > 10{//86400 {
+            return true
+        }
+        
+        return false
+    }
 }
 
 class TSActionRequestModel : TSBaseModel {
@@ -74,20 +95,33 @@ class TSActionRequestModel : TSBaseModel {
     var promptSort:String = ""  //用户自己输入的内容
     var width:Int = 0
     var height:Int = 0
+    
+    var imageUrl:String = ""
+    var imageUrlTimestamp:Int = 0
+    var style:String = ""
+    var advance:Bool = false
+    
     override func mapping(map: ObjectMapper.Map) {
         prompt              <- map["prompt"]
         promptSort          <- map["promptSort"]
         width               <- map["width"]
         height              <- map["height"]
+        
+        imageUrl            <- map["imageUrl"]
+        imageUrlTimestamp   <- map["imageUrlTimestamp"]
+        style               <- map["style"]
+        advance             <- map["advance"]
     }
 }
 
 class TSActionResponseModel : TSBaseModel {
     var resultUrl:String = ""
+    var code:Int = 0
     var vip:Bool = false
     override func mapping(map: ObjectMapper.Map) {
         resultUrl           <- map["resultUrl"]
-        vip           <- map["vip"]
+        vip                 <- map["vip"]
+        code                <- map["code"]
     }
 }
 

+ 70 - 6
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiItemCell.swift

@@ -6,7 +6,7 @@
 //
 
 class TSGenmojiItemCell: TSBaseCollectionCell {
-    
+    var itemModel:TSGenmojiCoLItemModel = TSGenmojiCoLItemModel()
     lazy var textLabel: UILabel = {
         let textLabel = UILabel.createLabel(
             text: "Example".localized,
@@ -36,7 +36,23 @@ class TSGenmojiItemCell: TSBaseCollectionCell {
         return showImageView
     }()
     
+    lazy var generateView: TSImageGenerateView = {
+        let generateView = TSImageGenerateView()
+        generateView.isHidden = true
+        generateView.refreshHandel = { [weak self]  in
+            guard let self = self else { return }
+            if itemModel.dataModel.upImageURLExpired { //任务已经过期了
+                self.actionHandler(any: "delete_task_expired")
+            }else{
+                if kJudgeVipFreeType(vipFreeNumType: .picToPic){ return }
+                TSGeneratePTPOperationQueue.shared.creatOperation(uuid: itemModel.dataModel.uuid).creatImage(oldModel: itemModel.dataModel)
+            }
+        }
+        return generateView
+    }()
+    
     override func creatUI() {
+        contentView.cornerRadius = 16.0
         contentView.addSubview(showImageView)
         showImageView.snp.makeConstraints { make in
             make.top.equalTo(0)
@@ -50,25 +66,73 @@ class TSGenmojiItemCell: TSBaseCollectionCell {
             make.leading.equalTo(8)
             make.height.equalTo(20)
         }
+        
+        contentView.addSubview(generateView)
+        generateView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
     }
     
     
     override func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
         super.renderView(with: object, component: component, attributes: attributes)
         if let itemModel = object as? TSGenmojiCoLItemModel{
+            self.itemModel = itemModel
+            self.updataActionInfoModelView(model: itemModel.dataModel)
+            if let operation = TSGeneratePTPOperationQueue.shared.findOperation(uuid: itemModel.dataModel.uuid) as? TSGeneratePTPOperation {
+                DispatchQueue.main.async {
+                    operation.currentActionInfoModelChanged = { [weak self] actionInfoModel in
+                        guard let self = self else { return }
+                        DispatchQueue.main.async {
+                            self.updataActionInfoModelView(model: actionInfoModel)
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    func updataActionInfoModelView(model:TSActionInfoModel){
+        
+        if model.modelType == .example {
+            model.actionStatus = .success
+        }
+        showImageView.image = nil
+//        dePrint("updataActionInfoModelView model.actionStatus 收到 = \(model.actionStatus)")
+        switch model.actionStatus {
+        case .pending,.running:
+            generateView.isHidden = false
+            generateView.setProgress(progress: model.percent)
+        case .success:
+            generateView.isHidden = true
+            
             if itemModel.dataModel.modelType == .example {
-                
+
                 if itemModel.style == .ptpPicHistory {
                     textLabel.text = "Example".localized
                 }
-                
+
                 exampleView.isHidden = false
-                showImageView.image = UIImage(named: itemModel.dataModel.response.resultUrl)
+                showImageView.image = UIImage(named: model.response.resultUrl)
             }else{
                 exampleView.isHidden = true
-                showImageView.setAsyncImage(urlString: itemModel.dataModel.response.resultUrl,contentMode: .scaleAspectFill,backgroundColor: .white.withAlphaComponent(0.1))
+                showImageView.setAsyncImage(urlString: model.response.resultUrl,contentMode: .scaleAspectFill,backgroundColor: .white.withAlphaComponent(0.1))
             }
+            
+        case .failed:
+            generateView.isHidden = false
+            if itemModel.dataModel.upImageURLExpired { //任务已经过期了
+                generateView.setTaskExpired()
+            }else{
+                generateView.setFail()
+            }
+            
+        }
+        
+        if generateView.isHidden == false {
+            generateView.setBgImageViewURLString(bgImageURLString: model.request.imageUrl)
         }
     }
-    
+
 }

+ 2 - 2
AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoBrowseVC.swift

@@ -137,7 +137,7 @@ class TSAIPhotoBrowseVC: TSBaseVC {
                 if let url = url {
                     PhotoManagerShared.saveVideoToAlbum(videoURL: url) { success, error in
                         if success {
-                            kSavePhotoSuccesswShared.show(atView: self.view)
+                            kSaveSuccesswShared.show(atView: self.view)
                         }else{
                             debugPrint(error)
                         }
@@ -149,7 +149,7 @@ class TSAIPhotoBrowseVC: TSBaseVC {
                 if let image = image{
                     PhotoManagerShared.saveImageToAlbum(image) { success, error in
                         if success {
-                            kSavePhotoSuccesswShared.show(atView: self.view)
+                            kSaveSuccesswShared.show(atView: self.view)
                         }else{
                             debugPrint(error)
                         }

+ 1 - 1
AIEmoji/Business/TSPTPGeneratorVC/TSPTPBrowseVC/TSPTPBrowseVC.swift

@@ -125,7 +125,7 @@ class TSPTPBrowseVC: TSBottomAlertVC {
         if let image = currentImage{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView: self.view)
+                    kSaveSuccesswShared.show(atView: self.view)
                 }else{
                     debugPrint(error)
                 }

+ 106 - 37
AIEmoji/Business/TSPTPGeneratorVC/TSPTPGeneratorVC/TSPTPGeneratorVC.swift

@@ -6,16 +6,16 @@
 //
 import Kingfisher
 class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
-    
-    var imageModel:TSActionInfoModel?
+    var infoModel:TSActionInfoModel?
     var complete:((TSActionInfoModel)->Void)
     var reloadViewBlock:(()->Void)?
     
     var progressState = TSProgressState.none
 
     var generateStyleModel:TSGenerateStyleModel
-    init(generateStyleModel:TSGenerateStyleModel,complete:@escaping ((TSActionInfoModel)->Void)) {
+    init(generateStyleModel:TSGenerateStyleModel,infoModel:TSActionInfoModel? = nil,complete:@escaping ((TSActionInfoModel)->Void)) {
         self.generateStyleModel = generateStyleModel
+        self.infoModel = infoModel
         viewModel = TSPTPGeneratorVM(generateStyleModel: generateStyleModel)
         self.complete = complete
         super.init()
@@ -30,6 +30,7 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
 
     lazy var generateInView : TSGeneratorloadingView = {
         let generateInView = TSGeneratorloadingView()
+        generateInView.backgroundGenerateBtn.addTarget(self, action: #selector(clickBackstageBtn), for: .touchUpInside)
         if generateStyleModel.advance {
             generateInView.timeLabel.text = String(format: "~ %d min".localized, 2)
             generateInView.infoLabel.text = "It is definitely worth your wait. Just watch it".localized
@@ -47,7 +48,7 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
             //旋转图片并储存
             if let image = netWorkImageView.image?.rotated(by: .degrees90) {
                 netWorkImageView.image = image
-                if let resultUrl = self.imageModel?.response.resultUrl,
+                if let resultUrl = self.infoModel?.response.resultUrl,
                    let url = URL(string: resultUrl){
                     ImageCache.default.store(image, forKey: url.cacheKey)
                     reloadViewBlock?()
@@ -74,35 +75,41 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
             make.width.equalTo(40)
             make.height.equalTo(40)
         }
+        
+        xBtn.isHidden = true
     }
     
+//    override func closePage() {
+//        if progressState.isResult {
+//            viewModel.cancelAllRequest()
+//            self.dismiss(animated: true, completion: nil)
+//        }else{
+//            TSCustomAlertController.show(in: self, config: TSCustomAlertController.AlertConfig(
+//                message: "As you leave, your generation will be interrupted and no result.".localized,
+//                messageColor: .white,
+//                messageFont: .systemFont(ofSize: 16),
+//                
+//                cancelTitle: "Leave".localized,
+//                cancelColor: .white,
+//                
+//                confirmTitle: "Wait".localized,
+//                confirmColor: .themeColor,
+//                
+//                cancelAction: { [weak self]  in
+//                    guard let self = self else { return }
+//                    print("用户点击了Leave")
+//                    viewModel.cancelAllRequest()
+//                    self.dismiss(animated: true, completion: nil)
+//                },
+//                confirmAction: {
+//                    print("用户点击了Stay")
+//                }
+//            ))
+//        }
+//    }
+    
     override func closePage() {
-        if progressState.isResult {
-            viewModel.cancelAllRequest()
-            self.dismiss(animated: true, completion: nil)
-        }else{
-            TSCustomAlertController.show(in: self, config: TSCustomAlertController.AlertConfig(
-                message: "As you leave, your generation will be interrupted and no result.".localized,
-                messageColor: .white,
-                messageFont: .systemFont(ofSize: 16),
-                
-                cancelTitle: "Leave".localized,
-                cancelColor: .white,
-                
-                confirmTitle: "Wait".localized,
-                confirmColor: .themeColor,
-                
-                cancelAction: { [weak self]  in
-                    guard let self = self else { return }
-                    print("用户点击了Leave")
-                    viewModel.cancelAllRequest()
-                    self.dismiss(animated: true, completion: nil)
-                },
-                confirmAction: {
-                    print("用户点击了Stay")
-                }
-            ))
-        }
+        self.dismiss(animated: true, completion: nil)
     }
     
     //重试
@@ -110,11 +117,25 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
         clickRegenerateBtn()
     }
     
+    @objc func clickBackstageBtn() {
+        self.dismiss(animated: true, completion: nil)
+    }
+    
     //重新生成
     @objc override func clickRegenerateBtn(){
         //判断 vip
         if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .picToPic) == false, vc: self) { return }
-        viewModel.uploadAndCreatImage()
+//        viewModel.uploadAndCreatImage()
+        
+        if let model = self.infoModel {
+            if model.actionStatus == .success  {
+                model.id = Date.timestampInt //如果是成功,且再生成一个,则更换一个 id ,作为新数据保存
+            }
+            creatOperation(infoModel: model)
+        }else {
+            uploadImageCreatOperation()
+        }
+        
     }
     
     //保存功能
@@ -122,7 +143,7 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
         if let image = getSuccessImage() {
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView:self.view)
+                    kSaveSuccesswShared.show(atView:self.view)
                 }else{
                     debugPrint(error)
                 }
@@ -131,13 +152,56 @@ class TSPTPGeneratorVC: TSAIPhotoGeneratorBaseVC {
     }
     
     override func dealThings() {
-        viewModel.uploadAndCreatImage()
-        viewModel.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
+//        viewModel.uploadAndCreatImage()
+//        viewModel.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
+//            guard let self = self else { return }
+//            self.upDateView(state: state, model: model)
+//        }.store(in: &cancellable)
+        
+        creatImage()
+    }
+    
+    
+    func creatImage() {
+        //判断 vip
+        if kJudgeVipFreeType(vipFreeNumType: .picToPic, vc: self){ return }
+        if let model = self.infoModel{
+            if model.actionStatus == .failed{
+                creatOperation(infoModel: model)
+            }else if model.response.resultUrl.count > 0 {
+                showSuccess(model: model)
+            }
+        }else{
+            uploadImageCreatOperation()
+        }
+    }
+    
+    func creatOperation(infoModel: TSActionInfoModel) {
+        let operation:TSGeneratePTPOperation = TSGeneratePTPOperationQueue.shared.creatOperation(uuid: UUID().uuidString)
+        operation.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
             guard let self = self else { return }
             self.upDateView(state: state, model: model)
         }.store(in: &cancellable)
+        operation.creatImage(oldModel: infoModel)
+        generateInView.setBackgroundGenerateBtnHidden(false)
+        xBtn.isHidden = false
     }
     
+    func uploadImageCreatOperation() {
+        let operation:TSGeneratePTPOperation = TSGeneratePTPOperationQueue.shared.creatOperation(uuid: UUID().uuidString)
+        operation.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
+            guard let self = self else { return }
+            self.upDateView(state: state, model: model)
+        }.store(in: &cancellable)
+        operation.uploadImage(generateStyleModel: viewModel.generateStyleModel) {[weak self] actionInfoModel in
+            guard let self = self else { return }
+            if let actionInfoModel = actionInfoModel{
+                operation.creatImage(oldModel: actionInfoModel)
+                generateInView.setBackgroundGenerateBtnHidden(false)
+                xBtn.isHidden = false
+            }
+        }
+    }
 }
 extension TSPTPGeneratorVC {
     
@@ -153,6 +217,7 @@ extension TSPTPGeneratorVC {
 extension TSPTPGeneratorVC {
     
     func upDateView(state:TSProgressState,model:TSActionInfoModel?){
+        infoModel = model
         progressState = state
         switch state {
             case .failed(let errorStr):
@@ -165,6 +230,8 @@ extension TSPTPGeneratorVC {
                 }
             case .progressString(let string):
                 showProgress(text: string)
+            case .none:
+                break
             default:
                 showLoading()
         }
@@ -194,6 +261,7 @@ extension TSPTPGeneratorVC {
 
         isClickTheBlankClosePage = true
         
+        xBtn.isHidden = false
         tryAgainBtn.isHidden = false
         bigSaveBtn.isHidden = true
         bottomView.isHidden = false
@@ -202,11 +270,12 @@ extension TSPTPGeneratorVC {
     }
     
     func showSuccess(model:TSActionInfoModel){
+        xBtn.isHidden = false
         generateInView.updateShowSuccess()
-        
-        imageModel = model
+
         isClickTheBlankClosePage = true
         
+        xBtn.isHidden = false
         tryAgainBtn.isHidden = false
         bigSaveBtn.isHidden = false
         bottomView.isHidden = false
@@ -216,7 +285,7 @@ extension TSPTPGeneratorVC {
         self.netWorkImageView.setAsyncImage(urlString: model.response.resultUrl,placeholder:kPlaceholderImage,backgroundColor:netWorkImageView.backgroundColor!)
         
         kPurchaseDefault.useOnceForFree(type: .picToPic)
-        if let model = imageModel {
+        if let model = infoModel {
             model.request.promptSort = viewModel.generateStyleModel.inputText
             complete(model)
         }

+ 45 - 13
AIEmoji/Business/TSPTPGeneratorVC/TSPTPGeneratorVC/VM/TSPTPGeneratorVM.swift

@@ -6,19 +6,19 @@
 //
 import Alamofire
 
-var kRandomBoolLastResult:Bool = true
-func kRandomBool() -> Bool {
-   if !kRandomBoolLastResult {
-       // 如果上一次是 false,这次必须返回 true
-       kRandomBoolLastResult = true
-       return true
-   } else {
-       // 如果上一次是 true,随机返回 true 或 false
-       let randomResult = Bool.random()
-       kRandomBoolLastResult = randomResult
-       return randomResult
-   }
-}
+//var kRandomBoolLastResult:Bool = true
+//func kRandomBool() -> Bool {
+//   if !kRandomBoolLastResult {
+//       // 如果上一次是 false,这次必须返回 true
+//       kRandomBoolLastResult = true
+//       return true
+//   } else {
+//       // 如果上一次是 true,随机返回 true 或 false
+//       let randomResult = Bool.random()
+//       kRandomBoolLastResult = randomResult
+//       return randomResult
+//   }
+//}
 
 let actionInfoDictPoster:[String:Any] = [
     "actionType":"image_create",
@@ -77,6 +77,38 @@ class TSPTPGeneratorVM {
 //        
 //    }
     
+    
+    func uploadImage(complete:(TSActionInfoModel)->Void) {
+        guard let upLoadImage = generateStyleModel.upLoadImage else { return  }
+        if let imageUrl = generateStyleModel.upLoadImageUrl,imageUrl.contains("http") {
+            creatImage()
+            return
+        }
+        
+        stopNetwork = false
+        stateDatauPblished = (.start,nil)
+        
+        stateDatauPblished = (.progressString(uploadingPhoto(progress: 0.0)),nil)
+        uploadRequest = TSNetworkShared.uploadImage(upLoadImage: upLoadImage, maxKb: imageMaxKb) { [weak self]  progress in
+            guard let self = self else { return }
+            if generatingProgress == 0 {
+                stateDatauPblished = (.progressString(uploadingPhoto(progress: progress)),nil)
+            }
+        } completion: { [weak self]  data, error in
+            guard let self = self else { return }
+            if let error = error {
+                generateStyleModel.upLoadImageUrl = nil
+                self.stateDatauPblished = (.failed(error.localizedDescription),nil)
+            }else{
+                if let string = data as? String {
+                    generateStyleModel.upLoadImageUrl = string
+                    creatImage()
+                }
+            }
+        }
+    }
+    
+    
     func uploadAndCreatImage() {
         guard let upLoadImage = generateStyleModel.upLoadImage else { return  }
         if let imageUrl = generateStyleModel.upLoadImageUrl,imageUrl.contains("http") {

+ 119 - 49
AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/TSPTPInputVC.swift

@@ -12,7 +12,8 @@ class TSPTPInputVC: TSBaseVC {
         let viewModel = TSPTPInputVM()
         viewModel.isCanGennerateBlock = { [weak self] isCan in
             guard let self = self else { return }
-            submitBtn.isEnabled = isCan
+//            submitBtn.isEnabled = isCan
+            creatBtnView.setBtnEnabled(isEnabled: isCan)
         }
         return viewModel
     }()
@@ -191,6 +192,8 @@ class TSPTPInputVC: TSBaseVC {
               automaticallyAdjustsScrollViewInsets = false
           }
         
+     
+        
         cp.sectionActionHandler = { [weak self] cellCp, indexPath in
             guard let self = self else { return }
             if let cmd = cellCp as? String, cmd == "delete"  {
@@ -202,9 +205,48 @@ class TSPTPInputVC: TSBaseVC {
             }
         }
         
+        cp.itemActionHandler = { [weak self] (object, indexPath) in
+            guard let self = self else { return }
+            //删除过期的任务
+            if let cmd = object as? String, cmd == "delete_task_expired"  {
+//                TSCustomAlertController.show(in: self, config: TSCustomAlertController.AlertConfig(
+//                    message: "This task has expired".localized,
+//                    messageColor: .white,
+//                    messageFont: .systemFont(ofSize: 16),
+//                    
+//                    cancelTitle: "Cancel".localized,
+//                    cancelColor: .white,
+//                    
+//                    confirmTitle: "Delete".localized,
+//                    confirmColor: .themeColor,
+//                    
+//                    cancelAction: {},
+//                    confirmAction: {
+                        TSPTPHistory.shared.removeModel(index: indexPath.item)
+                        self.viewModel.updateRecentData()
+                        self.collectionComponent.clear()
+                        self.collectionComponent.reloadView(with: self.viewModel.colDataArray)
+//                    }
+//                ))
+                return
+            }
+        }
+        
+        
+        
         cp.itemDidSelectedHandler = { [weak self] (object, indexPath) in
             guard let self = self else { return }
- 
+//            if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel{
+//                if let currentActionInfoModel = sections.items.safeObj(At: indexPath.item) {
+//                    let gennerateVC = TSPTPGeneratorVC(generateStyleModel: TSGenerateStyleModel(),infoModel: currentActionInfoModel.dataModel) { model in }
+//                        gennerateVC.modalPresentationStyle = .overFullScreen
+//                        gennerateVC.modalTransitionStyle = .crossDissolve
+//                    self.present(gennerateVC, animated: true)
+//                    
+//                }
+//                
+//            }
+
             if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSGenmojiCoLSectionModel{
                 var dataModelArray:[TSActionInfoModel] = []
                 for itemModel in sections.items {
@@ -221,15 +263,31 @@ class TSPTPInputVC: TSBaseVC {
         return cp
     }()
     
-    //###################################### 按钮 ######################################
-    lazy var submitBtn: UIButton = {
-        let submitBtn = kCreateNormalSubmitBtn(title: getVipText()) { [weak self]  in
+    //###################################### Button ######################################
+    lazy var creatBtnView:TSAppBtnView  = {
+        let creatBtnView = TSAppBtnView()
+        creatBtnView.setUpButton(style: .generate, vipFreeNumType: .picToPic) { [weak self] in
             guard let self = self else { return }
             generateImage()
         }
-        submitBtn.cornerRadius = 24.0
-        submitBtn.isEnabled = false
-        return submitBtn
+        creatBtnView.setBtnEnabled(isEnabled: false)
+        creatBtnView.isIconVipBlock = { [weak self]  in
+            guard let self = self else { return false}
+            var showVip = kPurchaseDefault.generateVipShow(type: .picToPic)
+            if showVip == false {
+                showVip = self.viewModel.selectedPTPStyleModel.isVip
+            }
+            return showVip
+        }
+        creatBtnView.isClickVipBlock = { [weak self]  in
+            guard let self = self else { return false}
+            var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false
+            if viewModel.selectedPTPStyleModel.isVip == true {
+                isVip = true
+            }
+            return isVip
+        }
+        return creatBtnView
     }()
     
     
@@ -249,8 +307,8 @@ class TSPTPInputVC: TSBaseVC {
             make.edges.equalToSuperview()
         }
 
-        contentView.addSubview(submitBtn)
-        submitBtn.snp.makeConstraints { make in
+        contentView.addSubview(creatBtnView)
+        creatBtnView.snp.makeConstraints { make in
             make.bottom.equalTo(-16)
             make.leading.equalTo(16)
             make.trailing.equalTo(-16)
@@ -260,28 +318,23 @@ class TSPTPInputVC: TSBaseVC {
         setUpCusStackView()
     }
     
-
-    
     override func dealThings() {
         
+        //设置colDataArray 数据
         collectionComponent.clear()
         collectionComponent.reloadView(with:viewModel.colDataArray)
         
+        //监听 vip 变化
         NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
         updateVipView()
+        
         TSAIListHintBaseVC.userDefaultsKey = "isFirstUploadImagePTP"
 
 //        // 监听键盘事件
         NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
         
-//        kDelayMainShort {
-//            let contentSize = self.collectionComponent.collectionView.collectionViewLayout.collectionViewContentSize
-//            self.collectionComponent.collectionView.snp.updateConstraints { make in
-//               make.height.equalTo(contentSize.height)
-//            }
-//        }
-        
+        //监听collectionView 的 contentSize
         collectionViewObserver = CollectionViewObserver(collectionView: collectionComponent.collectionView)
         collectionViewObserver.onContentSizeChange = {[weak self]  size in
             guard let self = self else { return }
@@ -290,6 +343,25 @@ class TSPTPInputVC: TSBaseVC {
                make.height.equalTo(size.height)
             }
         }
+        
+        //监听按钮任务生成中
+        NotificationCenter.default.addObserver(forName: .kBaseOperationQueueCountChanged, object: nil, queue: nil) { [weak self] notification in
+            guard let self = self else { return }
+            setCreatBtnEnabled()
+        }
+        
+        NotificationCenter.default.addObserver(forName: .kGeneratePTPOperationChanged, object: nil, queue: nil) { [weak self] notification in
+            guard let self = self else { return }
+            if let userInfo = notification.userInfo as? [String: Any],let state = userInfo["state"] as? TSProgressState {
+                dePrint("TSBaseOperation stateDatauPblished 收到 = \(state)")
+                if state.reloadNewData {
+                    self.viewModel.updateRecentData()
+                    collectionComponent.clear()
+                    collectionComponent.reloadView(with:viewModel.colDataArray)
+                }
+               
+            }
+        }
     }
 }
 
@@ -389,13 +461,7 @@ extension TSPTPInputVC {
     func updateVipView() {
         kExecuteOnMainThread {
             self.vipBtn.isHidden = PurchaseManager.default.isVip
-            
-            var showVip = kPurchaseDefault.generateVipShow(type: .picToPic)
-            if showVip == false {
-                showVip = self.viewModel.selectedPTPStyleModel.isVip
-            }
-            
-            kSetBtnVipIcon(btn: self.submitBtn, show: showVip)
+            self.creatBtnView.updateVipView()
         }
     }
     
@@ -413,22 +479,22 @@ extension TSPTPInputVC {
     
     func generateImage() {
         
-        var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false
-        if viewModel.selectedPTPStyleModel.isVip == true {
-            isVip = true
-        }
-        if kJudgeVip(externalBool: isVip, vc: self) { return }  //判断 vip
+//        var isVip = kPurchaseDefault.freeNumAvailable(type: .picToPic) == false
+//        if viewModel.selectedPTPStyleModel.isVip == true {
+//            isVip = true
+//        }
+//        if kJudgeVip(externalBool: isVip, vc: self) { return }  //判断 vip
         
         viewModel.selectedPTPStyleModel.upLoadImage = viewModel.upLoadImage
         viewModel.selectedPTPStyleModel.upLoadImageUrl = nil
         let gennerateVC = TSPTPGeneratorVC(generateStyleModel: viewModel.selectedPTPStyleModel) { [weak self] model in
             guard let self = self else { return }
-            if viewModel.saveModel(model:model) {
-                collectionComponent.clear()
-                collectionComponent.reloadView(with:viewModel.colDataArray)
-            }else{
-                collectionComponent.reloadData()
-            }
+//            if viewModel.saveModel(model:model) {
+//                collectionComponent.clear()
+//                collectionComponent.reloadView(with:viewModel.colDataArray)
+//            }else{
+//                collectionComponent.reloadData()
+//            }
             
             updateVipView()
         }
@@ -439,26 +505,30 @@ extension TSPTPInputVC {
         }
         kPresentModalVC(target: self, modelVC: gennerateVC,transitionStyle: .crossDissolve)
     }
+    
+    func setCreatBtnEnabled() {
+        let isAvailability = TSGeneratePTPOperationQueue.shared.isAvailability
+        if viewModel.isCanGennerate,isAvailability {
+            creatBtnView.setBtnEnabled(isEnabled: true)
+            creatBtnView.loadingAnimation(loading: false)
+            dePrint("TSTextGeneralPicVC setCreatBtnEnabled false")
+        }else{
+            dePrint("TSTextGeneralPicVC setCreatBtnEnabled = \(isAvailability)")
+            creatBtnView.setBtnEnabled(isEnabled: false)
+            creatBtnView.loadingAnimation(loading: !isAvailability)
+        }
+    }
 
 }
 
 extension TSPTPInputVC {
     @objc func keyboardWillShow(_ notification: Notification) {
         guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
-        let textView = customTextView
-//        guard let scrollView = cusStackView.scrollView else { return }
         let scrollView = cusStackView.scrollView
-        // 1. 计算键盘高度(减去安全区域)
         let keyboardHeight = keyboardFrame.height - view.safeAreaInsets.bottom
-//        dePrint("keyboardHeight = \(keyboardHeight )")
-        // 2. 直接获取 TextView 在 ScrollView 中的位置
-        let textViewFrame = scrollView.convert(textView.frame, from: textView.superview)
-//        dePrint("textViewFrame = \(textViewFrame)")
-        // 3. 计算需要滚动的距离(TextView 底部 - (屏幕高度 - 键盘高度))
-        var scrollDistance = textViewFrame.maxY - (scrollView.bounds.height - keyboardHeight)
-//        dePrint("scrollDistance = \(scrollDistance)")
-        // 4. 如果需要滚动,调整 contentOffset
-        var y = scrollDistance
+        let textViewFrame = scrollView.convert(customTextView.frame, from: customTextView.superview)
+        let scrollDistance = textViewFrame.maxY - (scrollView.bounds.height - keyboardHeight)
+        let y = scrollDistance
         scrollView.setContentOffset(CGPoint(x: 0, y: y),animated: true)
     }
     

+ 22 - 68
AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/VM/TSPTPInputVM.swift

@@ -17,7 +17,7 @@ class TSPTPInputVM {
         }
     }
     
-    var selectedStyleIndex:Int = 1
+    var selectedStyleIndex:Int = 0
     
     var upLoadImage:UIImage?{
         didSet{
@@ -25,7 +25,6 @@ class TSPTPInputVM {
         }
     }
 
-    
     //选择类型组
     lazy var ptpStyleModels: [TSGenerateStyleModel] = {
         var ptpStyleModels = [TSGenerateStyleModel]()
@@ -39,24 +38,14 @@ class TSPTPInputVM {
         
         return ptpStyleModels
     }()
-    //历史记录
-    @UserDefault(key: "photoToPhotoHistoryListString", defaultValue: "")
-    private var historyListString: String
-    
-    
-    lazy var listModelArray: [TSActionInfoModel] = {
-        if let listModelArray = Mapper<TSActionInfoModel>().mapArray(JSONString: historyListString){
-            return listModelArray
-        }
-        return []
-    }()
     
-
     lazy var historySeciton: TSGenmojiCoLSectionModel = {
         let sectionModel = TSGenmojiCoLSectionModel()
         sectionModel.style = .ptpPicHistory
         sectionModel.name = "History".localized
-        for model in listModelArray {
+        let listModels = TSPTPHistory.shared.listModels
+//        let listModels = Array(TSPTPHistory.shared.listModels.prefix(10000))
+        for model in listModels {
             let itemModel = TSGenmojiCoLItemModel()
             itemModel.style = sectionModel.style
             itemModel.dataModel = model
@@ -64,7 +53,6 @@ class TSPTPInputVM {
         }
         return sectionModel
     }()
-    
 
     var isCanGennerate:Bool {
         if upLoadImage != nil {
@@ -78,35 +66,9 @@ class TSPTPInputVM {
     
     var isCanGennerateBlock:((Bool)->Void)?
     init() {
-        if UserDefaults.standard.string(forKey: "insertPTPExampleData") == nil {
-            insertExampleData()
-            UserDefaults.standard.set("1", forKey: "insertPTPExampleData")
-            UserDefaults.standard.synchronize()
-        }
         combinedData()
     }
-    
-    func insertExampleData(){
-        let array = [
-            createExampleModel(imageName: "ptp_example_image0"),
-            createExampleModel(imageName: "ptp_example_image1")
-        ]
-        if let jsonString = array.toJSONString() {
-            historyListString = jsonString
-        }
-    }
-    
-    func createExampleModel(imageName:String)->TSActionInfoModel{
-        let model = TSActionInfoModel()
-        model.modelType = .example
-        model.request.prompt = "Example"
-        model.request.promptSort = "Example"
-        model.request.width = 330
-        model.request.height = 440
-        model.response.resultUrl = imageName
-        return model
-    }
-    
+
     func combinedData(){
         colDataArray.removeAll()
         
@@ -121,27 +83,10 @@ class TSPTPInputVM {
 }
  
 extension TSPTPInputVM {
-    //历史记录
-    var modelArray:[TSActionInfoModel]{
-        get{
-            if let modelArray = Mapper<TSActionInfoModel>().mapArray(JSONString: historyListString){
-                return modelArray
-            }
-            return []
-        }
-        set{
-            if let jsonString = newValue.toJSONString() {
-                historyListString = jsonString
-            }
-        }
-    }
-    
     //返回值,是否需要清空后刷新
     func saveModel(model:TSActionInfoModel)->Bool{
-        listModelArray.insert(model, at: 0)
-        if let jsonString = listModelArray.toJSONString() {
-            historyListString = jsonString
-        }
+        TSPTPHistory.shared.saveModel(model: model, at: 0)
+        
         var isNeed = false
         if historySeciton.items.count == 0 {
             colDataArray.append(historySeciton)
@@ -155,7 +100,6 @@ extension TSPTPInputVM {
         return isNeed
     }
     
-    
     var prompt:String{
         var prompt = ""
 
@@ -166,15 +110,25 @@ extension TSPTPInputVM {
     }
 
     func removeAllHistoryList(){
-        listModelArray.removeAll()
-        if let jsonString = listModelArray.toJSONString() {
-            historyListString = jsonString
-        }
-        
+        TSPTPHistory.shared.removeALLModel()
         historySeciton.items.removeAll()
         colDataArray.removeLast()
     }
     
+    func updateRecentData() {
+        var items:[TSGenmojiCoLItemModel] = []
+        for model in TSPTPHistory.shared.listModels {
+            let itemModel = TSGenmojiCoLItemModel()
+            itemModel.style = historySeciton.style
+            itemModel.dataModel = model
+            items.append(itemModel)
+        }
+        historySeciton.items = items
+        
+     
+        colDataArray = [historySeciton]
+        
+    }
 }
 
 

+ 111 - 0
AIEmoji/Business/TSPTPGeneratorVC/TSPTPInputVC/View/TSImageGenerateView.swift

@@ -0,0 +1,111 @@
+//
+//  TSImageGenerateView.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/4/24.
+//
+
+class TSImageGenerateView:TSBaseView {
+    
+    var refreshHandel:(()->Void)?
+    lazy var infoLabel: UILabel = {
+        let infoLabel = UILabel.createLabel(font: .font(size: 12),textColor: .white,textAlignment: .center,numberOfLines: 0)
+        return infoLabel
+    }()
+    
+    lazy var refreshBtn: TSUIExpandedTouchButton = {
+        let refreshBtn = TSUIExpandedTouchButton()
+        refreshBtn.setUpButton(image: UIImage(named: "refresh_white")){[weak self]  in
+            guard let self = self else { return }
+            refreshHandel?()
+        }
+        refreshBtn.isHidden = true
+        return refreshBtn
+    }()
+    
+    lazy var blurEffect: UIVisualEffectView = {
+        let blurEffect = createBlurEffectView(style: .dark)
+        blurEffect.alpha = 0.8
+        return blurEffect
+    }()
+    
+    private var bgImageURLString:String?
+    lazy var bgImageView: UIImageView = {
+        let bgImageView = UIImageView.createImageView(contentMode: .scaleAspectFill)
+        bgImageView.addSubview(blurEffect)
+        return bgImageView
+    }()
+    
+    
+    var titleTop:CGFloat = 86.0 {
+        didSet{
+            infoLabel.snp.updateConstraints { make in
+                make.top.equalTo(titleTop)
+            }
+        }
+    }
+    
+    override func creatUI() {
+        backgroundColor = .assist
+
+        contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickContentView)))
+
+        contentView.addSubview(bgImageView)
+        bgImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        contentView.addSubview(infoLabel)
+        infoLabel.snp.makeConstraints { make in
+            make.top.equalTo(titleTop)
+            make.leading.equalTo(8)
+            make.trailing.equalTo(-8)
+        }
+        
+        contentView.addSubview(refreshBtn)
+        refreshBtn.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.top.equalTo(infoLabel.snp.bottom).offset(8)
+            make.width.height.equalTo(20.0)
+        }
+        
+    }
+    
+    @objc func clickContentView() {
+        if refreshBtn.isHidden == false{
+            refreshHandel?()
+        }
+    }
+    func setProgress(progress:Float) {
+        refreshBtn.isHidden = true
+        let progressInt = Int(progress*100)
+
+        infoLabel.text = "Generated you photo".localized + "\n\n\(progressInt)%..."
+        infoLabel.textColor = .themeColor
+//        infoLabel.applyGradient(colors: ["#E961F6".uiColor,"#7E57F4".uiColor])
+    }
+    
+    func setFail(){
+        setProgress(progress: 0.0)
+        infoLabel.text = "Generation Failed".localized
+        infoLabel.textColor = .white
+        refreshBtn.isHidden = false
+        refreshBtn.setImage(UIImage(named: "refresh_white"), for: .normal)
+    }
+    
+    func setTaskExpired(){
+        setProgress(progress: 0.0)
+        infoLabel.text = "This task has expired".localized
+        infoLabel.textColor = .white
+        refreshBtn.isHidden = false
+        refreshBtn.setImage(UIImage(named: "delete_white"), for: .normal)
+    }
+    
+    func setBgImageViewURLString(bgImageURLString:String) {
+        if self.bgImageURLString == bgImageURLString {
+            return
+        }
+        self.bgImageURLString = bgImageURLString
+        bgImageView.setAsyncImage(urlString: self.bgImageURLString,contentMode: .scaleAspectFill)
+    }
+}

+ 14 - 2
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

@@ -288,14 +288,26 @@ class TSPurchaseVC: TSBaseVC {
     }
 }
 
+func kJudgeVipFreeType(vipFreeNumType:VipFreeNumType,
+               vc:UIViewController? = nil,
+               closePageBlock:(()->Void)? = nil) -> Bool {
+    //判断 vip
+    return kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: vipFreeNumType) == false, vc: vc ,closePageBlock: closePageBlock)
+}
+
+
 func kJudgeVip(externalBool:Bool,
-               vc:UIViewController,
+               vc:UIViewController? = nil,
                closePageBlock:(()->Void)? = nil) -> Bool {
     //判断 vip
     if externalBool,
        PurchaseManager.default.isVip == false
     {
-        TSPurchaseVC.show(target: vc, closePageBlock: nil)
+        if let vc = vc {
+            TSPurchaseVC.show(target: vc, closePageBlock: nil)
+        }else if let rootVC = WindowHelper.getRootViewController() {
+            TSPurchaseVC.show(target: rootVC, closePageBlock: nil)
+        }
         return true
     }
     return false

+ 1 - 1
AIEmoji/Business/TSTextGeneralPictureVC/TSTextPicGennerateVC/TSTextPicGennerateVC.swift

@@ -92,7 +92,7 @@ class TSTextPicGennerateVC: TSAIPhotoGeneratorBaseVC {
         if let image = getSuccessImage() {
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
-                    kSavePhotoSuccesswShared.show(atView:self.view)
+                    kSaveSuccesswShared.show(atView:self.view)
                 }else{
                     debugPrint(error)
                 }

+ 1 - 1
AIEmoji/Business/TSWallpaperVC/TSDiyKeyboardVC/TSWallpaperVC.swift

@@ -66,7 +66,7 @@ class TSWallpaperVC: TSBaseVC {
             if let image = getCurrentDiyImage(){
                 PhotoManagerShared.saveImageToAlbum(image) { success, error in
                     if success {
-                        kSavePhotoSuccesswShared.show(atView: self.view)
+                        kSaveSuccesswShared.show(atView: self.view)
                     }else{
                         debugPrint(error)
                     }

+ 25 - 0
AIEmoji/Business/VIewTool/TSGeneratorloadingView.swift

@@ -71,6 +71,14 @@ class TSGeneratorloadingView: TSBaseView {
         return regenerateBtn
     }()
     
+    lazy var backgroundGenerateBtn: UIButton = {
+        let btn = UIButton.createButton(title: "Generate in the background".localized,font: .font(size: 16),titleColor: .themeColor,corner: 24)
+        btn.layer.borderColor = UIColor.themeColor.cgColor
+        btn.layer.borderWidth = 1.0
+        btn.titleLabel?.adjustsFontSizeToFitWidth = true
+        return btn
+    }()
+    
     lazy var xBtn: UIButton = {
         let xBtn = UIButton.createButton(image: UIImage(named: "close_gray")) { [weak self]  in
             guard let self = self else { return }
@@ -133,6 +141,15 @@ class TSGeneratorloadingView: TSBaseView {
             make.bottom.equalTo(-20)
         }
         
+        cusStackView.addSubviewToStackWhiteBoard(backgroundGenerateBtn)
+        backgroundGenerateBtn.snp.makeConstraints { make in
+            make.top.equalTo(20)
+            make.leading.equalTo(32)
+            make.trailing.equalTo(-32)
+            make.height.equalTo(48)
+            make.bottom.equalTo(0)
+        }
+        
         cusStackView.addSubviewToStackWhiteBoard(regenerateBtn)
         regenerateBtn.snp.makeConstraints { make in
             make.top.equalTo(20)
@@ -141,6 +158,7 @@ class TSGeneratorloadingView: TSBaseView {
             make.width.equalTo(126*kDesignScale)
             make.bottom.equalTo(0)
         }
+
         
         //关闭按钮
         contentView.addSubview(xBtn)
@@ -153,6 +171,7 @@ class TSGeneratorloadingView: TSBaseView {
         
         timeLabel.superview?.isHidden = true
         infoLabel.superview?.isHidden = true
+        setBackgroundGenerateBtnHidden(true)
         
     }
 
@@ -267,11 +286,17 @@ extension TSGeneratorloadingView{
         isRotating = false
         timeLabel.superview?.isHidden = true
         infoLabel.superview?.isHidden = true
+        setBackgroundGenerateBtnHidden(true)
     }
     
     func updateShowSuccess(){
         isHidden = true
         isRotating = false
+        setBackgroundGenerateBtnHidden(true)
     }
     
+    
+    func setBackgroundGenerateBtnHidden(_ isHidden:Bool){
+        backgroundGenerateBtn.superview?.isHidden = isHidden
+    }
 }

+ 0 - 104
AIEmoji/Business/VIewTool/TSViewTool.swift

@@ -61,110 +61,6 @@ func kSetBtnVipIcon(btn:UIButton,show:Bool){
 
 let kPlaceholderImage = UIImage(named: "placeholderImage")
 
-let kSavePhotoSuccesswShared = TSSavePhotoSuccessTool.shared
-class TSSavePhotoSuccessTool {
-    
-    static let shared = TSSavePhotoSuccessTool()
-    
-    private lazy var textLabel:UILabel = {
-        let textLabel = UILabel()
-        textLabel.textColor = UIColor.white
-        textLabel.text = "Save Successfully".localized
-        textLabel.font = UIFont.font(size: 14)
-        return textLabel
-    }()
-    
-    private lazy var saveSuccessBg: UIView = {
-        return creatSaveSuccessBg()
-    }()
-    
-    
-    private lazy var viewButton:UIView = {
-        let color = "4FEA9D".uiColor
-        let viewButton = UIButton.createButton(title: "View".localized ,backgroundColor: color.withAlphaComponent(0.1),font: UIFont.font(size: 14),titleColor: color,corner: 14) {
-            if let url = URL(string: "photos-redirect://") {
-                if UIApplication.shared.canOpenURL(url) {
-                    UIApplication.shared.open(url, options: [:], completionHandler: nil)
-                    playVibration()
-                }
-            }
-        }
-        viewButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
-        return viewButton
-    }()
-    
-    func creatSaveSuccessBg() -> UIView {
-        let view = UIView()
-        view.frame = CGRect(x: 0, y: 0, width: 288, height: 48)
-        // 阴影
-        view.backgroundColor = .clear
-        view.layer.shadowColor = UIColor.black.cgColor
-        view.layer.shadowOffset = CGSize(width: 0, height: 2)
-        view.layer.shadowOpacity = 0.1
-        
-        // 圆角
-        let colorBg = UIView()
-        colorBg.backgroundColor = "#333333".uiColor
-        colorBg.layer.cornerRadius = 8
-        colorBg.layer.masksToBounds = true
-        colorBg.clipsToBounds = true
-        
-        view.addSubview(colorBg)
-        colorBg.snp.makeConstraints { make in
-            make.leading.trailing.top.bottom.equalTo(0)
-        }
-        
-        let image = UIImage(named: "success_icon")
-        let iconView = UIImageView(image: image)
-        view.addSubview(iconView)
-        iconView.snp.makeConstraints { make in
-            make.width.height.equalTo(24)
-            make.centerY.equalToSuperview()
-            make.leading.equalTo(12)
-        }
-    
-        view.addSubview(viewButton)
-        view.addSubview(textLabel)
-        viewButton.snp.makeConstraints { make in
-            make.width.equalTo(viewButton.intrinsicContentSize.width)
-            make.height.equalTo(28)
-            make.trailing.equalTo(-8)
-            make.centerY.equalToSuperview()
-        }
-    
-        textLabel.snp.makeConstraints { make in
-            make.leading.equalTo(iconView.snp.trailing).offset(8)
-            make.trailing.equalTo(viewButton.snp.leading).offset(-4)
-            make.centerY.equalToSuperview()
-        }
-
-        return view
-    }
-
-    
-    
-    func show(atView:UIView,text:String = "Save Successfully".localized,showViewBtn:Bool = true) {
-        
-        kExecuteOnMainThread {
-            self.textLabel.text = text
-            self.viewButton.isHidden = !showViewBtn
-            atView.addSubview(self.saveSuccessBg)
-            self.saveSuccessBg.snp.remakeConstraints { make in
-                make.width.equalTo(288)
-                make.height.equalTo(48)
-                make.centerX.equalToSuperview()
-                make.bottom.equalTo(-112)
-            }
-        }
-
-        DispatchQueue.main.asyncAfter(deadline: .now()+2.0) {
-            self.saveSuccessBg.removeFromSuperview()
-        }
-    }
-    
-    
-}
-
 
 func kGetScaleHeight(originalSize:CGSize,width:CGFloat) -> CGFloat {
     let originalScale = originalSize.width/originalSize.height

+ 3 - 0
AIEmoji/Common/Purchase/TSPurchaseManager.swift

@@ -17,6 +17,7 @@ public enum PremiumPeriod: String, CaseIterable {
 }
 
 public enum VipFreeNumType: String, CaseIterable {
+    case none                   = "kNone"
     case generatePic            = "kGeneratePicFreeNum"
     case aichat                 = "kAIChatFreeNum"
     case textGeneratePic        = "kTextGeneratePicFreeNum"
@@ -587,6 +588,8 @@ extension PurchaseManager {
         
         freeDict[type.rawValue] = freeNum
         saveForFree()
+        
+        NotificationCenter.default.post(name: .kVipFreeNumChanged, object: nil, userInfo: ["VipFreeNumType": type])
     }
     
     func freeNum(type:VipFreeNumType) -> Int{

+ 122 - 0
AIEmoji/Common/Tool/OperationQueue/TSBaseOperation.swift

@@ -0,0 +1,122 @@
+//
+//  TSBaseOperation.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/20.
+//
+
+import Foundation
+
+class TSBaseOperation: Operation , @unchecked Sendable{
+    
+    let uuid:String
+    
+    enum TSBaseOperationState {
+        case executing(Bool)
+        case finished(Bool)
+        case cancelled(Bool)
+    }
+    
+    @Published var operationStatePblished:TSBaseOperationState = .executing(false)
+    
+    private var _executing = false
+    private var _finished = false
+    private var _cancelled = false
+    private var _error: Error?
+    
+    required init(uuid:String) {
+        self.uuid = uuid
+        super.init()
+    }
+    
+    // MARK: - State Management
+    
+    override var isExecuting: Bool {
+        return _executing
+    }
+    
+    override var isFinished: Bool {
+        return _finished
+    }
+    
+    override var isCancelled: Bool {
+        return _cancelled
+    }
+    
+    override var isAsynchronous: Bool {
+        return true
+    }
+    
+    override func start() {
+        if isCancelled {
+            finished()
+            return
+        }
+        setExecutingValue(value: true)
+    }
+    
+    override func cancel() {
+        setCancelValue(value: true)
+        if isExecuting {
+            finished()
+        }
+    }
+    
+    func finished() {
+        if _executing {
+            setExecutingValue(value: false)
+        }
+        
+        if !_finished {
+            setFinishedValue(value: true)
+        }
+    }
+    
+    func setCancelValue(value:Bool){
+        willChangeValue(forKey: "isCancelled")
+        _cancelled = value
+        didChangeValue(forKey: "isCancelled")
+        operationStatePblished = .cancelled(value)
+    }
+    
+    func setExecutingValue(value:Bool){
+        willChangeValue(forKey: "isExecuting")
+        _executing = value
+        didChangeValue(forKey: "isExecuting")
+        operationStatePblished = .executing(value)
+    }
+    
+    func setFinishedValue(value:Bool){
+        willChangeValue(forKey: "isFinished")
+        _finished = value
+        didChangeValue(forKey: "isFinished")
+        operationStatePblished = .finished(value)
+    }
+    
+    // MARK: - Error Handling
+    
+    var error: Error? {
+        return _error
+    }
+    
+    // MARK: - Convenience Methods
+    
+    override func waitUntilFinished() {
+        while !isFinished {
+            RunLoop.current.run(mode: .default, before: .distantFuture)
+        }
+    }
+    
+    func addDependency(_ operation: TSBaseOperation) {
+        super.addDependency(operation)
+    }
+    
+    func removeDependency(_ operation: TSBaseOperation) {
+        super.removeDependency(operation)
+    }
+    
+    
+    deinit {
+        dePrint("TSBaseOperation deinit")
+    }
+}

+ 106 - 0
AIEmoji/Common/Tool/OperationQueue/TSBaseOperationQueue.swift

@@ -0,0 +1,106 @@
+//
+//  TSBaseOperationQueue.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/20.
+//
+
+import Foundation
+import Combine
+
+
+
+class TSBaseOperationQueue {
+    
+    private(set) var queue: OperationQueue = OperationQueue()
+    
+    private var activeOperations:[String:TSBaseOperation] = [:]
+    // 存储每个操作的 AnyCancellable
+    private var cancellables: [String: AnyCancellable] = [:]
+    // 存储 KVO 观察者
+    private var operationCountObservation: NSKeyValueObservation?
+    
+    var isAvailability:Bool{
+        if queue.operationCount < queue.maxConcurrentOperationCount {
+            return true
+        }
+        return false
+    }
+    
+    init(maxConcurrentOperationCount: Int = 1) {
+        queue.maxConcurrentOperationCount = maxConcurrentOperationCount
+        dePrint("TSBaseOperationQueue operationCountObservation")
+        // 监听 operationCount 的变化
+           operationCountObservation = queue.observe(\.operationCount, options: [.new]) { [weak self] (queue, change) in
+               guard let self = self else { return }
+               if let _ = change.newValue {
+                   NotificationCenter.default.post(name: .kBaseOperationQueueCountChanged, object: nil, userInfo: nil)
+               }
+           }
+    }
+
+    func creatOperation(uuid: String, type: TSBaseOperation.Type) -> TSBaseOperation {
+        if let existingOperation = activeOperations[uuid] {
+            return existingOperation
+        } else {
+            // 使用 type 参数创建操作
+            let operation = type.init(uuid: uuid)
+            activeOperations[uuid] = operation
+            queue.addOperation(operation)
+            dePrint("TSBaseOperationQueue addOperation \(operation)")
+            cancellables[uuid] = operation.$operationStatePblished.sink { [weak self] state in
+                guard let self = self else { return }
+                
+                switch state {
+                case .finished(let finished):
+                    if finished == true {
+                        clearOperationsData(uuid: uuid)
+                    }
+                case .cancelled(let cancelled):
+                    if cancelled == true {
+                        clearOperationsData(uuid: uuid)
+                    }
+                default: break
+                }
+                
+                dePrint("TSBaseOperationQueue $operationStatePblished =\(state)")
+            }
+            return operation
+        }
+    }
+    
+    func findOperation<T: TSBaseOperation>(uuid: String) -> T? {
+        if let existingOperation = activeOperations[uuid] as? T {
+            return existingOperation
+        }
+        return nil
+    }
+    
+    /// 清理所有下载任务
+    func cancelAllOperations() {
+        queue.cancelAllOperations()
+        activeOperations = [:]
+    }
+    
+    /// 清理某个下载任务
+    /// - Parameter url: 下载任务的 URL
+    func cancelOperations(uuid: String) {
+        if let operation = activeOperations[uuid] {
+            operation.cancel()
+            clearOperationsData(uuid: uuid)
+        }
+    }
+
+    func clearOperationsData(uuid: String) {
+        dePrint("TSBaseOperationQueue cancelOperations activeOperations 前=\(activeOperations)")
+        dePrint("TSBaseOperationQueue cancelOperations cancellables 前=\(cancellables)")
+        activeOperations.removeValue(forKey: uuid)
+        cancellables.removeValue(forKey: uuid)
+        dePrint("TSBaseOperationQueue cancelOperations activeOperations 后=\(activeOperations)")
+        dePrint("TSBaseOperationQueue cancelOperations cancellables 后=\(cancellables)")
+    }
+    func waitUntilAllOperationsAreFinished() {
+        queue.waitUntilAllOperationsAreFinished()
+    }
+
+}

+ 213 - 0
AIEmoji/Common/Tool/OperationQueue/TSGenerateBaseOperation/TSGenerateBaseOperation.swift

@@ -0,0 +1,213 @@
+//
+//  TSGenerateBaseOperation.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/24.
+//
+
+import Combine
+import Alamofire
+class TSGenerateBaseOperationQueue: TSBaseOperationQueue {
+    // 存储每个操作的 AnyCancellable
+    var stateables: [String: AnyCancellable] = [:]
+    
+    var generateOperationStateChanged:((String)->Void)?
+    
+    
+    override func cancelOperations(uuid: String) {
+        super.cancelOperations(uuid: uuid)
+        stateables.removeValue(forKey: uuid)
+    }
+
+    func handleStateDatauPblished(uuid:String,generateOperation: TSGenerateBaseOperation,notificationName:Notification.Name) {
+        stateables[uuid] = generateOperation.$stateDatauPblished.sink { [weak self] state in
+            guard let self = self else { return }
+            DispatchQueue.main.async {
+                self.generateOperationStateChanged?(uuid)
+                
+                let uuidData = self.getUUIDData(uuid: uuid)
+                NotificationCenter.default.post(
+                    name: notificationName,
+                    object: nil,
+                    userInfo: [
+                        "uuid": uuid,
+                        "count":self.queue.maxConcurrentOperationCount,
+                        "state":uuidData.0,
+                        "actionInfo":uuidData.1,
+                    ])
+            }
+        }
+    }
+    
+    func getUUIDData(uuid:String)->(TSProgressState,TSActionInfoModel?){
+        return (.none,TSActionInfoModel())
+    }
+    
+}
+
+class TSGenerateBaseOperation: TSBaseOperation , @unchecked Sendable{
+    
+    var actionInfoDict:[String:Any]{
+        return [:]
+    }
+    
+    @Published var stateDatauPblished:(TSProgressState,TSActionInfoModel?) = (TSProgressState.none,nil){
+        didSet{
+            dePrint("TSBaseOperation stateDatauPblished didSet = \(stateDatauPblished)")
+            if case .start = stateDatauPblished.0 {
+                start()
+            }else if stateDatauPblished.0.isResult {
+                DispatchQueue.main.asyncAfter(deadline: .now()+0.3){//稍微延迟,让通知报成功状态发送出去
+                    self.finished()
+                }
+            }
+        }
+    }
+    
+    var creatRequest:Request?
+    var queryRequest:Request?
+    var stopNetwork = false
+    var generatingProgress = 0
+    var action_id:Int = 0
+    
+    var currentActionInfoModelChanged:((TSActionInfoModel)->Void)?
+    @Published var currentActionInfoModel: TSActionInfoModel = TSActionInfoModel()
+    
+
+//    func initializeFirstCurrentActionInfoModel(oldModel:TSActionInfoModel? = nil) {
+//        if let model = oldModel {
+//            currentActionInfoModel = model
+//        }else {
+//            currentActionInfoModel.id = Int.timestampInt()
+//            currentActionInfoModel.actionStatus = .pending
+//            currentActionInfoModel.status = "pending"
+//        }
+//        replaceSaveInfoModel(model: currentActionInfoModel)
+//        stateDatauPblished = (.start,currentActionInfoModel)
+//    }
+    
+    func initializeActionInfoModel(oldModel:TSActionInfoModel) {
+        currentActionInfoModel = oldModel
+        replaceSaveInfoModel(model: currentActionInfoModel)
+        stateDatauPblished = (.start,currentActionInfoModel)
+    }
+    
+    func replaceSaveInfoModel(model:TSActionInfoModel){ }
+    
+    func handleGenerateSuccess(){
+        
+    }
+
+    func handleFailInfoModel(errorString:String?){
+        self.currentActionInfoModel.actionStatus = .failed
+        self.currentActionInfoModel.status = "failed"
+        generatingProgress = 0
+        self.replaceSaveInfoModel(model: self.currentActionInfoModel)
+        self.stateDatauPblished = (.failed(errorString ?? ""),self.currentActionInfoModel)
+    }
+    
+    func getActionInfo(oldModel:TSActionInfoModel) {
+        currentActionInfoModel = oldModel
+        self.getActionInfo(action_id:oldModel.id)
+    }
+
+    func getActionInfo(action_id:Int){
+        self.action_id = action_id
+        queryRequest = TSNetworkShared.get(urlType: .actionInfo,parameters: ["action_id":action_id]) { [weak self] data,error in
+            guard let self = self else { return }
+            if let result = kNetWorkResultSuccess(data: data) {
+                if let genmojiModel = TSActionInfoModel(JSON: result) {
+                    
+                    if genmojiModel.actionStatus != .success {
+                        self.replaceSaveInfoModel(model: genmojiModel)
+                    }
+                    
+                    switch genmojiModel.actionStatus {
+                    case .success:
+                        
+                        let successBlock = { [weak self]  in
+                            guard let self = self else { return }
+                            self.replaceSaveInfoModel(model: genmojiModel)
+                            dePrint("successBlock genmojiModel=\(genmojiModel.toJSONString())")
+                            self.stateDatauPblished = (.success(nil),genmojiModel)
+                            generatingProgress = 0
+                            self.handleGenerateSuccess()
+                        }
+                        
+                        if let url = URL(string:genmojiModel.response.resultUrl) {
+                            UIImageView.downloadImageWithProgress(urlString: genmojiModel.response.resultUrl) { [weak self]  progress in
+                                guard let self = self else { return }
+                                let progressInt = Int(progress*10.0)
+                                let progressString = "Generating".localized + " \(90 + progressInt)%"
+                                stateDatauPblished = (.progressString(progressString),nil)
+                                dePrint("生成后图片下载进度 \(progress)")
+                            } completion: { image in
+                                successBlock()
+                            }
+
+                        }else{
+                            successBlock()
+                        }
+                    case .failed:
+                        handleFailInfoModel(errorString: kNetWorkMessage(data: data) ?? "")
+                    default:
+                        stateDatauPblished = (.progressString(generating(progress: genmojiModel.percent)),nil)
+                        if stopNetwork == false {
+                            kDelayOnMainThread(2.0) {
+                                self.getActionInfo(action_id: action_id)
+                            }
+                        }
+                    }
+                    
+                    return
+                }
+            }
+ 
+            handleFailInfoModel(errorString: error?.localizedDescription)
+            
+        }
+    }
+     func generating(progress:Float) -> String {
+
+         //Generating 0%-100%
+         var progressInt = Int(progress*100)
+
+         if generatingProgress >= progressInt{
+             return getGeneratingProgressText()
+         }
+
+         if progressInt > 99 {
+             progressInt = 99
+         }
+         
+         generatingProgress = progressInt
+         return getGeneratingProgressText()
+     }
+     
+     
+    func getGeneratingProgressText()->String{
+        return "Working on your ringtone \(generatingProgress)%..."
+    }
+     
+     func cancelAllRequest(){
+         creatRequest?.cancel()
+         queryRequest?.cancel()
+         stopNetwork = true
+         
+         cancel()
+     }
+     
+}
+ var kRandomBoolLastResult:Bool = true
+func kRandomBool() -> Bool {
+    if !kRandomBoolLastResult {
+        // 如果上一次是 false,这次必须返回 true
+        kRandomBoolLastResult = true
+        return true
+    } else {
+        // 如果上一次是 true,随机返回 true 或 false
+        let randomResult = Bool.random()
+        kRandomBoolLastResult = randomResult
+        return randomResult
+    }
+}

+ 231 - 0
AIEmoji/Common/Tool/OperationQueue/TSGenerateBaseOperation/TSGeneratePosterOperation.swift

@@ -0,0 +1,231 @@
+//
+//  TSGeneratePTPOperation.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/24.
+//
+
+import Combine
+import Alamofire
+import ObjectMapper
+class TSGeneratePTPOperationQueue: TSGenerateBaseOperationQueue {
+    static let shared:TSGeneratePTPOperationQueue = TSGeneratePTPOperationQueue()
+
+    func creatOperation(uuid: String) -> TSGeneratePTPOperation {
+        let operation = super.creatOperation(uuid: uuid, type: TSGeneratePTPOperation.self)
+        handleStateDatauPblished(uuid: uuid, generateOperation: operation as! TSGenerateBaseOperation, notificationName: .kGeneratePTPOperationChanged)
+        return operation as! TSGeneratePTPOperation
+    }
+    
+    override func getUUIDData(uuid:String)->(TSProgressState,TSActionInfoModel?){
+        if let PosterOperation = TSGeneratePTPOperationQueue.shared.findOperation(uuid: uuid) as? TSGeneratePTPOperation {
+            dePrint("TSBaseOperation stateDatauPblished 发送 = \(PosterOperation.stateDatauPblished)")
+            return (PosterOperation.stateDatauPblished.0,PosterOperation.currentActionInfoModel)
+        }
+        return (.none,TSActionInfoModel())
+    }
+    
+}
+
+class TSGeneratePTPOperation: TSGenerateBaseOperation , @unchecked Sendable{
+    
+    override var actionInfoDict:[String:Any]{
+        return [
+            "actionType":"image_create",
+            "comments": "Success",
+            "costTime":9,
+            "createdTimestamp":1742183242,
+            "id":2449,
+            "percent":1,
+            "request":"{\"prompt\": \"Traditional Chinese ink painting style phoenix soaring through misty mountain peaks, dynamic black brushstrokes with crimson accents on rice paper texture, Googie architecture space station, 1950s atomic age aesthetic, curved aluminum panels, starburst patterns, glass dome observatory --edge_threshold 0.5 --atomic_age_style 0.8 --chrome_reflection 1.2\", \"width\": 800, \"height\": 1440, \"countryCode\": \"FR\"}",
+            "response": "{\"resultUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/4c946f78-b1e7-4ffe-ba18-fff26b10178c.png\"}",
+            "status":"success"
+        ]
+    }
+
+    override func replaceSaveInfoModel(model:TSActionInfoModel){
+        model.uuid = uuid
+        model.request.imageUrlTimestamp = currentActionInfoModel.request.imageUrlTimestamp
+        TSPTPHistory.shared.replaceModel(oldID: currentActionInfoModel.id, newModel: model)
+        currentActionInfoModel = model
+        dePrint("TSPosterHistory.shared.listModels.count=\(TSPTPHistory.shared.listModels.count)")
+        dePrint("model actionStatus 发出=\(model.actionStatus)")
+        currentActionInfoModelChanged?(currentActionInfoModel)
+    }
+
+    override func handleGenerateSuccess() {
+        kPurchaseDefault.useOnceForFree(type: .picToPic)
+        
+        let copyModel = self.currentActionInfoModel.copy()
+        if let rootVC = WindowHelper.getCurrentViewController() ,let cyModel = copyModel as? TSActionInfoModel {
+            kSaveSuccesswShared.show(atView: rootVC.view,text: "Successfully generated".localized,deadline: 30.0) {
+                let gennerateVC = TSPTPGeneratorVC(generateStyleModel: TSGenerateStyleModel(),infoModel: cyModel) { model in }
+                gennerateVC.modalPresentationStyle = .overFullScreen
+                gennerateVC.modalTransitionStyle = .crossDissolve
+                rootVC.present(gennerateVC, animated: true)
+            }
+        }
+    }
+    
+    //    //模拟数据
+    //    func uploadAndCreatImage() {
+    //
+    //        stateDatauPblished = (.start,nil)
+    //        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
+    //
+    //        kDelayOnMainThread(0.2) {
+    //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.2)),nil)
+    //        }
+    //
+    //        kDelayOnMainThread(0.5) {
+    //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.5)),nil)
+    //        }
+    //
+    //        kDelayOnMainThread(0.8) {
+    //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.8)),nil)
+    //        }
+    //
+    //
+    //        kDelayOnMainThread(5.0) {
+    //            if kRandomBool() {
+    //                let infoModel = TSActionInfoModel(JSON:actionInfoDictPoster )
+    //                self.stateDatauPblished = (.success(nil),infoModel)
+    //            }else{
+    //                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
+    //            }
+    //        }
+    //
+    //    }
+    
+    
+    /**
+        1.用户上传图片
+        2.调用生成接口
+        3.后台返回查询 id,根据 id 不断查询进度和结果.(此时才允许进度后台)
+     
+        利用3后台返回的TSActionRequestModel中存着关键的请求接口数据,请求数据
+     
+     */
+    
+    
+    func createActionInfoModel(generateStyleModel:TSGenerateStyleModel) -> TSActionInfoModel? {
+
+        guard let upLoadImageUrl = generateStyleModel.upLoadImageUrl else { return nil }
+
+        let infoModel = TSActionInfoModel()
+        infoModel.id = Date.timestampInt
+        infoModel.request = TSActionRequestModel()
+        infoModel.request.imageUrl = upLoadImageUrl
+        infoModel.request.imageUrlTimestamp = Date.timestampInt
+        
+        infoModel.request.prompt = generateStyleModel.prompt
+        infoModel.request.promptSort = generateStyleModel.inputText
+        infoModel.request.style = generateStyleModel.style
+        infoModel.request.advance = generateStyleModel.advance
+        
+        return infoModel
+    }
+    
+    func uploadImage(generateStyleModel:TSGenerateStyleModel,complete:@escaping (TSActionInfoModel?)->Void) {
+        
+        guard let upLoadImage = generateStyleModel.upLoadImage else { return  }
+        if let imageUrl = generateStyleModel.upLoadImageUrl,imageUrl.contains("http") {
+            complete(createActionInfoModel(generateStyleModel: generateStyleModel))
+            return
+        }
+        
+        stopNetwork = false
+        stateDatauPblished = (.start,nil)
+        
+        stateDatauPblished = (.progressString(uploadingPhoto(progress: 0.0)),nil)
+        _ = TSNetworkShared.uploadImage(upLoadImage: upLoadImage, maxKb: imageMaxKb) { [weak self]  progress in
+            guard let self = self else { return }
+            if generatingProgress == 0 {
+                stateDatauPblished = (.progressString(uploadingPhoto(progress: progress)),nil)
+            }
+        } completion: { [weak self]  data, error in
+            guard let self = self else { return }
+            if let error = error {
+                generateStyleModel.upLoadImageUrl = nil
+                self.stateDatauPblished = (.failed(error.localizedDescription),nil)
+                complete(nil)
+            }else{
+                if let string = data as? String {
+                    generateStyleModel.upLoadImageUrl = string
+                    complete(createActionInfoModel(generateStyleModel: generateStyleModel))
+                }else{
+                    complete(nil)
+                }
+            }
+        }
+    }
+    
+    func creatImage(oldModel:TSActionInfoModel) {
+        
+        initializeActionInfoModel(oldModel: oldModel)
+        if oldModel.upImageURLExpired { return  }
+
+        generatingProgress = 0
+        stopNetwork = false
+        stateDatauPblished = (.start,nil)
+        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
+        
+        let request = currentActionInfoModel.request
+        var prompt = request.prompt
+        let promptSort = request.promptSort
+        
+        if promptSort.count>0{
+            if prompt.count > 0 {
+                prompt = prompt + ", " + promptSort
+            }else {
+                prompt = promptSort
+            }
+        }
+        
+        creatRequest = TSNetworkShared.post(urlType: .imageRewrite,parameters:
+                                                ["prompt":prompt,
+                                                 "imageUrl":request.imageUrl,
+                                                 "style":request.style,
+                                                 "device":getUserInfoJsonString(),
+                                                 "advance": request.advance
+                                                ]) { [weak self] data,error in
+            guard let self = self else { return }
+            
+            if let dataDict = data as? [String:Any] ,
+               dataDict.safeInt(forKey: "code") == 200,
+               let actionId = dataDict["actionId"] as? Int{
+                if stopNetwork == false {
+                    self.stateDatauPblished = (.pending,nil) //通知首页进行更新
+                    self.getActionInfo(action_id:actionId)
+                }
+            }else{
+                handleFailInfoModel(errorString: error?.localizedDescription ?? "")
+            }
+        }
+    }
+    override func generating(progress: Float) -> String {
+        let progress = Float(progress)*(0.9) // 预留 10% 进度给图片下载
+        //Generating 0%-100%
+        var progressInt = Int(progress*100)
+
+        if progressInt > 99 {
+            progressInt = 99
+        }
+        
+        generatingProgress = progressInt
+        return "Generating".localized + " \(progressInt)%"
+    }
+
+    var imageMaxKb:Int{
+        return 10*1024
+    }
+    
+    func uploadingPhoto(progress:Float) -> String {
+        //Uploading Photo 0%-100%
+        var progressInt = Int(progress*100)
+        if progressInt > 99 {
+            progressInt = 99
+        }
+        return "Uploading Photo".localized + " \(progressInt)%"
+    }
+}

+ 11 - 11
AIEmoji/Res/photo_to_photo_style.json

@@ -1,27 +1,27 @@
 [
     {
-        "imageName": "ptp_style_ActionFigure",
-        "imageText": "Action Figure",
-        "prompt":"将上传的照片转化为一款3D可爱玩偶风格形象,整体造型卡通立体,主角位于画面正中,清哳可见,风格融合插画与 3D 质感。背景为玩具包装盒展示样式:包括透明塑料外壳、纸卡底板,纸卡上印有 「Limited  Edition」字样。图片比例 3:4。包装设计包含玩具插卡元素,如顶部挂孔、边缘裁切线等,呈现真实的商用玩具包装感。包装内右侧竖排另外放置 4 件 “配件” 道具,要根据上传的照片中人物的造型和身份推理出匹配的道具,摆放自然协调,增强整体故事感。色调以照片配色为主,饱和度高,颜色明亮清澈,光线自然温暖,画面风格现代、专业,同时保持可爱、俏皮的趣味性。背景简洁,突出主体,整体视觉呈现应具备真实商品的质感与陈列吸引力。",
-        "input":true,
+        "imageName": "ptp_style_AnimeMax",
+        "imageText": "Anime Pro",
+        "prompt":"Turn uploaded photos into ghibli style",
         "specialStyle":1,
-        "isVip": true,
-        "advance":true
+        "isVip": false,
+        "advance":true,
     },
     {
         "imageName": "ptp_style_10",
         "imageText": "Anime Lite",
         "prompt":"Studio Ghibli-inspired anime aesthetic, Hayao Miyazaki style, vibrant yet soft color palette, detailed hand-painted textures, whimsical dreamlike atmosphere, soft cel-shading, watercolor wash effect, subtle grain texture, preserving original composition. Style strength: 85%",
         "specialStyle":0,
-        "isVip": true
+        "isVip": false
     },
     {
-        "imageName": "ptp_style_AnimeMax",
-        "imageText": "Anime Pro",
-        "prompt":"Turn uploaded photos into ghibli style",
+        "imageName": "ptp_style_ActionFigure",
+        "imageText": "Action Figure",
+        "prompt":"将上传的照片转化为一款3D可爱玩偶风格形象,整体造型卡通立体,主角位于画面正中,清哳可见,风格融合插画与 3D 质感。背景为玩具包装盒展示样式:包括透明塑料外壳、纸卡底板,纸卡上印有 「Limited  Edition」字样。图片比例 3:4。包装设计包含玩具插卡元素,如顶部挂孔、边缘裁切线等,呈现真实的商用玩具包装感。包装内右侧竖排另外放置 4 件 “配件” 道具,要根据上传的照片中人物的造型和身份推理出匹配的道具,摆放自然协调,增强整体故事感。色调以照片配色为主,饱和度高,颜色明亮清澈,光线自然温暖,画面风格现代、专业,同时保持可爱、俏皮的趣味性。背景简洁,突出主体,整体视觉呈现应具备真实商品的质感与陈列吸引力。",
+        "input":true,
         "specialStyle":0,
         "isVip": true,
-        "advance":true,
+        "advance":true
     },
     {
         "imageName": "ptp_style_7",