Procházet zdrojové kódy

音乐生成逻辑.基本调试完毕

100Years před 4 týdny
rodič
revize
b3d201dbbb
22 změnil soubory, kde provedl 542 přidání a 134 odebrání
  1. 22 0
      AIRingtone/Assets.xcassets/Common/generated_loading.imageset/Contents.json
  2. binární
      AIRingtone/Assets.xcassets/Common/generated_loading.imageset/generated_loading@2x.png
  3. binární
      AIRingtone/Assets.xcassets/Common/generated_loading.imageset/generated_loading@3x.png
  4. 33 2
      AIRingtone/Business/Data/TSUserDefaultData.swift
  5. 1 1
      AIRingtone/Business/LaunchVC/TSLaunchVC.swift
  6. 6 5
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/Model/TSGeneralPicModel.swift
  7. 76 1
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift
  8. 86 37
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift
  9. 10 5
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift
  10. 34 19
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift
  11. 1 0
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift
  12. 11 0
      AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift
  13. 1 1
      AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVM.swift
  14. 22 5
      AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift
  15. 0 4
      AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/VM/TSTextGeneralRintoneVM.swift
  16. 14 1
      AIRingtone/Business/VIewTool/TSButton.swift
  17. 25 5
      AIRingtone/Business/VIewTool/TSRingToneCellView.swift
  18. 4 0
      AIRingtone/Common/Ex/Notification+TSEx.swift
  19. 11 3
      AIRingtone/Common/Tool/OperationQueue/TSBaseOperation.swift
  20. 37 18
      AIRingtone/Common/Tool/OperationQueue/TSBaseOperationQueue.swift
  21. 145 24
      AIRingtone/Common/Tool/OperationQueue/TSGenerateRintoneOperation/TSGenerateRintoneOperation.swift
  22. 3 3
      AIRingtone/Common/Tool/OperationQueue/TSGenerateRintoneOperation/TSGenerateRintoneOperationQueue.swift

+ 22 - 0
AIRingtone/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ární
AIRingtone/Assets.xcassets/Common/generated_loading.imageset/generated_loading@2x.png


binární
AIRingtone/Assets.xcassets/Common/generated_loading.imageset/generated_loading@3x.png


+ 33 - 2
AIRingtone/Business/Data/TSUserDefaultData.swift

@@ -148,15 +148,37 @@ class TSAIRintoneHistory{
     }()
     
     static func saveModel(model:TSActionInfoModel){
-        self.listModelArray.insert(model, at: 0)
+        //若存在,则不更新替换
+        if let index = self.listModelArray.firstIndex(where: { $0.id == model.id }) {
+            self.listModelArray[index] = model
+        }else{
+            self.listModelArray.insert(model, at: 0)
+        }
         self.saveHistoryString()
     }
     
     static func removeModel(model:TSActionInfoModel){
-        self.listModelArray.removeAll { $0 === model }
+        self.listModelArray.removeAll { $0.id == model.id }
         self.saveHistoryString()
     }
     
+    static func replaceModel(oldID: Int, newModel: TSActionInfoModel) {
+        if let index = self.listModelArray.firstIndex(where: {$0.id == oldID}) {
+            self.listModelArray[index] = newModel
+            dePrint("TSAIRintoneHistory.listModelArray Model replaced at index \(index)")
+        } else {
+            self.listModelArray.insert(newModel, at: 0)
+            dePrint("TSAIRintoneHistory.listModel ArrayModel not found")
+        }
+        dePrint("TSAIRintoneHistory.listModelArray.count=\(TSAIRintoneHistory.listModelArray.count)")
+        
+        if newModel.status == "success" ||
+            newModel.status == "failed" {
+            self.saveHistoryString()
+        }
+   
+    }
+    
     static func removeIndex(index:Int){
         self.listModelArray.remove(at: index)
         self.saveHistoryString()
@@ -168,6 +190,15 @@ class TSAIRintoneHistory{
         }
     }
     
+    
+    static func dePrintAllModel(){
+        dePrint("=======================结果查询开始======================")
+        dePrint("TSAIRintoneHistory.listModelArray.count=\(TSAIRintoneHistory.listModelArray.count)")
+        for model in TSAIRintoneHistory.listModelArray {
+            dePrint(model.toJSON())
+        }
+        dePrint("=======================结果查询结束======================")
+    }
 
     private static func insertExampleData(){
         let array = [

+ 1 - 1
AIRingtone/Business/LaunchVC/TSLaunchVC.swift

@@ -15,7 +15,7 @@ class TSLaunchVC: UIViewController {
     private var timer: DispatchSourceTimer?
     // 闪屏页剩余显示时长
     #if DEBUG
-    private var remindTimeInterval: TimeInterval = 1.5
+    private var remindTimeInterval: TimeInterval = 0.5
     #else
         private var remindTimeInterval: TimeInterval = 3.0
     #endif

+ 6 - 5
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/Model/TSGeneralPicModel.swift

@@ -6,8 +6,8 @@
 //
 
 import ObjectMapper
-class TSActionInfoModel: TSBaseModel {
-    
+class TSActionInfoModel: TSBaseModel{
+
     enum ModelType:Int {
         case normal
         case example
@@ -32,10 +32,10 @@ class TSActionInfoModel: TSBaseModel {
     var createdTimestamp:Int = 0
     var status:String = ""
     var costTime:Int = 0
-    var percent:Float = 0.0
-    var actionStatus:ActionStatus = .failed
-    
+    var percent:Float = 0.0 //进度
+    var actionStatus:ActionStatus = .pending
     
+    var uuid:String = UUID().uuidString
     override func mapping(map: ObjectMapper.Map) {
         modelType           <- map["modelType"]
         id           <- map["id"]
@@ -49,6 +49,7 @@ class TSActionInfoModel: TSBaseModel {
         percent     <- map["percent"]
         actionStatus      <- map["actionStatus"]
         actionStatus = ActionStatus.from(status)
+        uuid      <- map["uuid"]
     }
 }
 

+ 76 - 1
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift

@@ -62,6 +62,13 @@ class TSAIRintoneVC: TSBaseVC {
             viewModel.updateRecentData()
             updateListView()
         }
+        
+//        generalRintoneVC.refreshGeneratedBlock = { [weak self] maxNum  in
+//            guard let self = self else { return }
+//            viewModel.updateRecentData()
+//            updateGeneratedList(num:maxNum)
+//        }
+        
         return generalRintoneVC
     }()
 
@@ -108,13 +115,81 @@ class TSAIRintoneVC: TSBaseVC {
             let self = self else { return }
             collectionView.reloadData()
         }
+        
+//        NotificationCenter.default.addObserver(forName: .kGenerateRintoneOperationStart, object: nil, queue: nil) { notification in
+//            self.viewModel.updateRecentData()
+//            self.updateListView()
+//        }
+  
+        NotificationCenter.default.addObserver(forName: .kGenerateRintoneOperationChanged, object: nil, queue: nil) { notification in
+            if let userInfo = notification.userInfo as? [String: Any], let uuid = userInfo["uuid"] as? String,let state = userInfo["state"] as? TSProgressState {
+                dePrint("TSBaseOperation stateDatauPblished 收到 = \(state)")
+                switch state {
+                case .start, .success(_),.failed(_):
+                    self.viewModel.updateRecentData()
+                    self.updateGeneratedHistory()
+                default:break
+                }
+            }
+            
+//            if let userInfo = notification.userInfo as? [String: Any], let uuid = userInfo["uuid"] as? String{
+//                
+//                if let rintoneOperation = TSGenerateRintoneOperationQueue.shared.findOperation(uuid: uuid) as? TSGenerateRintoneOperation {
+//                    switch rintoneOperation.stateDatauPblished.0 {
+//                    case .start, .success(_),.failed(_):
+//                        self.viewModel.updateRecentData()
+//                        self.updateListView()
+//                    default:break
+//                    }
+//                }
+//            }
+            
+//            if let userInfo = notification.userInfo as? [String: Any], let uuid = userInfo["uuid"] as? String,let state = userInfo["state"] as? TSProgressState {
+//                self.viewModel.updateRecentData()
+//                self.updateGeneratedList(num: 1)
+//            }
+        }
+        
+        TSAIRintoneHistory.dePrintAllModel()
     }
     
     func updateListView(){
         collectionView.reloadData()
     }
+    func updateGeneratedHistory(){
+        if collectionView.numberOfSections != viewModel.modelList.count {
+            collectionView.reloadData()
+        }else if let historyModel = viewModel.modelList.first,historyModel.type == .history{
+            UIView.performWithoutAnimation {
+                collectionView.reloadSections(IndexSet(integer: 0))
+            }
+        }
+        
+    }
+    func updateGeneratedList(num:Int){
+//        ​**reloadData()**:简单易用,适合数据量小且不需要动画的场景。
+//        ​**performBatchUpdates**:更高效且支持动画,适合数据量大或需要动画的场景。
+//        ​**UICollectionViewDiffableDataSource**:iOS 13 及以上版本的最佳选择,自动处理数据变化并支持动画。
+//        let maxNum = num > 2 ? 2 : num
+        
+        if collectionView.numberOfSections != viewModel.modelList.count {
+            collectionView.reloadData()
+        }
+        if let historyModel = viewModel.modelList.first,historyModel.type == .history{
+            UIView.performWithoutAnimation {
+                collectionView.reloadSections(IndexSet(integer: 0))
+            }
+        }
+//        var array:[IndexPath] = []
+//        for i in 0..<1 {
+//            array.append(IndexPath(item: i, section: 0))
+//        }
+//        
+//        UIView.performWithoutAnimation {
+//            collectionView.reloadItems(at: array)
+//        }
+    }
     
-
 }
 
 extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {

+ 86 - 37
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift

@@ -10,7 +10,8 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
     
     public var colComponent:TSCollectionViewComponent?
     public var colAttributes:[String : Any]?
-
+    public var cancellable: [AnyCancellable] = []
+    
     override init(frame: CGRect) {
         super.init(frame: frame)
         creatUI()
@@ -35,7 +36,6 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
     
     lazy var ringView: TSRingToneCellView = {
         let ringToneView = TSRingToneCellView()
-//        ringToneView.backgroundColor = .clear
         ringToneView.clickPlayHandel = { [weak self] isplay in
             guard let self = self else { return }
             
@@ -43,7 +43,6 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
                 if TSBusinessAudioPlayer.shared.isLoading{
                     return
                 }
-
                 if TSBusinessAudioPlayer.shared.isPlaying{
                     TSBusinessAudioPlayer.shared.stop()
                 }else{
@@ -53,14 +52,24 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
             }else{
                 TSBusinessAudioPlayer.shared.playUrlString(modelUrlString,indexPath: indexPath)
             }
-            
-           
-            
         }
         return ringToneView
     }()
     
     
+    lazy var generateView: TSRingToneGenerateView = {
+        let generateView = TSRingToneGenerateView()
+        generateView.isHidden = true
+        generateView.refreshHandel = { [weak self]  in
+            guard let self = self else { return }
+            guard let oldModel = model else { return }
+            TSGenerateRintoneOperationQueue.shared.creatOperation(uuid: oldModel.uuid).creatRintone(oldModel: oldModel, prompt: oldModel.request.prompt, promptSort: oldModel.request.promptSort)
+        }
+        return generateView
+    }()
+    
+    
+    
     lazy var exampleView: UIView = {
         let exampleView = UIView()
         exampleView.backgroundColor = "#7E57F4".uiColor
@@ -92,6 +101,45 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
     var model:TSActionInfoModel?{
         didSet{
             guard let model = model else { return }
+            cancellable.removeAll()
+            if let rintoneOperation = TSGenerateRintoneOperationQueue.shared.findOperation(uuid: model.uuid) as? TSGenerateRintoneOperation {
+                DispatchQueue.main.async {
+                    rintoneOperation.currentActionInfoModelChanged =
+                    
+                    //                rintoneOperation.$currentActionInfoModel.sink
+                    { [weak self] actionInfoModel in
+                        guard let self = self else { return }
+                        DispatchQueue.main.async {
+                            self.updataActionInfoModelView(model: actionInfoModel)
+                        }
+                    }
+                    //                .store(in: &cancellable)
+                }
+            }
+            
+            updataActionInfoModelView(model: model)
+            
+            dePrint("model actionStatus 收到=\(model.actionStatus)")
+            
+           
+
+        }
+    }
+    
+    func updataActionInfoModelView(model:TSActionInfoModel){
+        
+        if model.modelType == .example {
+            model.actionStatus = .success
+        }
+        
+        switch model.actionStatus {
+        case .pending,.running:
+            generateView.isHidden = false
+            generateView.setProgress(progress: model.percent)
+        case .success:
+            cancellable.removeAll()
+            generateView.isHidden = true
+            
             ringView.timeLab.text = Float(model.request.duration).floatToMinuteSecond()
             ringView.nameLab.text = model.response.title
             ringView.setCoverImageView(urlString: model.response.coverUrl)
@@ -99,42 +147,30 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
             vipView.isHidden = true
             
             self.changePlayerState(state: TSBusinessAudioPlayer.shared.currentPlayerState)
+            
+        case .failed:
+            cancellable.removeAll()
+            generateView.isHidden = false
+            generateView.setFail()
         }
     }
     
     var ringModel:TSRingModel?{
         didSet{
             guard let ringModel = ringModel else { return }
+            
+            cancellable.removeAll()
+            
             ringView.timeLab.text = Float(ringModel.duration).floatToMinuteSecond()
             ringView.nameLab.text = ringModel.title
             ringView.setCoverImageView(urlString: "")
             exampleView.isHidden = true
+            generateView.isHidden = true
             vipView.isHidden = !ringModel.vip
             self.changePlayerState(state: TSBusinessAudioPlayer.shared.currentPlayerState)
         }
     }
-    
-    
-//    override var isSelected: Bool{
-//        didSet{
-//            debugPrint("isSelected = \(isSelected),indexPath = \(indexPath)")
-//            ringView.isloading = isSelected
-//            if isSelected, self.ringView.isPlay == false{
-//                
-//                if let model = model{
-//                    TSBusinessAudioPlayer.shared.playUrlString(model.response.musicUrl)
-//                }else if let ringModel = ringModel{
-//                    TSBusinessAudioPlayer.shared.playUrlString(ringModel.audioUrl)
-//                }
-//                
-//            }else{
-//                TSBusinessAudioPlayer.shared.stop()
-//                backgroundColor = .cardColor
-//            }
-//        }
-//    }
-
-        
+     
     var playSelf:Bool{
         return TSBusinessAudioPlayer.shared.isPlayURLString(string: modelUrlString,indexPath: indexPath)
     }
@@ -178,7 +214,12 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
             make.width.height.equalTo(24)
         }
         
-
+        
+        contentView.addSubview(generateView)
+        generateView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
     }
     
     func dealThings(){
@@ -189,9 +230,24 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
                 }
             }
         }
+        
+//        NotificationCenter.default.addObserver(forName: .kGenerateRintoneOperationChanged, object: nil, queue: nil) { notification in
+//            if let model = self.model,
+//               let userInfo = notification.userInfo as? [String: Any],
+//               let uuid = userInfo["uuid"] as? String,
+//               model.uuid == uuid,
+//               let state = userInfo["state"] as? TSProgressState,
+//               let actionInfo = userInfo["actionInfo"] as? TSActionInfoModel
+//            {
+//                self.updataActionInfoModelView(model: actionInfo)
+//            }
+//        }
     }
     
-    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        cancellable.removeAll()
+    }
     func changePlayerState(state:TSBusinessAudioPlayer.PlayerState){
             if playSelf == false {
             self.ringView.isPlay = false
@@ -236,13 +292,6 @@ extension TSAIRintoneHistoryCell : TSComponentView {
 
     }
     
-//    public var indexPath:IndexPath?{
-//        if let attributes = colAttributes , let IndexPath = attributes[kIndexPath] as? IndexPath{
-//            return IndexPath
-//        }
-//        return nil
-//    }
-//    
     public var itemActionHandler: ((Any?, IndexPath) -> Void)?{
         if let colComponent = colComponent,let itemActionHandler = colComponent.itemActionHandler {
             return itemActionHandler

+ 10 - 5
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift

@@ -10,10 +10,19 @@ extension TSGeneralRintoneVC {
     var resultIcon:UIImage?{
         return UIImage(named: "ai_rintone_icon")
     }
+    
+    func updateInfoModel(model:TSActionInfoModel?){
+        if let model = model {
+            infoModel = model
+            complete(model)
+            setRingViewData(model: model)
+        }
+    }
 
 }
 extension TSGeneralRintoneVC {
     func upDateView(state:TSProgressState,model:TSActionInfoModel?){
+        updateInfoModel(model: model)
         switch state {
             case .failed(let errorStr):
                 showError(text: errorStr)
@@ -87,10 +96,6 @@ extension TSGeneralRintoneVC {
         regenerateBtn.isHidden = false
         
         kPurchaseToolShared.useOnceForFree(type: .ringtones)
-        
-        if let model = infoModel {
-            complete(model)
-            setRingViewData(model: model)
-        }
+ 
     }
 }

+ 34 - 19
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift

@@ -10,21 +10,23 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     var infoModel:TSActionInfoModel?
     var complete:((TSActionInfoModel)->Void)
     
-    var aiText:String
-    init(aiText: String,complete:@escaping ((TSActionInfoModel)->Void)) {
-        self.aiText = aiText
+    var prompt:String
+    var promptSort:String
+    init(prompt: String,promptSort: String,complete:@escaping ((TSActionInfoModel)->Void)) {
+        self.prompt = prompt
+        self.promptSort = promptSort
         self.complete = complete
         super.init()
     }
     
-    lazy var viewModel: TSGeneralRintoneVM = {
-        let viewModel:TSGeneralRintoneVM = TSGeneralRintoneVM()
-        viewModel.aiText = aiText
-        return viewModel
-    }()
+//    lazy var viewModel: TSGeneralRintoneVM = {
+//        let viewModel:TSGeneralRintoneVM = TSGeneralRintoneVM()
+//        viewModel.aiText = aiText
+//        return viewModel
+//    }()
     
     lazy var audioPlayer = TSBusinessAudioPlayer.shared
-    
+    var uuidString:String = UUID().uuidString
     @MainActor required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
@@ -80,19 +82,20 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     }
     
     @objc func clickBackstageBtn() {
-        viewModel.cancelAllRequest()
+//        viewModel.cancelAllRequest()
         self.dismiss(animated: true, completion: nil)
     }
     
-    @objc override func closePage() {
-        viewModel.cancelAllRequest()
-        self.dismiss(animated: true, completion: nil)
-    }
+//    @objc override func closePage() {
+//        viewModel.cancelAllRequest()
+//        self.dismiss(animated: true, completion: nil)
+//    }
     
     override func clickAgainBtn() {
         audioPlayer.stop()
         if kPurchaseToolShared.kJudgeVipFreeType(vipFreeNumType: .ringtones, vc: self){ return }//判断 vip
-        viewModel.creatRintone(text:aiText)
+//        viewModel.creatRintone(text:aiText)
+        creatRintone()
     }
     
     @objc override func clickSubmitBtn(){
@@ -120,13 +123,25 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     
     override func dealThings() {
         if kPurchaseToolShared.kJudgeVipFreeType(vipFreeNumType: .ringtones, vc: self){ return }//判断 vip
-        viewModel.creatRintone(text: self.aiText)
-        viewModel.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
+//        viewModel.creatRintone(text: self.aiText)
+//        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)
+        creatRintone()
+        ringView.monitorPlayStateDefaultHandle()
+    }
+    
+    func creatRintone() {
+        
+        self.uuidString = UUID().uuidString
+        let operation:TSGenerateRintoneOperation = TSGenerateRintoneOperationQueue.shared.creatOperation(uuid: self.uuidString) as! TSGenerateRintoneOperation
+        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)
-    
-        ringView.monitorPlayStateDefaultHandle()
+        operation.creatRintone(oldModel: self.infoModel, prompt: prompt, promptSort: promptSort)
+
     }
 }
 

+ 1 - 0
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift

@@ -29,6 +29,7 @@ class TSGeneralRintoneVM {
     @Published var stateDatauPblished:(TSProgressState,TSActionInfoModel?) = (TSProgressState.none,nil)
     var aiText:String = ""
     var generatingProgress = 0
+
     //模拟数据
     func creatRintone(text:String) {
 

+ 11 - 0
AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift

@@ -73,6 +73,17 @@ class TSGenerateHistoryVC: TSBaseVC {
     
     override func dealThings() {
         updateListView()
+        
+        NotificationCenter.default.addObserver(forName: .kGenerateRintoneOperationChanged, object: nil, queue: nil) { notification in
+            if let userInfo = notification.userInfo as? [String: Any],let state = userInfo["state"] as? TSProgressState {
+                switch state {
+                case .start, .success(_),.failed(_):
+                    self.viewModel.updateRecentData()
+                    self.updateListView()
+                default:break
+                }
+            }
+        }
     }
     
     func updateListView(){

+ 1 - 1
AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVM.swift

@@ -34,7 +34,7 @@ class TSGenerateHistoryVM {
 extension TSGenerateHistoryVM {
     
     func updateRecentData() {
-        aiRintoneHistoryModel.list = Array(TSAIRintoneHistory.listModelArray.prefix(2))
+        aiRintoneHistoryModel.list = TSAIRintoneHistory.listModelArray
         modelList = getModelList()
     }
     

+ 22 - 5
AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift

@@ -18,12 +18,13 @@ class TSTextGeneralRintoneVC: TSBaseVC {
     }
     
     var reloadUIBlock:(()->Void)?
+    var refreshGeneratedBlock:((Int)->Void)?
     
     lazy var viewModel: TSTextGeneralRintoneVM = {
         let viewModel:TSTextGeneralRintoneVM = TSTextGeneralRintoneVM()
         viewModel.isCanGennerateBlock = { [weak self] enabled in
             guard let self = self else { return }
-            creatBtnView.setBtnEnabled(isEnabled: enabled)
+            setCreatBtnEnabled()
         }
         return viewModel
     }()
@@ -64,7 +65,6 @@ class TSTextGeneralRintoneVC: TSBaseVC {
             guard let self = self else { return }
             generateImage()
         }
-        creatBtnView.setBtnEnabled(isEnabled: false)
         return creatBtnView
     }()
     
@@ -101,7 +101,16 @@ class TSTextGeneralRintoneVC: TSBaseVC {
     }
 
     override func dealThings() {
+        let maxCount = TSGenerateRintoneOperationQueue.shared.queue.maxConcurrentOperationCount
+        NotificationCenter.default.addObserver(forName: .kBaseOperationQueueCountChanged, object: nil, queue: nil) { [weak self] notification in
+            guard let self = self else { return }
+            setCreatBtnEnabled()
+        }
 
+        TSGenerateRintoneOperationQueue.shared.rintoneOperationStateChanged = { [weak self] uuid in
+            guard let self = self else { return }
+            refreshGeneratedBlock?(maxCount)
+        }
     }
 
     @objc func clickView() {
@@ -112,15 +121,23 @@ class TSTextGeneralRintoneVC: TSBaseVC {
 extension TSTextGeneralRintoneVC {
     func generateImage() {
 
-        let gennerateVC = TSGeneralRintoneVC(aiText:viewModel.prompt)
+        let gennerateVC = TSGeneralRintoneVC(prompt:viewModel.prompt,promptSort: viewModel.prompt)
         {[weak self] model in
             guard let self = self else { return }
-            model.request.promptSort = viewModel.promptText
-            viewModel.saveModel(model:model)
             reloadUIBlock?()
         }
         
         kPresentModalVC(target: self, modelVC: gennerateVC,transitionStyle: .crossDissolve)
     }
 
+    func setCreatBtnEnabled() {
+        let isAvailability = TSGenerateRintoneOperationQueue.shared.isAvailability
+        if viewModel.isCanGennerate,isAvailability {
+            creatBtnView.setBtnEnabled(isEnabled: true)
+            creatBtnView.loadingAnimation(loading: false)
+        }else{
+            creatBtnView.setBtnEnabled(isEnabled: false)
+            creatBtnView.loadingAnimation(loading: !isAvailability)
+        }
+    }
 }

+ 0 - 4
AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/VM/TSTextGeneralRintoneVM.swift

@@ -81,8 +81,4 @@ extension TSTextGeneralRintoneVM {
         }
         return prompt
     }
-
-    func saveModel(model:TSActionInfoModel){
-        TSAIRintoneHistory.saveModel(model: model)
-    }
 }

+ 14 - 1
AIRingtone/Business/VIewTool/TSButton.swift

@@ -169,7 +169,20 @@ extension TSAppBtnView{
    
     }
 }
-
+//创造按钮
+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()

+ 25 - 5
AIRingtone/Business/VIewTool/TSRingToneCellView.swift

@@ -26,8 +26,7 @@ class TSRingToneCellView: TSBaseView {
     let nameLab = UILabel.createLabel(text: "--",font: .font(size: 14),textColor: .white)
     let timeLab = UILabel.createLabel(text:"--:--",font: .font(size: 12),textColor: .white.withAlphaComponent(0.6))
     
-    
-    
+
     lazy var clearBtn = UIButton.createButton{[weak self]  in
         guard let self = self else { return }
         clickPlayHandel?(self.isPlay)
@@ -154,6 +153,9 @@ extension TSRingToneCellView {
 
 class TSRingToneGenerateView:TSBaseView {
     
+    
+    var refreshHandel:(()->Void)?
+    
     lazy var generateProgressView: UIImageView = {
         let generateProgressView = UIImageView.createImageView(imageName: "ringGenerateProgress",contentMode: .scaleToFill)
         return generateProgressView
@@ -172,9 +174,9 @@ class TSRingToneGenerateView:TSBaseView {
     
     lazy var refreshBtn: TSUIExpandedTouchButton = {
         let refreshBtn = TSUIExpandedTouchButton()
-        refreshBtn.setUpButton(image: UIImage(named: "refresh_white")){
-            [weak self]  in
+        refreshBtn.setUpButton(image: UIImage(named: "refresh_white")){[weak self]  in
             guard let self = self else { return }
+            refreshHandel?()
         }
         refreshBtn.isHidden = true
         return refreshBtn
@@ -187,7 +189,7 @@ class TSRingToneGenerateView:TSBaseView {
         contentView.addSubview(generateProgressView)
         generateProgressView.snp.makeConstraints { make in
             make.leading.top.bottom.equalTo(0)
-            make.width.equalToSuperview().multipliedBy(0.0)
+            make.width.equalToSuperview().multipliedBy(1.0)
         }
         
         contentView.addSubview(iconImageView)
@@ -212,4 +214,22 @@ class TSRingToneGenerateView:TSBaseView {
         }
         
     }
+    
+    func setProgress(progress:Float) {
+        generateProgressView.snp.remakeConstraints{ make in
+            make.leading.top.bottom.equalTo(0)
+            make.width.equalToSuperview().multipliedBy(progress)
+        }
+        generateProgressView.isHidden = false
+        refreshBtn.isHidden = true
+        let progressInt = Int(progress*100)
+        infoLabel.text = "Working On Your Ringtone \(progressInt)%..."
+    }
+    
+    func setFail(){
+        setProgress(progress: 0.0)
+        infoLabel.text = "Generation Failed".localized
+        refreshBtn.isHidden = false
+        generateProgressView.isHidden = true
+    }
 }

+ 4 - 0
AIRingtone/Common/Ex/Notification+TSEx.swift

@@ -14,6 +14,10 @@ extension Notification.Name {
     
     
     static let kVipFreeNumChanged = Notification.Name("kVipFreeNumChanged")   //Vip免费次数发生变化
+    
+    static let kGenerateRintoneOperationStart = Notification.Name("kGenerateRintoneOperationStart") //生成铃声任务开发
+    static let kGenerateRintoneOperationChanged = Notification.Name("kGenerateRintoneOperationChanged") //生成铃声任务发生变化
+    static let kBaseOperationQueueCountChanged = Notification.Name("kBaseOperationQueueCountChanged") //任务数量放生变化
 }
 
 

+ 11 - 3
AIRingtone/Common/Tool/OperationQueue/TSBaseOperation.swift

@@ -49,7 +49,7 @@ class TSBaseOperation: Operation , @unchecked Sendable{
     
     override func start() {
         if isCancelled {
-            completeOperation()
+            finished()
             return
         }
         setExecutingValue(value: true)
@@ -58,11 +58,11 @@ class TSBaseOperation: Operation , @unchecked Sendable{
     override func cancel() {
         setCancelValue(value: true)
         if isExecuting {
-            completeOperation()
+            finished()
         }
     }
     
-    private func completeOperation() {
+    func finished() {
         if _executing {
             setExecutingValue(value: false)
         }
@@ -76,18 +76,21 @@ class TSBaseOperation: Operation , @unchecked Sendable{
         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
@@ -111,4 +114,9 @@ class TSBaseOperation: Operation , @unchecked Sendable{
     func removeDependency(_ operation: TSBaseOperation) {
         super.removeDependency(operation)
     }
+    
+    
+    deinit {
+        dePrint("TSBaseOperation deinit")
+    }
 }

+ 37 - 18
AIRingtone/Common/Tool/OperationQueue/TSBaseOperationQueue.swift

@@ -7,6 +7,9 @@
 
 import Foundation
 import Combine
+
+
+
 class TSBaseOperationQueue {
     
     private(set) var queue: OperationQueue = OperationQueue()
@@ -17,53 +20,62 @@ class TSBaseOperationQueue {
     // 存储 KVO 观察者
     private var operationCountObservation: NSKeyValueObservation?
     
-    @Published var isAvailability:Bool = true
-    private func upDateAvailability(){
-        if queue.operationCount <= queue.maxConcurrentOperationCount {
-            isAvailability = true
+    var isAvailability:Bool{
+        if queue.operationCount < queue.maxConcurrentOperationCount {
+            return true
         }
-        isAvailability = false
+        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 newValue = change.newValue {
-                   upDateAvailability()
+               if let _ = change.newValue {
+                   NotificationCenter.default.post(name: .kBaseOperationQueueCountChanged, object: nil, userInfo: nil)
                }
            }
     }
-    
-    // 获取或创建操作
-    func getOperation<T: TSBaseOperation>(uuid: String) -> T {
-        if let existingOperation = activeOperations[uuid] as? T {
+
+    func creatOperation(uuid: String, type: TSBaseOperation.Type) -> TSBaseOperation {
+        if let existingOperation = activeOperations[uuid] {
             return existingOperation
         } else {
-            let operation = T(uuid: uuid)
+            // 使用 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 {
-                        cancelOperations(uuid: uuid)
+                        clearOperationsData(uuid: uuid)
                     }
                 case .cancelled(let cancelled):
                     if cancelled == true {
-                        cancelOperations(uuid: uuid)
+                        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()
@@ -75,11 +87,18 @@ class TSBaseOperationQueue {
     func cancelOperations(uuid: String) {
         if let operation = activeOperations[uuid] {
             operation.cancel()
-            activeOperations.removeValue(forKey: uuid)
-            cancellables.removeValue(forKey: uuid)
+            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()
     }

+ 145 - 24
AIRingtone/Common/Tool/OperationQueue/TSGenerateRintoneOperation/TSGenerateRintoneOperation.swift

@@ -7,12 +7,77 @@
 
 import Combine
 import Alamofire
+
+class TSGenerateRintoneOperationQueue: TSBaseOperationQueue {
+    static let shared:TSGenerateRintoneOperationQueue = TSGenerateRintoneOperationQueue()
+    
+    
+    // 存储每个操作的 AnyCancellable
+    private var stateables: [String: AnyCancellable] = [:]
+    
+    var rintoneOperationStateChanged:((String)->Void)?
+    
+
+    func creatOperation(uuid: String) -> TSGenerateRintoneOperation {
+        let operation = super.creatOperation(uuid: uuid, type: TSGenerateRintoneOperation.self)
+        if let rintoneOperation = operation as? TSGenerateRintoneOperation {
+            stateables[uuid] = rintoneOperation.$stateDatauPblished.sink { [weak self] state in
+                guard let self = self else { return }
+                DispatchQueue.main.async {
+                    self.rintoneOperationStateChanged?(uuid)
+                    
+                    let uuidData = self.getUUIDData(uuid: uuid)
+                    NotificationCenter.default.post(
+                        name: .kGenerateRintoneOperationChanged,
+                        object: nil,
+                        userInfo: [
+                            "uuid": uuid,
+                            "count":self.queue.maxConcurrentOperationCount,
+                            "state":uuidData.0,
+                            "actionInfo":uuidData.1,
+                        ])
+                }
+            }
+        }
+        return operation as! TSGenerateRintoneOperation
+    }
+    
+    func getUUIDData(uuid:String)->(TSProgressState,TSActionInfoModel?){
+        if let rintoneOperation = TSGenerateRintoneOperationQueue.shared.findOperation(uuid: uuid) as? TSGenerateRintoneOperation {
+            dePrint("TSBaseOperation stateDatauPblished 发送 = \(rintoneOperation.stateDatauPblished)")
+            return (rintoneOperation.stateDatauPblished.0,rintoneOperation.currentActionInfoModel)
+        }
+        return (.none,TSActionInfoModel())
+    }
+    
+    override func cancelOperations(uuid: String) {
+        super.cancelOperations(uuid: uuid)
+        stateables.removeValue(forKey: uuid)
+    }
+}
+
 class TSGenerateRintoneOperation: TSBaseOperation , @unchecked Sendable{
     
+    let actionInfoDict:[String:Any] = [
+        "actionType":"music_create",
+        "comments": "Success",
+        "costTime":15,
+        "createdTimestamp":1741338454,
+        "id":1536,
+        "percent":1,
+        "request":"{\"prompt\": \"Create a Techno ringtone with a repetitive bassline, crisp hi-hats, and subtle synth textures. Use a BPM of 125-130 for a sleek, modern sound., Create a uplifting and modern music track blending Pop, Electronic, and Ambient elements. Use a BPM of 100-120, a catchy melody with synth or piano, warm harmonies, and a mix of electronic and organic sounds. Ensure a clear structure (Intro, Verse, Chorus, Outro) and a light, positive vibe suitable for background or casual listening\", \"duration\": 5}",
+        "response":"{\"coverUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/f0fb7739-a5cc-4805-9b68-b4a5890eb285.png\", \"title\": \"Neon Pulse\\\"  \\n\\\"Horizon Glow\", \"musicUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/c47d40dd-d07c-4edc-a6d9-8382438149d1.wav\"}",
+        "status":"success"
+    ]
+    
     @Published var stateDatauPblished:(TSProgressState,TSActionInfoModel?) = (TSProgressState.none,nil){
         didSet{
-            dePrint("stateDatauPblished didSet = \(stateDatauPblished)")
-            
+            dePrint("TSBaseOperation stateDatauPblished didSet = \(stateDatauPblished)")
+            if case .start = stateDatauPblished.0 {
+                start()
+            }else if stateDatauPblished.0.isResult {
+                finished()
+            }
         }
     }
     
@@ -22,29 +87,83 @@ class TSGenerateRintoneOperation: TSBaseOperation , @unchecked Sendable{
     private var generatingProgress = 0
     private var aiText:String = ""
     private var action_id:Int = 0    
+   
+    var currentActionInfoModelChanged:((TSActionInfoModel)->Void)?
+    @Published  var currentActionInfoModel: TSActionInfoModel = TSActionInfoModel()
+
+    func replaceSaveInfoModel(model:TSActionInfoModel){
+        model.uuid = uuid
+        TSAIRintoneHistory.replaceModel(oldID: currentActionInfoModel.id, newModel: model)
+        currentActionInfoModel = model
+        dePrint("TSAIRintoneHistory.listModelArray.count=\(TSAIRintoneHistory.listModelArray.count)")
+        dePrint("model actionStatus 发出=\(model.actionStatus)")
+        currentActionInfoModelChanged?(currentActionInfoModel)
+    }
+
+    //模拟数据
+    func creatRintone(oldModel:TSActionInfoModel? = nil,prompt:String,promptSort:String) {
+        stateDatauPblished = (.start,nil)
+        if let model = oldModel {
+            currentActionInfoModel = model
+        }else {
+            currentActionInfoModel.id = Int.timestampInt()
+            currentActionInfoModel.request.prompt = prompt
+            currentActionInfoModel.request.promptSort = promptSort
+            currentActionInfoModel.actionStatus = .pending
+            currentActionInfoModel.status = "pending"
+        }
+        
+        replaceSaveInfoModel(model: currentActionInfoModel)
+        stateDatauPblished = (.start,currentActionInfoModel)
+        let time = 2.0
+        
+        for i in 0..<Int(time){
+            kDelayOnMainThread(Double(i)) {
+                let progress = Float(i)/100.0
+                self.currentActionInfoModel.percent = progress
+                self.currentActionInfoModel.actionStatus = .running
+                self.currentActionInfoModel.status = "running"
+                self.replaceSaveInfoModel(model: self.currentActionInfoModel)
+                self.stateDatauPblished = (.progressString(self.generating(progress: progress)),nil)
+            }
+        }
+
+        kDelayOnMainThread(time+1.0) {
+            if Bool.random(), let infoModel = TSActionInfoModel(JSON: self.actionInfoDict){
+                infoModel.id = Int.uuid
+                self.replaceSaveInfoModel(model: infoModel)
+                self.stateDatauPblished = (.success(nil),self.currentActionInfoModel)
+            }else{
+                self.currentActionInfoModel.actionStatus = .failed
+                self.currentActionInfoModel.status = "failed"
+                self.replaceSaveInfoModel(model: self.currentActionInfoModel)
+                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
+            }
+            TSAIRintoneHistory.dePrintAllModel()
+        }
+    }
     
-   func creatRintone(text:String) {
-       generatingProgress = 0
-       aiText = text
-       let postDict:[String : Any] = [
-           "prompt":text,
-           "duration":20
-       ]
-       stateDatauPblished = (.start,nil)
-       stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-       creatRequest = TSNetworkShared.post(urlType: .musicCreate,parameters: postDict) { [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.getActionInfo(action_id:actionId)
-               }
-           }else{
-               self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
-           }
-       }
-   }
+//   func creatRintone(text:String) {
+//       generatingProgress = 0
+//       aiText = text
+//       let postDict:[String : Any] = [
+//           "prompt":text,
+//           "duration":20
+//       ]
+//       stateDatauPblished = (.start,nil)
+//       creatRequest = TSNetworkShared.post(urlType: .musicCreate,parameters: postDict) { [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.getActionInfo(action_id:actionId)
+//               }
+//           }else{
+//               self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
+//           }
+//       }
+//   }
    
    func getActionInfo(action_id:Int){
        self.action_id = action_id
@@ -97,6 +216,8 @@ class TSGenerateRintoneOperation: TSBaseOperation , @unchecked Sendable{
         creatRequest?.cancel()
         queryRequest?.cancel()
         stopNetwork = true
+        
+        cancel()
     }
     
     

+ 3 - 3
AIRingtone/Common/Tool/OperationQueue/TSGenerateRintoneOperation/TSGenerateRintoneOperationQueue.swift

@@ -5,6 +5,6 @@
 //  Created by 100Years on 2025/3/21.
 //
 
-class TSGenerateRintoneOperationQueue: TSBaseOperationQueue {
-    let shared:TSGenerateRintoneOperationQueue = TSGenerateRintoneOperationQueue()
-}
+//class TSGenerateRintoneOperationQueue: TSBaseOperationQueue {
+//    let shared:TSGenerateRintoneOperationQueue = TSGenerateRintoneOperationQueue()
+//}