Parcourir la source

聊天框架,markdowns,ai 对话接口,初步调试完毕

100Years il y a 2 mois
Parent
commit
e11fe36e10
35 fichiers modifiés avec 2417 ajouts et 47 suppressions
  1. 127 10
      AIEmoji.xcodeproj/project.pbxproj
  2. 24 0
      AIEmoji.xcworkspace/xcshareddata/swiftpm/Package.resolved
  3. 1 1
      AIEmoji/AppDelegate.swift
  4. 22 0
      AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/Contents.json
  5. BIN
      AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/tabbar_select_aichat@2x.png
  6. BIN
      AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/tabbar_select_aichat@3x.png
  7. 22 0
      AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/Contents.json
  8. BIN
      AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/tabbar_unSelect_aichat@2x.png
  9. BIN
      AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/tabbar_unSelect_aichat@3x.png
  10. 297 0
      AIEmoji/Business/AIChat/Data Generation/Lorem.swift
  11. 28 0
      AIEmoji/Business/AIChat/TSAIChatContainerVC.swift
  12. 71 0
      AIEmoji/Business/AIChat/TSChatViewController/Layout/CustomMessageFlowLayout.swift
  13. 209 0
      AIEmoji/Business/AIChat/TSChatViewController/Models/CustomLayoutSizeCalculator.swift
  14. 78 0
      AIEmoji/Business/AIChat/TSChatViewController/Models/CustomTextLayoutSizeCalculator.swift
  15. 175 0
      AIEmoji/Business/AIChat/TSChatViewController/Models/MockMessage.swift
  16. 31 0
      AIEmoji/Business/AIChat/TSChatViewController/Models/TSChatUser.swift
  17. 15 0
      AIEmoji/Business/AIChat/TSChatViewController/TSAIChatVC.swift
  18. 244 0
      AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController+Ex.swift
  19. 333 0
      AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController.swift
  20. 79 0
      AIEmoji/Business/AIChat/TSChatViewController/ViewModel/StreamPostRequest.swift
  21. 46 0
      AIEmoji/Business/AIChat/TSChatViewController/ViewModel/TSAIChatVM.swift
  22. 183 0
      AIEmoji/Business/AIChat/TSChatViewController/Views/CameraInputBarAccessoryView.swift
  23. 66 0
      AIEmoji/Business/AIChat/TSChatViewController/Views/CustomCell.swift
  24. 132 0
      AIEmoji/Business/AIChat/TSChatViewController/Views/CustomMessageContentCell.swift
  25. 91 0
      AIEmoji/Business/AIChat/TSChatViewController/Views/CustomTextMessageContentCell.swift
  26. 63 0
      AIEmoji/Business/AIChat/TSChatViewController/Views/TableViewCells.swift
  27. 1 11
      AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift
  28. 3 7
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/TSGenmojiVC.swift
  29. 13 0
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift
  30. 6 6
      AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift
  31. 4 2
      AIEmoji/Business/TSTabBarController/TSTabBarController.swift
  32. 3 4
      AIEmoji/Business/TSWallpaperVC/TSDiyKeyboardViewVC/View/TSKeyboardView.swift
  33. 35 0
      AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  34. 3 2
      Podfile
  35. 12 4
      Podfile.lock

+ 127 - 10
AIEmoji.xcodeproj/project.pbxproj

@@ -40,6 +40,23 @@
 		A80E73E12D533E5800C64288 /* TSPurchaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */; };
 		A80E73E42D533EB000C64288 /* TSPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */; };
 		A80E73E62D5348D000C64288 /* SettingPurchaseTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73E52D5348CF00C64288 /* SettingPurchaseTopView.swift */; };
+		A89EA64B2D59A588000EB181 /* MessageKit in Frameworks */ = {isa = PBXBuildFile; productRef = A89EA64A2D59A588000EB181 /* MessageKit */; };
+		A89EA6542D59A9F4000EB181 /* CustomTextLayoutSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA64F2D59A9F4000EB181 /* CustomTextLayoutSizeCalculator.swift */; };
+		A89EA6552D59A9F4000EB181 /* MockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6502D59A9F4000EB181 /* MockMessage.swift */; };
+		A89EA6562D59A9F4000EB181 /* TSChatUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6522D59A9F4000EB181 /* TSChatUser.swift */; };
+		A89EA6582D59A9F4000EB181 /* CustomLayoutSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA64E2D59A9F4000EB181 /* CustomLayoutSizeCalculator.swift */; };
+		A89EA6592D59A9F4000EB181 /* CustomMessageFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA64C2D59A9F4000EB181 /* CustomMessageFlowLayout.swift */; };
+		A89EA65F2D59AA11000EB181 /* TSChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA65E2D59AA11000EB181 /* TSChatViewController.swift */; };
+		A89EA6692D59AA31000EB181 /* CameraInputBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6632D59AA31000EB181 /* CameraInputBarAccessoryView.swift */; };
+		A89EA66B2D59AA31000EB181 /* CustomTextMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6662D59AA31000EB181 /* CustomTextMessageContentCell.swift */; };
+		A89EA66C2D59AA31000EB181 /* CustomMessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6652D59AA31000EB181 /* CustomMessageContentCell.swift */; };
+		A89EA66D2D59AA31000EB181 /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6672D59AA31000EB181 /* TableViewCells.swift */; };
+		A89EA66E2D59AA31000EB181 /* CustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6642D59AA31000EB181 /* CustomCell.swift */; };
+		A89EA6762D59C93E000EB181 /* TSAIChatContainerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */; };
+		A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6792D59D248000EB181 /* TSAIChatVM.swift */; };
+		A89EA67D2D59F1AF000EB181 /* StreamPostRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA67C2D59F1AC000EB181 /* StreamPostRequest.swift */; };
+		A89EA6812D59F44D000EB181 /* TSAIChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6802D59F440000EB181 /* TSAIChatVC.swift */; };
+		A89EA6832D59F4F9000EB181 /* TSChatViewController+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A89EA6822D59F4F1000EB181 /* TSChatViewController+Ex.swift */; };
 		A8EEADD42D3E6C660032C5A0 /* Flower💐.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD32D3E6C610032C5A0 /* Flower💐.json */; };
 		A8EEADD62D3E6CD80032C5A0 /* Fish🐠.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */; };
 		A8EEADD82D3E74D20032C5A0 /* Pink🩷.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */; };
@@ -108,7 +125,6 @@
 		A8F7754B2D39376800AA6E93 /* TSSettingListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F7754A2D39376700AA6E93 /* TSSettingListView.swift */; };
 		A8F7754E2D39E59100AA6E93 /* TSGenmojiModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F7754D2D39E58B00AA6E93 /* TSGenmojiModel.swift */; };
 		A8F775502D39ECED00AA6E93 /* PhotoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F7754F2D39ECED00AA6E93 /* PhotoManager.swift */; };
-		A8F775532D3A038800AA6E93 /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = A8F775522D3A038800AA6E93 /* SwiftUIX */; };
 		A8F776212D3A3F0200AA6E93 /* TSEmojisChildVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F776202D3A3F0100AA6E93 /* TSEmojisChildVC.swift */; };
 		A8F776272D3A6EC200AA6E93 /* TSGenmojiTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F776262D3A6EBA00AA6E93 /* TSGenmojiTextView.swift */; };
 		A8F7762B2D3A70B200AA6E93 /* PaddedLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F7762A2D3A70AF00AA6E93 /* PaddedLabel.swift */; };
@@ -170,6 +186,22 @@
 		A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVC.swift; sourceTree = "<group>"; };
 		A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseManager.swift; sourceTree = "<group>"; };
 		A80E73E52D5348CF00C64288 /* SettingPurchaseTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingPurchaseTopView.swift; sourceTree = "<group>"; };
+		A89EA64C2D59A9F4000EB181 /* CustomMessageFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMessageFlowLayout.swift; sourceTree = "<group>"; };
+		A89EA64E2D59A9F4000EB181 /* CustomLayoutSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLayoutSizeCalculator.swift; sourceTree = "<group>"; };
+		A89EA64F2D59A9F4000EB181 /* CustomTextLayoutSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextLayoutSizeCalculator.swift; sourceTree = "<group>"; };
+		A89EA6502D59A9F4000EB181 /* MockMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMessage.swift; sourceTree = "<group>"; };
+		A89EA6522D59A9F4000EB181 /* TSChatUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatUser.swift; sourceTree = "<group>"; };
+		A89EA65E2D59AA11000EB181 /* TSChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChatViewController.swift; sourceTree = "<group>"; };
+		A89EA6632D59AA31000EB181 /* CameraInputBarAccessoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraInputBarAccessoryView.swift; sourceTree = "<group>"; };
+		A89EA6642D59AA31000EB181 /* CustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCell.swift; sourceTree = "<group>"; };
+		A89EA6652D59AA31000EB181 /* CustomMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMessageContentCell.swift; sourceTree = "<group>"; };
+		A89EA6662D59AA31000EB181 /* CustomTextMessageContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextMessageContentCell.swift; sourceTree = "<group>"; };
+		A89EA6672D59AA31000EB181 /* TableViewCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
+		A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatContainerVC.swift; sourceTree = "<group>"; };
+		A89EA6792D59D248000EB181 /* TSAIChatVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatVM.swift; sourceTree = "<group>"; };
+		A89EA67C2D59F1AC000EB181 /* StreamPostRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPostRequest.swift; sourceTree = "<group>"; };
+		A89EA6802D59F440000EB181 /* TSAIChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIChatVC.swift; sourceTree = "<group>"; };
+		A89EA6822D59F4F1000EB181 /* TSChatViewController+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSChatViewController+Ex.swift"; sourceTree = "<group>"; };
 		A8EEADD32D3E6C610032C5A0 /* Flower💐.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Flower💐.json"; sourceTree = "<group>"; };
 		A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Fish🐠.json"; sourceTree = "<group>"; };
 		A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Pink🩷.json"; sourceTree = "<group>"; };
@@ -268,8 +300,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				A89EA64B2D59A588000EB181 /* MessageKit in Frameworks */,
 				D34BB2B8FE0DBF83E06A3FC4 /* Pods_AIEmoji.framework in Frameworks */,
-				A8F775532D3A038800AA6E93 /* SwiftUIX in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -442,6 +474,69 @@
 			path = Purchase;
 			sourceTree = "<group>";
 		};
+		A80E74222D5996BF00C64288 /* AIChat */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA6772D59D224000EB181 /* TSChatViewController */,
+				A89EA6752D59C932000EB181 /* TSAIChatContainerVC.swift */,
+			);
+			path = AIChat;
+			sourceTree = "<group>";
+		};
+		A89EA64D2D59A9F4000EB181 /* Layout */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA64C2D59A9F4000EB181 /* CustomMessageFlowLayout.swift */,
+			);
+			path = Layout;
+			sourceTree = "<group>";
+		};
+		A89EA6532D59A9F4000EB181 /* Models */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA64E2D59A9F4000EB181 /* CustomLayoutSizeCalculator.swift */,
+				A89EA64F2D59A9F4000EB181 /* CustomTextLayoutSizeCalculator.swift */,
+				A89EA6502D59A9F4000EB181 /* MockMessage.swift */,
+				A89EA6522D59A9F4000EB181 /* TSChatUser.swift */,
+			);
+			path = Models;
+			sourceTree = "<group>";
+		};
+		A89EA6682D59AA31000EB181 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA6632D59AA31000EB181 /* CameraInputBarAccessoryView.swift */,
+				A89EA6642D59AA31000EB181 /* CustomCell.swift */,
+				A89EA6652D59AA31000EB181 /* CustomMessageContentCell.swift */,
+				A89EA6662D59AA31000EB181 /* CustomTextMessageContentCell.swift */,
+				A89EA6672D59AA31000EB181 /* TableViewCells.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		A89EA6772D59D224000EB181 /* TSChatViewController */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA6782D59D238000EB181 /* ViewModel */,
+				A89EA6682D59AA31000EB181 /* Views */,
+				A89EA65E2D59AA11000EB181 /* TSChatViewController.swift */,
+				A89EA6822D59F4F1000EB181 /* TSChatViewController+Ex.swift */,
+				A89EA6802D59F440000EB181 /* TSAIChatVC.swift */,
+				A89EA64D2D59A9F4000EB181 /* Layout */,
+				A89EA6532D59A9F4000EB181 /* Models */,
+			);
+			path = TSChatViewController;
+			sourceTree = "<group>";
+		};
+		A89EA6782D59D238000EB181 /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				A89EA67C2D59F1AC000EB181 /* StreamPostRequest.swift */,
+				A89EA6792D59D248000EB181 /* TSAIChatVM.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
 		A8F774602D38E8B000AA6E93 = {
 			isa = PBXGroup;
 			children = (
@@ -478,6 +573,7 @@
 		A8F774922D38EA8C00AA6E93 /* Business */ = {
 			isa = PBXGroup;
 			children = (
+				A80E74222D5996BF00C64288 /* AIChat */,
 				A80E73DD2D533E5800C64288 /* TSPurchaseMembershipVC */,
 				A80E72702D40F85800C64288 /* LaunchVC */,
 				A8F775142D38EB5300AA6E93 /* TSWallpaperVC */,
@@ -954,7 +1050,7 @@
 			mainGroup = A8F774602D38E8B000AA6E93;
 			minimizedProjectReferenceProxies = 1;
 			packageReferences = (
-				A8F775512D3A038800AA6E93 /* XCLocalSwiftPackageReference "../SwiftUIX-master" */,
+				A89EA6492D59A588000EB181 /* XCRemoteSwiftPackageReference "MessageKit" */,
 			);
 			preferredProjectObjectVersion = 77;
 			productRefGroup = A8F7746A2D38E8B000AA6E93 /* Products */;
@@ -1045,6 +1141,11 @@
 				A8F774DA2D38EA8C00AA6E93 /* UIFont+Ex.swift in Sources */,
 				A80E72532D3F985E00C64288 /* TSWallpaperVC.swift in Sources */,
 				A8FB02B32D3E39A40031A396 /* TSEmojisModel.swift in Sources */,
+				A89EA6542D59A9F4000EB181 /* CustomTextLayoutSizeCalculator.swift in Sources */,
+				A89EA6552D59A9F4000EB181 /* MockMessage.swift in Sources */,
+				A89EA6562D59A9F4000EB181 /* TSChatUser.swift in Sources */,
+				A89EA6582D59A9F4000EB181 /* CustomLayoutSizeCalculator.swift in Sources */,
+				A89EA6592D59A9F4000EB181 /* CustomMessageFlowLayout.swift in Sources */,
 				A8F774DB2D38EA8C00AA6E93 /* AVAsset+Ex.swift in Sources */,
 				A80E72792D42285500C64288 /* TSBootPageVC.swift in Sources */,
 				A80E726A2D409E5400C64288 /* TSDiyTLYFlowersView.swift in Sources */,
@@ -1086,6 +1187,7 @@
 				A8F775352D38FC9A00AA6E93 /* TSViewTool.swift in Sources */,
 				A80E72592D3FA67800C64288 /* TSWallpaperViewModel.swift in Sources */,
 				A8F774ED2D38EA8C00AA6E93 /* Font+Ex.swift in Sources */,
+				A89EA67D2D59F1AF000EB181 /* StreamPostRequest.swift in Sources */,
 				A8F774EE2D38EA8C00AA6E93 /* UIViewController+Ex.swift in Sources */,
 				A8F774EF2D38EA8C00AA6E93 /* UIImage+Ex.swift in Sources */,
 				A8F774F02D38EA8C00AA6E93 /* TSBaseCollectionCell.swift in Sources */,
@@ -1112,6 +1214,8 @@
 				A8F774FF2D38EA8C00AA6E93 /* UserDefault+Ex.swift in Sources */,
 				A8F775002D38EA8C00AA6E93 /* WindowHelper.swift in Sources */,
 				A8F7764B2D3E008500AA6E93 /* TSEmojisChildColViewModel.swift in Sources */,
+				A89EA6762D59C93E000EB181 /* TSAIChatContainerVC.swift in Sources */,
+				A89EA6812D59F44D000EB181 /* TSAIChatVC.swift in Sources */,
 				A8F775012D38EA8C00AA6E93 /* UIButton+Ex.swift in Sources */,
 				A80E72772D41EFF900C64288 /* TSEmojisTutorialsVC.swift in Sources */,
 				A8F775022D38EA8C00AA6E93 /* Dictionary+Ex.swift in Sources */,
@@ -1122,6 +1226,7 @@
 				A8FB02BA2D3E3BB20031A396 /* TSEmojisCoLItemCell.swift in Sources */,
 				A80E72562D3F98D700C64288 /* TSDiyKeyboardViewVC.swift in Sources */,
 				A8F775032D38EA8C00AA6E93 /* GlobalImports.swift in Sources */,
+				A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */,
 				A8F775062D38EA8C00AA6E93 /* Date+Ex.swift in Sources */,
 				A8F775072D38EA8C00AA6E93 /* UIView+Ex.swift in Sources */,
 				A8F775082D38EA8C00AA6E93 /* UITableView+Ex.swift in Sources */,
@@ -1131,14 +1236,21 @@
 				A80E72742D40FEA600C64288 /* UIApplication+Ex.swift in Sources */,
 				A80E721E2D3F3A7500C64288 /* DiyElement.swift in Sources */,
 				A8F776372D3A806E00AA6E93 /* TSGenmojiItemCell.swift in Sources */,
+				A89EA6692D59AA31000EB181 /* CameraInputBarAccessoryView.swift in Sources */,
+				A89EA66B2D59AA31000EB181 /* CustomTextMessageContentCell.swift in Sources */,
+				A89EA66C2D59AA31000EB181 /* CustomMessageContentCell.swift in Sources */,
+				A89EA66D2D59AA31000EB181 /* TableViewCells.swift in Sources */,
+				A89EA66E2D59AA31000EB181 /* CustomCell.swift in Sources */,
 				A8F7750A2D38EA8C00AA6E93 /* TSNetworkTool.swift in Sources */,
 				A8F7762D2D3A74A100AA6E93 /* TSGenmojiGennerateCell.swift in Sources */,
 				A8F775322D38FA5E00AA6E93 /* UITextView+Ex.swift in Sources */,
+				A89EA6832D59F4F9000EB181 /* TSChatViewController+Ex.swift in Sources */,
 				A8F7753F2D39340E00AA6E93 /* TSSetingVC.swift in Sources */,
 				A8F7762B2D3A70B200AA6E93 /* PaddedLabel.swift in Sources */,
 				A80E73E62D5348D000C64288 /* SettingPurchaseTopView.swift in Sources */,
 				A80E72382D3F473B00C64288 /* DiyPaperProtocol.swift in Sources */,
 				A8F775382D390C3C00AA6E93 /* TSNetworkManager.swift in Sources */,
+				A89EA65F2D59AA11000EB181 /* TSChatViewController.swift in Sources */,
 				A8F7750B2D38EA8C00AA6E93 /* TSBaseTabViewCell.swift in Sources */,
 				A8F7750D2D38EA8C00AA6E93 /* TSBaseVC.swift in Sources */,
 				A8FB02B72D3E3A3D0031A396 /* TSEmojisChildViewModel.swift in Sources */,
@@ -1380,17 +1492,22 @@
 		};
 /* End XCConfigurationList section */
 
-/* Begin XCLocalSwiftPackageReference section */
-		A8F775512D3A038800AA6E93 /* XCLocalSwiftPackageReference "../SwiftUIX-master" */ = {
-			isa = XCLocalSwiftPackageReference;
-			relativePath = "../SwiftUIX-master";
+/* Begin XCRemoteSwiftPackageReference section */
+		A89EA6492D59A588000EB181 /* XCRemoteSwiftPackageReference "MessageKit" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/MessageKit/MessageKit";
+			requirement = {
+				kind = upToNextMajorVersion;
+				minimumVersion = 5.0.0;
+			};
 		};
-/* End XCLocalSwiftPackageReference section */
+/* End XCRemoteSwiftPackageReference section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		A8F775522D3A038800AA6E93 /* SwiftUIX */ = {
+		A89EA64A2D59A588000EB181 /* MessageKit */ = {
 			isa = XCSwiftPackageProductDependency;
-			productName = SwiftUIX;
+			package = A89EA6492D59A588000EB181 /* XCRemoteSwiftPackageReference "MessageKit" */;
+			productName = MessageKit;
 		};
 /* End XCSwiftPackageProductDependency section */
 	};

+ 24 - 0
AIEmoji.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -0,0 +1,24 @@
+{
+  "originHash" : "d281920be0ed75da2c7d97d9e379cb02ef073582c219818a03874cc5f03eb07d",
+  "pins" : [
+    {
+      "identity" : "inputbaraccessoryview",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/nathantannar4/InputBarAccessoryView",
+      "state" : {
+        "revision" : "dfdc822b0d4a0d1b5777100347c399069e9bfc62",
+        "version" : "7.0.0"
+      }
+    },
+    {
+      "identity" : "messagekit",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/MessageKit/MessageKit",
+      "state" : {
+        "revision" : "dc48356f830d40de5acc0e126a44257d0a1329b6",
+        "version" : "5.0.0"
+      }
+    }
+  ],
+  "version" : 3
+}

+ 1 - 1
AIEmoji/AppDelegate.swift

@@ -6,7 +6,7 @@
 //
 
 import UIKit
-
+import SQLite3
 @main
 class AppDelegate: UIResponder, UIApplicationDelegate {
 

+ 22 - 0
AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/tabbar_select_aichat@2x.png


BIN
AIEmoji/Assets.xcassets/Tabbar/tabbar_select_aichat.imageset/tabbar_select_aichat@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/tabbar_unSelect_aichat@2x.png


BIN
AIEmoji/Assets.xcassets/Tabbar/tabbar_unSelect_aichat.imageset/tabbar_unSelect_aichat@3x.png


+ 297 - 0
AIEmoji/Business/AIChat/Data Generation/Lorem.swift

@@ -0,0 +1,297 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import Foundation
+
+// MARK: - Lorem
+
+public class Lorem {
+  // MARK: Public
+
+  /// Return a random word.
+  ///
+  /// - returns: Returns a random word.
+  public class func word() -> String {
+    wordList.random()!
+  }
+
+  /// Return an array of `count` words.
+  ///
+  /// - parameter count: The number of words to return.
+  ///
+  /// - returns: Returns an array of `count` words.
+  public class func words(nbWords: Int = 3) -> [String] {
+    wordList.random(nbWords)
+  }
+
+  /// Return a string of `count` words.
+  ///
+  /// - parameter count: The number of words the string should contain.
+  ///
+  /// - returns: Returns a string of `count` words.
+  public class func words(nbWords: Int = 3) -> String {
+    words(nbWords: nbWords).joined(separator: " ")
+  }
+
+  /// Generate a sentence of `nbWords` words.
+  /// - parameter nbWords:  The number of words the sentence should contain.
+  /// - parameter variable: If `true`, the number of words will vary between
+  /// +/- 40% of `nbWords`.
+  /// - returns:
+  public class func sentence(nbWords: Int = 6, variable: Bool = true) -> String {
+    if nbWords <= 0 {
+      return ""
+    }
+
+    let result: String = words(nbWords: variable ? nbWords.randomize(variation: 40) : nbWords)
+
+    return result.firstCapitalized + "."
+  }
+
+  /// Generate an array of sentences.
+  /// - parameter nbSentences: The number of sentences to generate.
+  ///
+  /// - returns: Returns an array of random sentences.
+  public class func sentences(nbSentences: Int = 3) -> [String] {
+    (0 ..< nbSentences).map { _ in sentence() }
+  }
+
+  /// Generate a paragraph with `nbSentences` random sentences.
+  /// - parameter nbSentences: The number of sentences the paragraph should
+  /// contain.
+  /// - parameter variable:    If `true`, the number of sentences will vary
+  /// between +/- 40% of `nbSentences`.
+  /// - returns: Returns a paragraph with `nbSentences` random sentences.
+  public class func paragraph(nbSentences: Int = 3, variable: Bool = true) -> String {
+    if nbSentences <= 0 {
+      return ""
+    }
+
+    return sentences(nbSentences: variable ? nbSentences.randomize(variation: 40) : nbSentences).joined(separator: " ")
+  }
+
+  /// Generate an array of random paragraphs.
+  /// - parameter nbParagraphs: The number of paragraphs to generate.
+  /// - returns: Returns an array of `nbParagraphs` paragraphs.
+  public class func paragraphs(nbParagraphs: Int = 3) -> [String] {
+    (0 ..< nbParagraphs).map { _ in paragraph() }
+  }
+
+  /// Generate a string of random paragraphs.
+  /// - parameter nbParagraphs: The number of paragraphs to generate.
+  /// - returns: Returns a string of random paragraphs.
+  public class func paragraphs(nbParagraphs: Int = 3) -> String {
+    paragraphs(nbParagraphs: nbParagraphs).joined(separator: "\n\n")
+  }
+
+  /// Generate a string of at most `maxNbChars` characters.
+  /// - parameter maxNbChars: The maximum number of characters the string
+  /// should contain.
+  /// - returns: Returns a string of at most `maxNbChars` characters.
+  public class func text(maxNbChars: Int = 200) -> String {
+    var result: [String] = []
+
+    if maxNbChars < 5 {
+      return ""
+    } else if maxNbChars < 25 {
+      while result.count == 0 {
+        var size = 0
+
+        while size < maxNbChars {
+          let w = (size != 0 ? " " : "") + word()
+          result.append(w)
+          size += w.count
+        }
+
+        _ = result.popLast()
+      }
+    } else if maxNbChars < 100 {
+      while result.count == 0 {
+        var size = 0
+
+        while size < maxNbChars {
+          let s = (size != 0 ? " " : "") + sentence()
+          result.append(s)
+          size += s.count
+        }
+
+        _ = result.popLast()
+      }
+    } else {
+      while result.count == 0 {
+        var size = 0
+
+        while size < maxNbChars {
+          let p = (size != 0 ? "\n" : "") + paragraph()
+          result.append(p)
+          size += p.count
+        }
+
+        _ = result.popLast()
+      }
+    }
+
+    return result.joined(separator: "")
+  }
+
+  // MARK: Private
+
+  private static let wordList = [
+    "alias", "consequatur", "aut", "perferendis", "sit", "voluptatem",
+    "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab",
+    "illo", "inventore", "veritatis", "et", "quasi", "architecto",
+    "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut",
+    "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni",
+    "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt",
+    "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet",
+    "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam",
+    "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore",
+    "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad",
+    "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam",
+    "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas",
+    "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea",
+    "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure",
+    "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse",
+    "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos",
+    "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam",
+    "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos",
+    "dolores", "et", "quas", "molestias", "excepturi", "sint",
+    "occaecati", "cupiditate", "non", "provident", "sed", "ut",
+    "perspiciatis", "unde", "omnis", "iste", "natus", "error",
+    "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt",
+    "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga",
+    "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita",
+    "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis",
+    "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo",
+    "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime",
+    "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda",
+    "est", "omnis", "dolor", "repellendus", "temporibus", "autem",
+    "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui",
+    "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur",
+    "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut",
+    "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et",
+    "voluptates", "repudiandae", "sint", "et", "molestiae", "non",
+    "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a",
+    "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus",
+    "maiores", "doloribus", "asperiores", "repellat",
+  ]
+}
+
+extension String {
+  var firstCapitalized: String {
+    var string = self
+    string.replaceSubrange(string.startIndex ... string.startIndex, with: String(string[string.startIndex]).capitalized)
+    return string
+  }
+}
+
+extension Array {
+  /// Shuffle the array in-place using the Fisher-Yates algorithm.
+  public mutating func shuffle() {
+    if count == 0 {
+      return
+    }
+
+    for i in 0 ..< (count - 1) {
+      let j = Int(arc4random_uniform(UInt32(count - i))) + i
+      if j != i {
+        swapAt(i, j)
+      }
+    }
+  }
+
+  /// Return a shuffled version of the array using the Fisher-Yates
+  /// algorithm.
+  ///
+  /// - returns: Returns a shuffled version of the array.
+  public func shuffled() -> [Element] {
+    var list = self
+    list.shuffle()
+
+    return list
+  }
+
+  /// Return a random element from the array.
+  /// - returns: Returns a random element from the array or `nil` if the
+  /// array is empty.
+  public func random() -> Element? {
+    (count > 0) ? shuffled()[0] : nil
+  }
+
+  /// Return a random subset of `cnt` elements from the array.
+  /// - returns: Returns a random subset of `cnt` elements from the array.
+  public func random(_ count: Int = 1) -> [Element] {
+    let result = shuffled()
+
+    return (count > result.count) ? result : Array(result[0 ..< count])
+  }
+}
+
+extension Int {
+  /// Return a random number between `min` and `max`.
+  /// - note: The maximum value cannot be more than `UInt32.max - 1`
+  ///
+  /// - parameter min: The minimum value of the random value (defaults to `0`).
+  /// - parameter max: The maximum value of the random value (defaults to `UInt32.max - 1`)
+  ///
+  /// - returns: Returns a random value between `min` and `max`.
+  public static func random(min: Int = 0, max: Int = Int.max) -> Int {
+    precondition(min <= max, "attempt to call random() with min > max")
+
+    let diff = UInt(bitPattern: max &- min)
+    let result = UInt.random(min: 0, max: diff)
+
+    return min + Int(bitPattern: result)
+  }
+
+  public func randomize(variation: Int) -> Int {
+    let multiplier = Double(Int.random(min: 100 - variation, max: 100 + variation)) / 100
+    let randomized = Double(self) * multiplier
+
+    return Int(randomized) + 1
+  }
+}
+
+extension UInt {
+  fileprivate static func random(min: UInt, max: UInt) -> UInt {
+    precondition(min <= max, "attempt to call random() with min > max")
+
+    if min == UInt.min, max == UInt.max {
+      var result: UInt = 0
+      arc4random_buf(&result, MemoryLayout.size(ofValue: result))
+
+      return result
+    } else {
+      let range = max - min + 1
+      let limit = UInt.max - UInt.max % range
+      var result: UInt = 0
+
+      repeat {
+        arc4random_buf(&result, MemoryLayout.size(ofValue: result))
+      } while result >= limit
+
+      result = result % range
+
+      return min + result
+    }
+  }
+}

+ 28 - 0
AIEmoji/Business/AIChat/TSAIChatContainerVC.swift

@@ -0,0 +1,28 @@
+//
+//  TSAIChatVC.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/9.
+//
+
+class TSAIChatContainerVC: TSBaseVC {
+    
+    
+
+    
+    lazy var chatVC: TSChatViewController = {
+        let chatVC = TSChatViewController()
+        return chatVC
+    }()
+    
+    
+    override func createView() {
+        
+        addChild(chatVC)
+        contentView.addSubview(chatVC.view)
+        
+        chatVC.view.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+}

+ 71 - 0
AIEmoji/Business/AIChat/TSChatViewController/Layout/CustomMessageFlowLayout.swift

@@ -0,0 +1,71 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import Foundation
+import MessageKit
+import UIKit
+
+// MARK: - CustomMessagesFlowLayout
+
+open class CustomMessagesFlowLayout: MessagesCollectionViewFlowLayout {
+  open lazy var customMessageSizeCalculator = CustomMessageSizeCalculator(layout: self)
+
+  open override func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
+    if isSectionReservedForTypingIndicator(indexPath.section) {
+      return typingIndicatorSizeCalculator
+    }
+    let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+    if case .custom = message.kind {
+      return customMessageSizeCalculator
+    }
+    return super.cellSizeCalculatorForItem(at: indexPath)
+  }
+
+  open override func messageSizeCalculators() -> [MessageSizeCalculator] {
+    var superCalculators = super.messageSizeCalculators()
+    // Append any of your custom `MessageSizeCalculator` if you wish for the convenience
+    // functions to work such as `setMessageIncoming...` or `setMessageOutgoing...`
+    superCalculators.append(customMessageSizeCalculator)
+    return superCalculators
+  }
+}
+
+// MARK: - CustomMessageSizeCalculator
+
+open class CustomMessageSizeCalculator: MessageSizeCalculator {
+  // MARK: Lifecycle
+
+  public override init(layout: MessagesCollectionViewFlowLayout? = nil) {
+    super.init()
+    self.layout = layout
+  }
+
+  // MARK: Open
+
+  open override func sizeForItem(at _: IndexPath) -> CGSize {
+    guard let layout = layout else { return .zero }
+    let collectionViewWidth = layout.collectionView?.bounds.width ?? 0
+    let contentInset = layout.collectionView?.contentInset ?? .zero
+    let inset = layout.sectionInset.left + layout.sectionInset.right + contentInset.left + contentInset.right
+    return CGSize(width: collectionViewWidth - inset, height: 44)
+  }
+}

+ 209 - 0
AIEmoji/Business/AIChat/TSChatViewController/Models/CustomLayoutSizeCalculator.swift

@@ -0,0 +1,209 @@
+//
+//  CustomLayoutSizeCalculator.swift
+//  ChatExample
+//
+//  Created by Vignesh J on 01/05/21.
+//  Copyright © 2021 MessageKit. All rights reserved.
+//
+
+import MessageKit
+import UIKit
+
+class CustomLayoutSizeCalculator: CellSizeCalculator {
+  // MARK: Lifecycle
+
+  init(layout: MessagesCollectionViewFlowLayout? = nil) {
+    super.init()
+
+    self.layout = layout
+  }
+
+  // MARK: Internal
+
+  var cellTopLabelVerticalPadding: CGFloat = 32
+  var cellTopLabelHorizontalPadding: CGFloat = 32
+  var cellMessageContainerHorizontalPadding: CGFloat = 48
+  var cellMessageContainerExtraSpacing: CGFloat = 16
+  var cellMessageContentVerticalPadding: CGFloat = 16
+  var cellMessageContentHorizontalPadding: CGFloat = 16
+  var cellDateLabelHorizontalPadding: CGFloat = 24
+  var cellDateLabelBottomPadding: CGFloat = 8
+
+  var messagesLayout: MessagesCollectionViewFlowLayout {
+    layout as! MessagesCollectionViewFlowLayout
+  }
+
+  var messageContainerMaxWidth: CGFloat {
+    messagesLayout.itemWidth -
+      cellMessageContainerHorizontalPadding -
+      cellMessageContainerExtraSpacing
+  }
+
+  var messagesDataSource: MessagesDataSource {
+    self.messagesLayout.messagesDataSource
+  }
+
+  override func sizeForItem(at indexPath: IndexPath) -> CGSize {
+    let dataSource = messagesDataSource
+    let message = dataSource.messageForItem(
+      at: indexPath,
+      in: messagesLayout.messagesCollectionView)
+    let itemHeight = cellContentHeight(
+      for: message,
+      at: indexPath)
+    return CGSize(
+      width: messagesLayout.itemWidth,
+      height: itemHeight)
+  }
+
+  func cellContentHeight(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGFloat
+  {
+    cellTopLabelSize(
+      for: message,
+      at: indexPath).height +
+      cellMessageBottomLabelSize(
+        for: message,
+        at: indexPath).height +
+      messageContainerSize(
+        for: message,
+        at: indexPath).height
+  }
+
+  // MARK: - Top cell Label
+
+  func cellTopLabelSize(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGSize
+  {
+    guard
+      let attributedText = messagesDataSource.cellTopLabelAttributedText(
+        for: message,
+        at: indexPath) else
+    {
+      return .zero
+    }
+
+    let maxWidth = messagesLayout.itemWidth - cellTopLabelHorizontalPadding
+    let size = attributedText.size(consideringWidth: maxWidth)
+    let height = size.height + cellTopLabelVerticalPadding
+
+    return CGSize(
+      width: maxWidth,
+      height: height)
+  }
+
+  func cellTopLabelFrame(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGRect
+  {
+    let size = cellTopLabelSize(
+      for: message,
+      at: indexPath)
+    guard size != .zero else {
+      return .zero
+    }
+
+    let origin = CGPoint(
+      x: cellTopLabelHorizontalPadding / 2,
+      y: 0)
+
+    return CGRect(
+      origin: origin,
+      size: size)
+  }
+
+  func cellMessageBottomLabelSize(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGSize
+  {
+    guard
+      let attributedText = messagesDataSource.messageBottomLabelAttributedText(
+        for: message,
+        at: indexPath) else
+    {
+      return .zero
+    }
+    let maxWidth = messageContainerMaxWidth - cellDateLabelHorizontalPadding
+
+    return attributedText.size(consideringWidth: maxWidth)
+  }
+
+  func cellMessageBottomLabelFrame(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGRect
+  {
+    let messageContainerSize = messageContainerSize(
+      for: message,
+      at: indexPath)
+    let labelSize = cellMessageBottomLabelSize(
+      for: message,
+      at: indexPath)
+    let x = messageContainerSize.width - labelSize.width - (cellDateLabelHorizontalPadding / 2)
+    let y = messageContainerSize.height - labelSize.height - cellDateLabelBottomPadding
+    let origin = CGPoint(
+      x: x,
+      y: y)
+
+    return CGRect(
+      origin: origin,
+      size: labelSize)
+  }
+
+  // MARK: - MessageContainer
+
+  func messageContainerSize(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGSize
+  {
+    let labelSize = cellMessageBottomLabelSize(
+      for: message,
+      at: indexPath)
+    let width = labelSize.width +
+      cellMessageContentHorizontalPadding +
+      cellDateLabelHorizontalPadding
+    let height = labelSize.height +
+      cellMessageContentVerticalPadding +
+      cellDateLabelBottomPadding
+
+    return CGSize(
+      width: width,
+      height: height)
+  }
+
+  func messageContainerFrame(
+    for message: MessageType,
+    at indexPath: IndexPath,
+    fromCurrentSender: Bool)
+    -> CGRect
+  {
+    let y = cellTopLabelSize(
+      for: message,
+      at: indexPath).height
+    let size = messageContainerSize(
+      for: message,
+      at: indexPath)
+    let origin: CGPoint
+    if fromCurrentSender {
+      let x = messagesLayout.itemWidth -
+        size.width -
+        (cellMessageContainerHorizontalPadding / 2)
+      origin = CGPoint(x: x, y: y)
+    } else {
+      origin = CGPoint(
+        x: cellMessageContainerHorizontalPadding / 2,
+        y: y)
+    }
+
+    return CGRect(
+      origin: origin,
+      size: size)
+  }
+}

+ 78 - 0
AIEmoji/Business/AIChat/TSChatViewController/Models/CustomTextLayoutSizeCalculator.swift

@@ -0,0 +1,78 @@
+//
+//  CustomTextMessageSizeCalculator.swift
+//  ChatExample
+//
+//  Created by Vignesh J on 30/04/21.
+//  Copyright © 2021 MessageKit. All rights reserved.
+//
+
+import MessageKit
+import UIKit
+
+class CustomTextLayoutSizeCalculator: CustomLayoutSizeCalculator {
+  var messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
+  var cellMessageContainerRightSpacing: CGFloat = 16
+
+  override func messageContainerSize(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGSize
+  {
+    let size = super.messageContainerSize(
+      for: message,
+      at: indexPath)
+    let labelSize = messageLabelSize(
+      for: message,
+      at: indexPath)
+    let selfWidth = labelSize.width +
+      cellMessageContentHorizontalPadding +
+      cellMessageContainerRightSpacing
+    let width = max(selfWidth, size.width)
+    let height = size.height + labelSize.height
+
+    return CGSize(
+      width: width,
+      height: height)
+  }
+
+  func messageLabelSize(
+    for message: MessageType,
+    at _: IndexPath)
+    -> CGSize
+  {
+    let attributedText: NSAttributedString
+
+    let textMessageKind = message.kind
+    switch textMessageKind {
+    case .attributedText(let text):
+      attributedText = text
+    case .text(let text), .emoji(let text):
+      attributedText = NSAttributedString(string: text, attributes: [.font: messageLabelFont])
+    default:
+      fatalError("messageLabelSize received unhandled MessageDataType: \(message.kind)")
+    }
+
+    let maxWidth = messageContainerMaxWidth -
+      cellMessageContentHorizontalPadding -
+      cellMessageContainerRightSpacing
+
+    return attributedText.size(consideringWidth: maxWidth)
+  }
+
+  func messageLabelFrame(
+    for message: MessageType,
+    at indexPath: IndexPath)
+    -> CGRect
+  {
+    let origin = CGPoint(
+      x: cellMessageContentHorizontalPadding / 2,
+      y: cellMessageContentVerticalPadding / 2)
+    let size = messageLabelSize(
+      for: message,
+      at: indexPath)
+
+    return CGRect(
+      origin: origin,
+      size: size)
+  }
+}

+ 175 - 0
AIEmoji/Business/AIChat/TSChatViewController/Models/MockMessage.swift

@@ -0,0 +1,175 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import AVFoundation
+import CoreLocation
+import Foundation
+import MessageKit
+import UIKit
+
+// MARK: - CoordinateItem
+
+private struct CoordinateItem: LocationItem {
+  var location: CLLocation
+  var size: CGSize
+
+  init(location: CLLocation) {
+    self.location = location
+    size = CGSize(width: 240, height: 240)
+  }
+}
+
+// MARK: - ImageMediaItem
+
+private struct ImageMediaItem: MediaItem {
+  var url: URL?
+  var image: UIImage?
+  var placeholderImage: UIImage
+  var size: CGSize
+
+  init(image: UIImage) {
+    self.image = image
+    size = CGSize(width: 240, height: 240)
+    placeholderImage = UIImage()
+  }
+
+  init(imageURL: URL) {
+    url = imageURL
+    size = CGSize(width: 240, height: 240)
+    placeholderImage = UIImage(imageLiteralResourceName: "image_message_placeholder")
+  }
+}
+
+// MARK: - MockAudioItem
+
+private struct MockAudioItem: AudioItem {
+  var url: URL
+  var size: CGSize
+  var duration: Float
+
+  init(url: URL) {
+    self.url = url
+    size = CGSize(width: 160, height: 35)
+    // compute duration
+    let audioAsset = AVURLAsset(url: url)
+    duration = Float(CMTimeGetSeconds(audioAsset.duration))
+  }
+}
+
+// MARK: - MockContactItem
+
+struct MockContactItem: ContactItem {
+  var displayName: String
+  var initials: String
+  var phoneNumbers: [String]
+  var emails: [String]
+
+  init(name: String, initials: String, phoneNumbers: [String] = [], emails: [String] = []) {
+    displayName = name
+    self.initials = initials
+    self.phoneNumbers = phoneNumbers
+    self.emails = emails
+  }
+}
+
+// MARK: - MockLinkItem
+
+struct MockLinkItem: LinkItem {
+  let text: String?
+  let attributedText: NSAttributedString?
+  let url: URL
+  let title: String?
+  let teaser: String
+  let thumbnailImage: UIImage
+}
+
+// MARK: - TSChatMessage
+
+internal class TSChatMessage: MessageType {
+  // MARK: Lifecycle
+
+    private init(kind: MessageKind, user: TSChatUser, messageId: String, date: Date) {
+        self.kind = kind
+        self.user = user
+        self.messageId = messageId
+        sentDate = date
+    }
+
+    convenience init(custom: Any?, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .custom(custom), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(text: String, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .text(text), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(attributedText: NSAttributedString, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .attributedText(attributedText), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(image: UIImage, user: TSChatUser, messageId: String, date: Date) {
+        let mediaItem = ImageMediaItem(image: image)
+        self.init(kind: .photo(mediaItem), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(imageURL: URL, user: TSChatUser, messageId: String, date: Date) {
+        let mediaItem = ImageMediaItem(imageURL: imageURL)
+        self.init(kind: .photo(mediaItem), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(thumbnail: UIImage, user: TSChatUser, messageId: String, date: Date) {
+        let mediaItem = ImageMediaItem(image: thumbnail)
+        self.init(kind: .video(mediaItem), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(location: CLLocation, user: TSChatUser, messageId: String, date: Date) {
+        let locationItem = CoordinateItem(location: location)
+        self.init(kind: .location(locationItem), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(emoji: String, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .emoji(emoji), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(audioURL: URL, user: TSChatUser, messageId: String, date: Date) {
+        let audioItem = MockAudioItem(url: audioURL)
+        self.init(kind: .audio(audioItem), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(contact: MockContactItem, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .contact(contact), user: user, messageId: messageId, date: date)
+    }
+
+    convenience init(linkItem: LinkItem, user: TSChatUser, messageId: String, date: Date) {
+        self.init(kind: .linkPreview(linkItem), user: user, messageId: messageId, date: date)
+    }
+
+    // MARK: Internal
+    var messageId: String
+    var sentDate: Date
+    var kind: MessageKind
+    var user: TSChatUser
+    var sender: SenderType {
+        user
+    }
+    var sendState: TSProgressState = .none
+}

+ 31 - 0
AIEmoji/Business/AIChat/TSChatViewController/Models/TSChatUser.swift

@@ -0,0 +1,31 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import Foundation
+import MessageKit
+
+struct TSChatUser: SenderType, Equatable {
+    var senderId: String
+    var displayName: String
+}
+
+let kUserSender = TSChatUser(senderId: "001", displayName: "001")

+ 15 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSAIChatVC.swift

@@ -0,0 +1,15 @@
+//
+//  TSAIChatVC.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/10.
+//
+
+class TSAIChatVC: TSChatViewController {
+    
+    
+    
+    override func viewDidLoad() {
+        
+    }
+}

+ 244 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController+Ex.swift

@@ -0,0 +1,244 @@
+//
+//  TSChatViewController+Ex.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/10.
+//
+import InputBarAccessoryView
+import MessageKit
+import UIKit
+import MapKit
+
+// MARK: MessagesLayoutDelegate
+
+extension TSChatViewController: MessagesLayoutDelegate {
+  func cellTopLabelHeight(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+    18
+  }
+
+  func cellBottomLabelHeight(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+    17
+  }
+
+  func messageTopLabelHeight(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+    20
+  }
+
+  func messageBottomLabelHeight(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+    16
+  }
+    
+    
+    func textCellSizeCalculator(
+     for _: MessageType,
+     at _: IndexPath,
+     in _: MessagesCollectionView)
+     -> CellSizeCalculator?
+   {
+       self.textMessageSizeCalculator
+   }
+}
+
+
+// MARK: MessagesDisplayDelegate
+
+extension TSChatViewController: MessagesDisplayDelegate {
+  // MARK: - Text Messages
+
+  func textColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+    isFromCurrentSender(message: message) ? .white : .darkText
+  }
+
+  func detectorAttributes(for detector: DetectorType, and _: MessageType, at _: IndexPath) -> [NSAttributedString.Key: Any] {
+    switch detector {
+    case .hashtag, .mention: return [.foregroundColor: UIColor.blue]
+    default: return MessageLabel.defaultAttributes
+    }
+  }
+
+  func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
+    [.url, .address, .phoneNumber, .date, .transitInformation, .mention, .hashtag]
+  }
+
+  // MARK: - All Messages
+
+  func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+      isFromCurrentSender(message: message) ? .themeColor : UIColor(red: 230 / 255, green: 230 / 255, blue: 230 / 255, alpha: 1)
+  }
+
+  func messageStyle(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
+    let tail: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft
+    if let image = UIImage(named: "bobbly") {
+        return .customImageTail(image, tail)
+    } else {
+        return .bubbleTail(tail, .curved)
+    }
+  }
+
+  func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) {
+//    let avatar = SampleData.shared.getAvatarFor(sender: message.sender)
+    //设置聊天头像
+      avatarView.set(avatar: Avatar(image: UIImage(named: "App-Icon")))
+  }
+
+  func configureMediaMessageImageView(
+    _ imageView: UIImageView,
+    for message: MessageType,
+    at _: IndexPath,
+    in _: MessagesCollectionView)
+  {
+    if case MessageKind.photo(let media) = message.kind, let imageURL = media.url {
+      imageView.kf.setImage(with: imageURL)
+    } else {
+      imageView.kf.cancelDownloadTask()
+    }
+  }
+
+  // MARK: - Location Messages
+
+  func annotationViewForLocation(message _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> MKAnnotationView? {
+    let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
+    let pinImage = #imageLiteral(resourceName: "ic_map_marker")
+    annotationView.image = pinImage
+    annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
+    return annotationView
+  }
+
+  func animationBlockForLocation(
+    message _: MessageType,
+    at _: IndexPath,
+    in _: MessagesCollectionView) -> ((UIImageView) -> Void)?
+  {
+    { view in
+      view.layer.transform = CATransform3DMakeScale(2, 2, 2)
+      UIView.animate(
+        withDuration: 0.6,
+        delay: 0,
+        usingSpringWithDamping: 0.9,
+        initialSpringVelocity: 0,
+        options: [],
+        animations: {
+          view.layer.transform = CATransform3DIdentity
+        },
+        completion: nil)
+    }
+  }
+
+  func snapshotOptionsForLocation(
+    message _: MessageType,
+    at _: IndexPath,
+    in _: MessagesCollectionView)
+    -> LocationMessageSnapshotOptions
+  {
+    LocationMessageSnapshotOptions(
+      showsBuildings: true,
+      showsPointsOfInterest: true,
+      span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 10))
+  }
+
+  // MARK: - Audio Messages
+
+  func audioTintColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+    isFromCurrentSender(message: message) ? .white : UIColor(red: 15 / 255, green: 135 / 255, blue: 255 / 255, alpha: 1.0)
+  }
+
+  func configureAudioCell(_ cell: AudioMessageCell, message: MessageType) {
+      
+  }
+}
+
+
+// MARK: MessageCellDelegate
+
+extension TSChatViewController: MessageCellDelegate {
+  func didTapAvatar(in _: MessageCollectionViewCell) {
+    print("Avatar tapped")
+  }
+
+  func didTapMessage(in _: MessageCollectionViewCell) {
+    print("Message tapped")
+  }
+
+  func didTapImage(in _: MessageCollectionViewCell) {
+    print("Image tapped")
+  }
+
+  func didTapCellTopLabel(in _: MessageCollectionViewCell) {
+    print("Top cell label tapped")
+  }
+
+  func didTapCellBottomLabel(in _: MessageCollectionViewCell) {
+    print("Bottom cell label tapped")
+  }
+
+  func didTapMessageTopLabel(in _: MessageCollectionViewCell) {
+    print("Top message label tapped")
+  }
+
+  func didTapMessageBottomLabel(in _: MessageCollectionViewCell) {
+    print("Bottom label tapped")
+  }
+
+  func didTapPlayButton(in cell: AudioMessageCell) {
+    guard
+      let indexPath = messagesCollectionView.indexPath(for: cell),
+      let message = messagesCollectionView.messagesDataSource?.messageForItem(at: indexPath, in: messagesCollectionView)
+    else {
+      print("Failed to identify message when audio cell receive tap gesture")
+      return
+    }
+  }
+
+  func didStartAudio(in _: AudioMessageCell) {
+    print("Did start playing audio sound")
+  }
+
+  func didPauseAudio(in _: AudioMessageCell) {
+    print("Did pause audio sound")
+  }
+
+  func didStopAudio(in _: AudioMessageCell) {
+    print("Did stop audio sound")
+  }
+
+  func didTapAccessoryView(in _: MessageCollectionViewCell) {
+    print("Accessory view tapped")
+  }
+}
+
+
+// MARK: MessageLabelDelegate
+
+extension TSChatViewController: MessageLabelDelegate {
+  func didSelectAddress(_ addressComponents: [String: String]) {
+    print("Address Selected: \(addressComponents)")
+  }
+
+  func didSelectDate(_ date: Date) {
+    print("Date Selected: \(date)")
+  }
+
+  func didSelectPhoneNumber(_ phoneNumber: String) {
+    print("Phone Number Selected: \(phoneNumber)")
+  }
+
+  func didSelectURL(_ url: URL) {
+    print("URL Selected: \(url)")
+  }
+
+  func didSelectTransitInformation(_ transitInformation: [String: String]) {
+    print("TransitInformation Selected: \(transitInformation)")
+  }
+
+  func didSelectHashtag(_ hashtag: String) {
+    print("Hashtag selected: \(hashtag)")
+  }
+
+  func didSelectMention(_ mention: String) {
+    print("Mention selected: \(mention)")
+  }
+
+  func didSelectCustom(_ pattern: String, match _: String?) {
+    print("Custom data detector patter selected: \(pattern)")
+  }
+}

+ 333 - 0
AIEmoji/Business/AIChat/TSChatViewController/TSChatViewController.swift

@@ -0,0 +1,333 @@
+//
+// MIT License
+//
+// Copyright (c) 2017-2020 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import InputBarAccessoryView
+import MessageKit
+import UIKit
+import MapKit
+// MARK: - TSChatViewController
+
+/// A base class for the example controllers
+class TSChatViewController: MessagesViewController, MessagesDataSource {
+  // MARK: Internal
+    lazy var viewModel : TSAIChatVM = {
+        let viewModel = TSAIChatVM()
+        return viewModel
+    }()
+    
+
+    
+  // MARK: - Public properties
+  lazy var messageList: [TSChatMessage] = []
+
+  private(set) lazy var refreshControl: UIRefreshControl = {
+    let control = UIRefreshControl()
+    control.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
+    return control
+  }()
+
+    // MARK: Private
+     lazy var textMessageSizeCalculator = CustomTextLayoutSizeCalculator(
+      layout: self.messagesCollectionView
+        .messagesCollectionViewFlowLayout)
+    
+
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    navigationItem.title = "MessageKit"
+
+    configureMessageCollectionView()
+    configureMessageInputBar()
+    loadFirstMessages()
+  }
+
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+  }
+
+  override func viewDidDisappear(_ animated: Bool) {
+    super.viewDidDisappear(animated)
+  }
+
+  func loadFirstMessages() {
+      //获取消息数量
+
+  }
+
+  @objc
+  func loadMoreMessages() {
+      //获取更多消息数量
+
+  }
+
+    func configureMessageCollectionView() {
+        messagesCollectionView.register(CustomTextMessageContentCell.self)
+        messagesCollectionView.messagesLayoutDelegate = self
+        messagesCollectionView.messagesDisplayDelegate = self
+        messagesCollectionView.messagesDataSource = self
+        messagesCollectionView.messageCellDelegate = self
+        messagesCollectionView.clipsToBounds = true
+        scrollsToLastItemOnKeyboardBeginsEditing = true // default false
+        maintainPositionOnInputBarHeightChanged = true // default false
+        showMessageTimestampOnSwipeLeft = false // default false
+
+        messagesCollectionView.refreshControl = refreshControl
+        messagesCollectionView.reloadData()
+    }
+
+    func configureMessageInputBar() {
+        messageInputBar.delegate = self
+        messageInputBar.inputTextView.tintColor = .themeColor
+        messageInputBar.sendButton.setTitleColor(.themeColor, for: .normal)
+        messageInputBar.sendButton.setTitleColor(
+        UIColor.themeColor.withAlphaComponent(0.3),for: .highlighted)
+    }
+
+    // MARK: - Helpers
+    var lastIndexPath:IndexPath{
+        if messageList.count == 0 {
+            return IndexPath(item: 0, section: 0)
+        }
+        return IndexPath(item: messageList.count - 1, section: 0)
+    }
+    func insertMessage(_ message: TSChatMessage) {
+        messageList.append(message)
+        messagesCollectionView.performBatchUpdates({
+            messagesCollectionView.insertItems(at: [lastIndexPath])
+            if messageList.count >= 2 {
+                messagesCollectionView.reloadItems(at: [lastIndexPath])
+            }
+        }, completion: { [weak self] _ in
+            if self?.isLastSectionVisible() == true {
+                self?.messagesCollectionView.scrollToLastItem(animated: true)
+            }
+        })
+    }
+
+  func isLastSectionVisible() -> Bool {
+    guard !messageList.isEmpty else { return false }
+    return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
+  }
+
+
+  private let formatter: DateFormatter = {
+    let formatter = DateFormatter()
+    formatter.dateStyle = .medium
+    return formatter
+  }()
+}
+
+
+// MARK: MessagesDataSource
+extension TSChatViewController {
+
+    var currentSender: SenderType {
+        return kUserSender
+    }
+    
+    func numberOfSections(in _: MessagesCollectionView) -> Int {
+        return 1
+    }
+    
+    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int{
+        messageList.count
+    }
+
+    func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
+        messageList[indexPath.item]
+    }
+
+    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        if indexPath.item % 3 == 0 {
+        return NSAttributedString(
+          string: MessageKitDateFormatter.shared.string(from: message.sentDate),
+          attributes: [
+            NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+            NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+          ])
+      }
+      return nil
+    }
+
+    func cellBottomLabelAttributedText(for _: MessageType, at _: IndexPath) -> NSAttributedString? {
+      NSAttributedString(
+        string: "Read",
+        attributes: [
+          NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+          NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+        ])
+    }
+
+    func messageTopLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
+      let name = message.sender.displayName
+      return NSAttributedString(
+        string: name,
+        attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
+    }
+
+    func messageBottomLabelAttributedText(for message: MessageType, at _: IndexPath) -> NSAttributedString? {
+      let dateString = formatter.string(from: message.sentDate)
+      return NSAttributedString(
+        string: dateString,
+        attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
+    }
+
+      func textCell(
+        for message: MessageType,
+        at indexPath: IndexPath,
+        in messagesCollectionView: MessagesCollectionView)
+        -> UICollectionViewCell?
+      {
+        let cell = messagesCollectionView.dequeueReusableCell(
+          CustomTextMessageContentCell.self,
+          for: indexPath)
+        cell.configure(
+          with: message,
+          at: indexPath,
+          in: messagesCollectionView,
+          dataSource: self,
+          and: textMessageSizeCalculator)
+
+        return cell
+      }
+}
+
+
+// MARK: InputBarAccessoryViewDelegate
+
+extension TSChatViewController: InputBarAccessoryViewDelegate {
+  // MARK: Internal
+
+  @objc
+  func inputBar(_: InputBarAccessoryView, didPressSendButtonWith _: String) {
+    processInputBar(messageInputBar)
+  }
+
+    //聊天发送内容
+  func processInputBar(_ inputBar: InputBarAccessoryView) {
+    // Here we can parse for which substrings were autocompleted
+    let attributedText = inputBar.inputTextView.attributedText!
+    let range = NSRange(location: 0, length: attributedText.length)
+    attributedText.enumerateAttribute(.autocompleted, in: range, options: []) { _, range, _ in
+
+      let substring = attributedText.attributedSubstring(from: range)
+      let context = substring.attribute(.autocompletedContext, at: 0, effectiveRange: nil)
+      print("Autocompleted: `", substring, "` with context: ", context ?? "-")
+    }
+
+    let components = inputBar.inputTextView.components
+    inputBar.inputTextView.text = String()
+    inputBar.invalidatePlugins()
+      
+      inputBar.inputTextView.placeholder = "Aa"
+      sendMessages(components)
+      messagesCollectionView.scrollToLastItem(animated: true)
+  }
+
+    // MARK: Private
+
+    private func sendMessages(_ data: [Any]) {
+        let user = kUserSender
+        for component in data {
+          if let str = component as? String {
+              let message = TSChatMessage(text: str, user: user, messageId: UUID().uuidString, date: Date())
+              insertMessage(message)
+              //发送消息后,进行AI 对话生成
+              generativeAIChat(message: message)
+          } else if let img = component as? UIImage {
+              let message = TSChatMessage(image: img, user: user, messageId: UUID().uuidString, date: Date())
+              insertMessage(message)
+          }
+        }
+    }
+    
+    func generativeAIChat(message:TSChatMessage) {
+        var messageString = ""
+        switch message.kind {
+            case .text(let message):
+                messageString = message
+            default:
+                break
+        }
+        
+        if messageString.count == 0 {
+            return
+        }
+        
+        let aiUser = TSChatUser(senderId: "000", displayName: "AI")
+        let message = TSChatMessage(text: "...", user: aiUser, messageId: UUID().uuidString, date: Date())
+        message.sendState = .start
+        insertMessage(message)
+        
+        
+        viewModel.sendChatMessage(message: messageString) {[weak self] string in
+            print("\(string)")
+            
+            self?.updataAIChatCell(string: string)
+            
+        } completion: {[weak self] data, error in
+
+            guard let self = self else { return }
+            if let netData = data {
+                message.sendState = .success("netData")
+            }else {
+                message.kind = .text(kAIErrorString)
+                message.sendState = .failed(kAIErrorString)
+            }
+            
+            updataAIChatCellUI()
+        }
+    }
+    
+    func updataAIChatCell(string:String){
+        
+        guard var generativeAIMessage = messageList.last else { return }
+        var message = ""
+        if case let .text(msg) = generativeAIMessage.kind {
+            message = msg + string
+        }
+        generativeAIMessage.kind = .text(message)
+        generativeAIMessage.sendState = .progress(0.5)
+        
+        updataAIChatCellUI()
+    }
+    
+    func updataAIChatCellUI(){
+        kExecuteOnMainThread {
+            if self.messageList.count >= 2 {
+                UIView.performWithoutAnimation {
+                    self.messagesCollectionView.reloadItems(at: [self.lastIndexPath])
+                }
+            }else{
+                self.messagesCollectionView.reloadData()
+            }
+            
+            if self.isLastSectionVisible() == true {
+                self.messagesCollectionView.scrollToLastItem(animated: true)
+            }
+        }
+    }
+    
+}
+

+ 79 - 0
AIEmoji/Business/AIChat/TSChatViewController/ViewModel/StreamPostRequest.swift

@@ -0,0 +1,79 @@
+//
+//  StreamPostRequest.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/10.
+//
+
+import Foundation
+
+// 定义一个用于处理流式响应的回调闭包类型
+typealias StreamResponseHandler = (Data) -> Void
+typealias CompletionHandler = (Error?) -> Void
+
+class StreamPostRequest {
+    private let url: URL
+    private var request: URLRequest
+    private var session: URLSession
+    private let streamHandler: StreamResponseHandler
+    private let completionHandler: CompletionHandler
+    private var task: URLSessionDataTask?
+
+    // 初始化方法,接收请求 URL、参数、流式响应处理闭包和完成处理闭包
+    init(url: URL, parameters: [String: Any]?, streamHandler: @escaping StreamResponseHandler, completionHandler: @escaping CompletionHandler) {
+        self.url = url
+        self.streamHandler = streamHandler
+        self.completionHandler = completionHandler
+
+        // 创建 URLRequest 对象
+        request = URLRequest(url: url)
+        request.httpMethod = "POST"
+
+        // 设置请求体(如果有)
+        if let parameters = parameters {
+            do {
+                request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
+                request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+            } catch {
+                print("Error encoding parameters: \(error)")
+            }
+        }
+
+        // 创建 URLSession 并设置代理
+        let delegate = StreamDelegate(streamHandler: streamHandler, completionHandler: completionHandler)
+        session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
+    }
+
+    // 启动请求的方法
+    func startRequest() {
+        task = session.dataTask(with: request)
+        task?.resume()
+    }
+
+    // 停止请求的方法
+    func stopRequest() {
+        task?.cancel()
+    }
+}
+
+// 自定义的 URLSessionDataDelegate 类
+private class StreamDelegate: NSObject, URLSessionDataDelegate {
+    private let streamHandler: StreamResponseHandler
+    private let completionHandler: CompletionHandler
+
+    init(streamHandler: @escaping StreamResponseHandler, completionHandler: @escaping CompletionHandler) {
+        self.streamHandler = streamHandler
+        self.completionHandler = completionHandler
+    }
+
+    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+        // 调用流式响应处理闭包
+        streamHandler(data)
+    }
+
+    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+        // 调用完成处理闭包
+        completionHandler(error)
+    }
+}
+

+ 46 - 0
AIEmoji/Business/AIChat/TSChatViewController/ViewModel/TSAIChatVM.swift

@@ -0,0 +1,46 @@
+//
+//  TSAIChatVM.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/9.
+//
+import Alamofire
+class TSAIChatVM {
+    
+    
+    var streamRequest:StreamPostRequest?
+    
+    var sessionId = "001"
+
+    func sendChatMessage(
+        message:String,
+        streamHandler:@escaping (String) -> Void,
+        completion: @escaping (Any?, Error?) -> Void
+    ) {
+        // URL 和请求参数
+        let url = "http://ai.100yearslater.com/api/text/chat"
+        let parameters: [String: String] = [
+            "sessionId": sessionId,
+            "message": message
+        ]
+        streamRequest = TSNetworkShared.postStream(urlType: .chat,parameters: parameters) { string in
+            streamHandler(string)
+        } completion: { result in
+            switch result {
+            case .success(let data):
+                completion(data,nil)
+            case .failure(let error):
+                completion(nil,error)
+            }
+        }
+    }
+    
+    func stopAiGenerate () {
+        streamRequest?.stopRequest()
+    }
+
+}
+
+
+
+let kAIErrorString = "Server is busy, please try again later".localized

+ 183 - 0
AIEmoji/Business/AIChat/TSChatViewController/Views/CameraInputBarAccessoryView.swift

@@ -0,0 +1,183 @@
+//
+//  CameraInput.swift
+//  ChatExample
+//
+//  Created by Mohannad on 12/25/20.
+//  Copyright © 2020 MessageKit. All rights reserved.
+//
+
+import InputBarAccessoryView
+import UIKit
+
+// MARK: - CameraInputBarAccessoryViewDelegate
+
+protocol CameraInputBarAccessoryViewDelegate: InputBarAccessoryViewDelegate {
+  func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith attachments: [AttachmentManager.Attachment])
+}
+
+extension CameraInputBarAccessoryViewDelegate {
+  func inputBar(_: InputBarAccessoryView, didPressSendButtonWith _: [AttachmentManager.Attachment]) { }
+}
+
+// MARK: - CameraInputBarAccessoryView
+
+class CameraInputBarAccessoryView: InputBarAccessoryView {
+  // MARK: Lifecycle
+
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    configure()
+  }
+
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  // MARK: Internal
+
+  lazy var attachmentManager: AttachmentManager = { [unowned self] in
+    let manager = AttachmentManager()
+    manager.delegate = self
+    return manager
+  }()
+
+  func configure() {
+    let camera = makeButton(named: "ic_camera")
+    camera.tintColor = .darkGray
+    camera.onTouchUpInside { [weak self] _ in
+      self?.showImagePickerControllerActionSheet()
+    }
+    setLeftStackViewWidthConstant(to: 35, animated: true)
+    setStackViewItems([camera], forStack: .left, animated: false)
+    inputPlugins = [attachmentManager]
+  }
+
+  override func didSelectSendButton() {
+    if attachmentManager.attachments.count > 0 {
+      (delegate as? CameraInputBarAccessoryViewDelegate)?
+        .inputBar(self, didPressSendButtonWith: attachmentManager.attachments)
+    }
+    else {
+      delegate?.inputBar(self, didPressSendButtonWith: inputTextView.text)
+    }
+  }
+
+  // MARK: Private
+
+  private func makeButton(named _: String) -> InputBarButtonItem {
+    InputBarButtonItem()
+      .configure {
+        $0.spacing = .fixed(10)
+        $0.image = UIImage(systemName: "camera.fill")?.withRenderingMode(.alwaysTemplate)
+        $0.setSize(CGSize(width: 30, height: 30), animated: false)
+      }.onSelected {
+        $0.tintColor = .systemBlue
+      }.onDeselected {
+        $0.tintColor = UIColor.lightGray
+      }.onTouchUpInside { _ in
+        print("Item Tapped")
+      }
+  }
+}
+
+// MARK: UIImagePickerControllerDelegate, UINavigationControllerDelegate
+
+extension CameraInputBarAccessoryView: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
+  @objc
+  func showImagePickerControllerActionSheet() {
+    let photoLibraryAction = UIAlertAction(title: "Choose From Library", style: .default) { [weak self] _ in
+      self?.showImagePickerController(sourceType: .photoLibrary)
+    }
+
+    let cameraAction = UIAlertAction(title: "Take From Camera", style: .default) { [weak self] _ in
+      self?.showImagePickerController(sourceType: .camera)
+    }
+
+    let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
+
+
+  }
+
+  func showImagePickerController(sourceType: UIImagePickerController.SourceType) {
+    let imgPicker = UIImagePickerController()
+    imgPicker.delegate = self
+    imgPicker.allowsEditing = true
+    imgPicker.sourceType = sourceType
+    imgPicker.presentationController?.delegate = self
+    inputAccessoryView?.isHidden = true
+    getRootViewController()?.present(imgPicker, animated: true, completion: nil)
+  }
+
+  func imagePickerController(
+    _: UIImagePickerController,
+    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
+  {
+    if let editedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
+      // self.sendImageMessage(photo: editedImage)
+      inputPlugins.forEach { _ = $0.handleInput(of: editedImage) }
+    }
+    else if let originImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
+      inputPlugins.forEach { _ = $0.handleInput(of: originImage) }
+      // self.sendImageMessage(photo: originImage)
+    }
+    getRootViewController()?.dismiss(animated: true, completion: nil)
+    inputAccessoryView?.isHidden = false
+  }
+
+  func imagePickerControllerDidCancel(_: UIImagePickerController) {
+    getRootViewController()?.dismiss(animated: true, completion: nil)
+    inputAccessoryView?.isHidden = false
+  }
+
+  func getRootViewController() -> UIViewController? {
+    (UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController
+  }
+}
+
+// MARK: AttachmentManagerDelegate
+
+extension CameraInputBarAccessoryView: AttachmentManagerDelegate {
+  // MARK: - AttachmentManagerDelegate
+
+  func attachmentManager(_: AttachmentManager, shouldBecomeVisible: Bool) {
+    setAttachmentManager(active: shouldBecomeVisible)
+  }
+
+  func attachmentManager(_ manager: AttachmentManager, didReloadTo _: [AttachmentManager.Attachment]) {
+    sendButton.isEnabled = manager.attachments.count > 0
+  }
+
+  func attachmentManager(_ manager: AttachmentManager, didInsert _: AttachmentManager.Attachment, at _: Int) {
+    sendButton.isEnabled = manager.attachments.count > 0
+  }
+
+  func attachmentManager(_ manager: AttachmentManager, didRemove _: AttachmentManager.Attachment, at _: Int) {
+    sendButton.isEnabled = manager.attachments.count > 0
+  }
+
+  func attachmentManager(_: AttachmentManager, didSelectAddAttachmentAt _: Int) {
+    showImagePickerControllerActionSheet()
+  }
+
+  // MARK: - AttachmentManagerDelegate Helper
+
+  func setAttachmentManager(active: Bool) {
+    let topStackView = topStackView
+    if active, !topStackView.arrangedSubviews.contains(attachmentManager.attachmentView) {
+      topStackView.insertArrangedSubview(attachmentManager.attachmentView, at: topStackView.arrangedSubviews.count)
+      topStackView.layoutIfNeeded()
+    } else if !active, topStackView.arrangedSubviews.contains(attachmentManager.attachmentView) {
+      topStackView.removeArrangedSubview(attachmentManager.attachmentView)
+      topStackView.layoutIfNeeded()
+    }
+  }
+}
+
+// MARK: UIAdaptivePresentationControllerDelegate
+
+extension CameraInputBarAccessoryView: UIAdaptivePresentationControllerDelegate {
+  // Swipe to dismiss image modal
+  public func presentationControllerWillDismiss(_: UIPresentationController) {
+    isHidden = false
+  }
+}

+ 66 - 0
AIEmoji/Business/AIChat/TSChatViewController/Views/CustomCell.swift

@@ -0,0 +1,66 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import MessageKit
+import UIKit
+
+open class CustomCell: UICollectionViewCell {
+  // MARK: Lifecycle
+
+  public override init(frame: CGRect) {
+    super.init(frame: frame)
+    setupSubviews()
+  }
+
+  public required init?(coder aDecoder: NSCoder) {
+    super.init(coder: aDecoder)
+    setupSubviews()
+  }
+
+  // MARK: Open
+
+  open func setupSubviews() {
+    contentView.addSubview(label)
+    label.textAlignment = .center
+    label.font = UIFont.italicSystemFont(ofSize: 13)
+  }
+
+  open override func layoutSubviews() {
+    super.layoutSubviews()
+    label.frame = contentView.bounds
+  }
+
+  open func configure(with message: MessageType, at _: IndexPath, and _: MessagesCollectionView) {
+    // Do stuff
+    switch message.kind {
+    case .custom(let data):
+      guard let systemMessage = data as? String else { return }
+      label.text = systemMessage
+    default:
+      break
+    }
+  }
+
+  // MARK: Internal
+
+  let label = UILabel()
+}

+ 132 - 0
AIEmoji/Business/AIChat/TSChatViewController/Views/CustomMessageContentCell.swift

@@ -0,0 +1,132 @@
+//
+//  CustomMessageContentCell.swift
+//  ChatExample
+//
+//  Created by Vignesh J on 01/05/21.
+//  Copyright © 2021 MessageKit. All rights reserved.
+//
+
+import MessageKit
+import UIKit
+
+class CustomMessageContentCell: MessageCollectionViewCell {
+  // MARK: Lifecycle
+
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+    setupSubviews()
+  }
+
+  required init?(coder aDecoder: NSCoder) {
+    super.init(coder: aDecoder)
+    contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+    setupSubviews()
+  }
+
+  // MARK: Internal
+
+  /// The `MessageCellDelegate` for the cell.
+  weak var delegate: MessageCellDelegate?
+
+  /// The container used for styling and holding the message's content view.
+  var messageContainerView: UIView = {
+    let containerView = UIView()
+    containerView.clipsToBounds = true
+    containerView.layer.masksToBounds = true
+    return containerView
+  }()
+
+  /// The top label of the cell.
+  var cellTopLabel: UILabel = {
+    let label = UILabel()
+    label.numberOfLines = 0
+    label.textAlignment = .center
+    return label
+  }()
+
+  var cellDateLabel: UILabel = {
+    let label = UILabel()
+    label.numberOfLines = 0
+    label.textAlignment = .right
+    return label
+  }()
+
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    cellTopLabel.text = nil
+    cellTopLabel.attributedText = nil
+    cellDateLabel.text = nil
+    cellDateLabel.attributedText = nil
+  }
+
+  /// Handle tap gesture on contentView and its subviews.
+  override func handleTapGesture(_ gesture: UIGestureRecognizer) {
+    let touchLocation = gesture.location(in: self)
+
+    switch true {
+    case messageContainerView.frame
+      .contains(touchLocation) && !cellContentView(canHandle: convert(touchLocation, to: messageContainerView)):
+      delegate?.didTapMessage(in: self)
+    case cellTopLabel.frame.contains(touchLocation):
+      delegate?.didTapCellTopLabel(in: self)
+    case cellDateLabel.frame.contains(touchLocation):
+      delegate?.didTapMessageBottomLabel(in: self)
+    default:
+      delegate?.didTapBackground(in: self)
+    }
+  }
+
+  /// Handle long press gesture, return true when gestureRecognizer's touch point in `messageContainerView`'s frame
+  override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+    let touchPoint = gestureRecognizer.location(in: self)
+    guard gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) else { return false }
+    return messageContainerView.frame.contains(touchPoint)
+  }
+
+  func setupSubviews() {
+    messageContainerView.layer.cornerRadius = 5
+
+    contentView.addSubview(cellTopLabel)
+    contentView.addSubview(messageContainerView)
+    messageContainerView.addSubview(cellDateLabel)
+  }
+
+  func configure(
+    with message: MessageType,
+    at indexPath: IndexPath,
+    in messagesCollectionView: MessagesCollectionView,
+    dataSource: MessagesDataSource,
+    and sizeCalculator: CustomLayoutSizeCalculator)
+  {
+    guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+      return
+    }
+    cellTopLabel.frame = sizeCalculator.cellTopLabelFrame(
+      for: message,
+      at: indexPath)
+    cellDateLabel.frame = sizeCalculator.cellMessageBottomLabelFrame(
+      for: message,
+      at: indexPath)
+    messageContainerView.frame = sizeCalculator.messageContainerFrame(
+      for: message,
+      at: indexPath,
+      fromCurrentSender: dataSource
+        .isFromCurrentSender(message: message))
+    cellTopLabel.attributedText = dataSource.cellTopLabelAttributedText(
+      for: message,
+      at: indexPath)
+    cellDateLabel.attributedText = dataSource.messageBottomLabelAttributedText(
+      for: message,
+      at: indexPath)
+    messageContainerView.backgroundColor = displayDelegate.backgroundColor(
+      for: message,
+      at: indexPath,
+      in: messagesCollectionView)
+  }
+
+  /// Handle `ContentView`'s tap gesture, return false when `ContentView` doesn't needs to handle gesture
+  func cellContentView(canHandle _: CGPoint) -> Bool {
+    false
+  }
+}

+ 91 - 0
AIEmoji/Business/AIChat/TSChatViewController/Views/CustomTextMessageContentCell.swift

@@ -0,0 +1,91 @@
+//
+//  CustomTextMessageContentCell.swift
+//  ChatExample
+//
+//  Created by Vignesh J on 01/05/21.
+//  Copyright © 2021 MessageKit. All rights reserved.
+//
+
+import MessageKit
+import UIKit
+import SwiftyMarkdown
+class CustomTextMessageContentCell: CustomMessageContentCell {
+  /// The label used to display the message's text.
+  var messageLabel: UILabel = {
+    let label = UILabel()
+    label.numberOfLines = 0
+    label.font = UIFont.preferredFont(forTextStyle: .body)
+    return label
+  }()
+    
+    override func prepareForReuse() {
+        super.prepareForReuse()
+//        messageLabel.attributedText = nil
+//        messageLabel.text = nil
+    }
+
+  override func setupSubviews() {
+     super.setupSubviews()
+     messageContainerView.addSubview(messageLabel)
+  }
+
+  override func configure(
+    with message: MessageType,
+    at indexPath: IndexPath,
+    in messagesCollectionView: MessagesCollectionView,
+    dataSource: MessagesDataSource,
+    and sizeCalculator: CustomLayoutSizeCalculator)
+  {
+    super.configure(
+      with: message,
+      at: indexPath,
+      in: messagesCollectionView,
+      dataSource: dataSource,
+      and: sizeCalculator)
+
+    guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+      return
+    }
+
+    let calculator = sizeCalculator as? CustomTextLayoutSizeCalculator
+    messageLabel.frame = calculator?.messageLabelFrame(for: message,at: indexPath) ?? .zero
+    let textMessageKind = message.kind
+    switch textMessageKind {
+        case .text(let text), .emoji(let text):
+//            let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView)
+//            messageLabel.textColor = textColor
+        
+        messageLabel.text = text
+        messageLabel.attributedText = SwiftyMarkdown(string: text).attributedString()
+//        extractAndPrintSubstring(from: messageLabel.text ?? "", to: text)
+            
+
+        case .attributedText(let text):
+            messageLabel.attributedText = text
+        default:
+          break
+    }
+  }
+    
+
+    func extractAndPrintSubstring(from sourceString: String, to targetString: String) {
+        // 查找 sourceString 在 targetString 中的结束位置
+        if let range = targetString.range(of: sourceString) {
+            // 获取 sourceString 在 targetString 中的结束位置
+            let startIdx = range.upperBound
+            // 提取从该位置到 targetString 结束的子字符串
+            let extractedSubstring = targetString[startIdx...]
+            
+            // 将提取的子字符串逐个字符输出
+            for character in extractedSubstring {
+                print(character)
+                messageLabel.text = (messageLabel.text ?? "") + String(character)
+                messageLabel.attributedText = SwiftyMarkdown(string: messageLabel.text ?? "").attributedString()
+            }
+        } else {
+            print("未找到 sourceString")
+            messageLabel.text = targetString
+            messageLabel.attributedText = SwiftyMarkdown(string: targetString).attributedString()
+        }
+    }
+}

+ 63 - 0
AIEmoji/Business/AIChat/TSChatViewController/Views/TableViewCells.swift

@@ -0,0 +1,63 @@
+// MIT License
+//
+// Copyright (c) 2017-2019 MessageKit
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import UIKit
+
+internal class TextFieldTableViewCell: UITableViewCell {
+  // MARK: Lifecycle
+
+  // MARK: - View lifecycle
+
+  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+    mainLabel.translatesAutoresizingMaskIntoConstraints = false
+    textField.translatesAutoresizingMaskIntoConstraints = false
+
+    contentView.addSubview(mainLabel)
+    contentView.addSubview(textField)
+
+    NSLayoutConstraint.activate([
+      mainLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
+      mainLabel.widthAnchor.constraint(equalToConstant: 200),
+      mainLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+
+      textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+
+      textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
+      textField.widthAnchor.constraint(equalToConstant: 50),
+    ])
+
+    textField.textAlignment = .right
+  }
+
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  // MARK: Internal
+
+  static let identifier = "TextFieldTableViewCellIdentifier"
+
+  var mainLabel = UILabel()
+  var textField = UITextField()
+}

+ 1 - 11
AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift

@@ -130,17 +130,7 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
     
     
     func JudgeVip() -> Bool {
-        //判断 vip
-        if currentModel?.response.vip
-            == true,
-           PurchaseManager.default.isVip == false
-        {
-            TSPurchaseVC.show(target: self) {[weak self]  in
-                guard let self = self else { return }
-            }
-            return true
-        }
-        return false
+        return kJudgeVip(externalBool: currentModel?.response.vip ?? false , vc: self, closePageBlock: nil)
     }
 }
 

+ 3 - 7
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/TSGenmojiVC.swift

@@ -135,13 +135,9 @@ extension TSGenmojiVC {
     func generateImage(text:String) {
         
         //判断 vip
-        if kPurchaseDefault.freeNumAvailable() == false{
-            TSPurchaseVC.show(target: self) {[weak self] in
-                guard let self = self else { return }
-                collectionComponent.reloadData()
-            }
-            return
-        }
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable() == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         
         let gennerateVC = TSGenmojiGennerateVC(aiText: text) {[weak self] model in
             guard let self = self else { return }

+ 13 - 0
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

@@ -186,6 +186,19 @@ class TSPurchaseVC: TSBaseVC {
 
 
 
+func kJudgeVip(externalBool:Bool,
+               vc:UIViewController,
+               closePageBlock:(()->Void)?) -> Bool {
+    //判断 vip
+    if externalBool,
+       PurchaseManager.default.isVip == false
+    {
+        TSPurchaseVC.show(target: vc, closePageBlock: nil)
+        return true
+    }
+    return false
+}
+
 
 extension TSPurchaseVC{
     

+ 6 - 6
AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift

@@ -6,7 +6,6 @@
 //
 
 import SwiftUI
-import SwiftUIX
 
 struct TSSettingListView: View {
     @ObservedObject var viewModel: TSSetingViewModel
@@ -15,10 +14,10 @@ struct TSSettingListView: View {
         ZStack {
             Color.clear
             VStack(spacing: 0) {
-                Spacer().height(16)
+                Spacer().frame(height: 16)
                 
                 SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
-                    .height(117*kDesignScale)
+                    .frame(height: 117*kDesignScale)
                     .onTapGesture {
                         if PurchaseManager.default.isVip {
                             return
@@ -27,7 +26,7 @@ struct TSSettingListView: View {
                     }
                 
                 ForEach(viewModel.settingTypes, id:\.self) { type in
-                    Spacer().height(16)
+                    Spacer().frame(height: 16)
                     SettingListItemView(type: type)
                     .onTapGesture {
                         publisher.settingPublisher.send(type)
@@ -60,7 +59,8 @@ struct SettingListItemView: View {
                 }
             }.padding(.horizontal)
         }
-        .height(64)
-        .cornerRadius(.allCorners, 16)
+        .frame(height: 64)
+        .cornerRadius(16)
+//        .cornerRadius(.allCorners, 16)
     }
 }

+ 4 - 2
AIEmoji/Business/TSTabBarController/TSTabBarController.swift

@@ -24,16 +24,18 @@ class TSTabBarController: UITabBarController {
     }
 
     @objc private func setUpData() {
-        viewControllerArray = ["TSEmojisVC", "TSGenmojiVC", "TSWallpaperVC"]
-        titleArray = ["Emojis","Genmoji","Wallpaper"]
+        viewControllerArray = ["TSEmojisVC", "TSGenmojiVC","TSAIChatContainerVC", "TSWallpaperVC"]
+        titleArray = ["Emojis","Genmoji","AIChat","Wallpaper"]
         selectedImageArray = [
             "tabbar_select_emoji",
             "tabbar_select_aiemoji",
+            "tabbar_select_aichat",
             "tabbar_select_keyboard"
         ]
         unselectedImageArray = [
             "tabbar_unSelect_emoji",
             "tabbar_unSelect_aiemoji",
+            "tabbar_unSelect_aichat",
             "tabbar_unSelect_keyboard"
         ]
 

+ 3 - 4
AIEmoji/Business/TSWallpaperVC/TSDiyKeyboardViewVC/View/TSKeyboardView.swift

@@ -6,7 +6,6 @@
 //
 
 import SwiftUI
-import SwiftUIX
 import Combine
 struct TSKeyboardView : View {
     
@@ -64,7 +63,7 @@ struct TSKeyboardItemView : View {
                 Spacer().frame(height: 2)
             }
             .background("#F1F4F8".color.color)
-            .cornerRadius(.allCorners, 5)
+            .cornerRadius(5)
             .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 2) // 设置阴影
             
         }
@@ -76,13 +75,13 @@ struct TSKeyboardDeleteView : View {
     var model:DiyStickerModel
     var body: some View {
         return HStack {
-            Spacer().frame(width: 14).background(.red)
+            Spacer().frame(width: 14).background(Color.red)
             VStack {
                 Image(model.imageUrl ?? "").resizable().frame(width: 23, height: 17)
             }
             .frame(width: 42,height: 42)
             .background("#F1F4F8".color.color)
-            .cornerRadius(.allCorners, 5)
+            .cornerRadius(5)
             .shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 2) // 设置阴影
         }
     }

+ 35 - 0
AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -12,6 +12,7 @@ enum TSNeURLType:String {
     
     case imageEmoji = "/api/image/emoji"
     case actionInfo = "/api/action/info"
+    case chat = "/api/text/chat"
     
     func getUrlString() -> String {
         return baseURL + self.rawValue
@@ -52,6 +53,40 @@ extension TSNetworkManager {
             completion(result)
         }
     }
+    
+    /// 通用 POST Stream 请求
+    /// - Parameters:
+    ///   - endpoint: 接口路径
+    ///   - parameters: 请求参数
+    ///   - responseType: 响应数据模型(可选)
+    ///   - completion: 请求完成的回调
+    func postStream<T: TSBaseModel>(
+        urlType: TSNeURLType,
+        parameters: [String: Any]? = nil,
+        responseType: T.Type? = nil,
+        streamHandler:@escaping (String) -> Void,
+        completion: @escaping (Result<Any, Error>) -> Void
+    ) -> StreamPostRequest{
+        let urlString = urlType.getUrlString()
+        
+        let streamRequest = StreamPostRequest(url: URL(string: urlString)!, parameters: parameters) { data in
+            if let string = String(data: data, encoding: .utf8) {
+                print("Received chunk: \(string)")
+                streamHandler(string)
+            }
+        } completionHandler: { error in
+            if let error = error {
+                print("Request failed with error: \(error)")
+                completion(.failure(error))
+            } else {
+                print("Request completed successfully.")
+                completion(.success("success"))
+            }
+        }
+        streamRequest.startRequest()
+        
+        return streamRequest
+    }
 
 }
 

+ 3 - 2
Podfile

@@ -12,13 +12,14 @@ target 'AIEmoji' do
   pod 'SnapKit'
   pod 'SVProgressHUD'
   pod 'Kingfisher'
-  pod 'Alamofire'
+  pod 'Alamofire', '~> 5.4.4'
   pod 'MJRefresh'
   pod 'IQKeyboardManagerSwift'
   pod 'JXSegmentedView'
   pod 'JXPagingView/Paging'
   pod 'Masonry'
-  
+  pod 'SwiftyMarkdown'
+  pod 'RealmSwift', '~>10'
 end
 
 

+ 12 - 4
Podfile.lock

@@ -1,5 +1,5 @@
 PODS:
-  - Alamofire (5.10.2)
+  - Alamofire (5.4.4)
   - IQKeyboardCore (1.0.5)
   - IQKeyboardManagerSwift (8.0.0):
     - IQKeyboardManagerSwift/Appearance (= 8.0.0)
@@ -43,6 +43,7 @@ PODS:
   - JXPagingView/Paging (2.1.3)
   - JXSegmentedView (1.4.1)
   - Kingfisher (8.1.3)
+  - MarkdownView (1.9.1)
   - Masonry (1.1.0)
   - MJRefresh (3.7.9)
   - ObjectMapper (4.4.2)
@@ -50,18 +51,21 @@ PODS:
   - SVProgressHUD (2.3.1):
     - SVProgressHUD/Core (= 2.3.1)
   - SVProgressHUD/Core (2.3.1)
+  - SwiftyMarkdown (1.2.4)
 
 DEPENDENCIES:
-  - Alamofire
+  - Alamofire (~> 5.4.4)
   - IQKeyboardManagerSwift
   - JXPagingView/Paging
   - JXSegmentedView
   - Kingfisher
+  - MarkdownView
   - Masonry
   - MJRefresh
   - ObjectMapper
   - SnapKit
   - SVProgressHUD
+  - SwiftyMarkdown
 
 SPEC REPOS:
   trunk:
@@ -77,14 +81,16 @@ SPEC REPOS:
     - JXPagingView
     - JXSegmentedView
     - Kingfisher
+    - MarkdownView
     - Masonry
     - MJRefresh
     - ObjectMapper
     - SnapKit
     - SVProgressHUD
+    - SwiftyMarkdown
 
 SPEC CHECKSUMS:
-  Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
+  Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9
   IQKeyboardCore: 28c8bf3bcd8ba5aa1570b318cbc4da94b861711e
   IQKeyboardManagerSwift: 0c6fbbaa2e60739e48d7cf59f25661471a7a3a65
   IQKeyboardNotification: d7382c4466c5a5adef92c7452ebf861b36050088
@@ -96,12 +102,14 @@ SPEC CHECKSUMS:
   JXPagingView: afdd2e9af09c90160dd232b970d603cc6e7ddd0e
   JXSegmentedView: cd73555ce2134d1656db2cb383ba9c2f36fb5078
   Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef
+  MarkdownView: 22249778219cc22901f1454e8355298dd0bda7f0
   Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
   MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
   ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677
   SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
   SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22
+  SwiftyMarkdown: 7cdec87c6ab5cd9ea0e22ced288e889960649ba5
 
-PODFILE CHECKSUM: 0ef421e6c2e505066564b341dcdbe67ad5b551ae
+PODFILE CHECKSUM: fb3b545ddff07a5339fc12bcf8e3efd3833a4eb7
 
 COCOAPODS: 1.16.2