From b4db79eba89c1de03bec6af91cf49f3206065e60 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Wed, 28 Jan 2026 16:35:47 +0800 Subject: [PATCH] 2 --- keyBoard.xcodeproj/project.pbxproj | 48 +++++ .../AI/ai_message_icon.imageset/Contents.json | 22 +++ .../ai_message_icon@2x.png | Bin 0 -> 2090 bytes .../ai_message_icon@3x.png | Bin 0 -> 3935 bytes .../Class/AiTalk/M/KBChattedCompanionModel.h | 54 ++++++ .../Class/AiTalk/M/KBChattedCompanionModel.m | 19 ++ .../Class/AiTalk/M/KBLikedCompanionModel.h | 54 ++++++ .../Class/AiTalk/M/KBLikedCompanionModel.m | 19 ++ keyBoard/Class/AiTalk/V/KBAIMessageCell.h | 47 +++++ keyBoard/Class/AiTalk/V/KBAIMessageCell.m | 157 ++++++++++++++++ keyBoard/Class/AiTalk/VC/KBAIHomeVC.m | 28 +++ .../Class/AiTalk/VC/KBAIMessageChatingVC.h | 17 ++ .../Class/AiTalk/VC/KBAIMessageChatingVC.m | 99 ++++++++++ keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h | 33 ++++ keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m | 142 +++++++++++++++ keyBoard/Class/AiTalk/VC/KBAIMessageVC.h | 17 ++ keyBoard/Class/AiTalk/VC/KBAIMessageVC.m | 170 ++++++++++++++++++ keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h | 17 ++ keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m | 110 ++++++++++++ keyBoard/Class/AiTalk/VM/AIMessageVM.h | 16 ++ keyBoard/Class/AiTalk/VM/AIMessageVM.m | 12 ++ keyBoard/Class/AiTalk/VM/AiVM.h | 12 ++ keyBoard/Class/AiTalk/VM/AiVM.m | 92 ++++++++++ 23 files changed, 1185 insertions(+) create mode 100644 keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@2x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@3x.png create mode 100644 keyBoard/Class/AiTalk/M/KBChattedCompanionModel.h create mode 100644 keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m create mode 100644 keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h create mode 100644 keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m create mode 100644 keyBoard/Class/AiTalk/V/KBAIMessageCell.h create mode 100644 keyBoard/Class/AiTalk/V/KBAIMessageCell.m create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageVC.h create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageVC.m create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h create mode 100644 keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m create mode 100644 keyBoard/Class/AiTalk/VM/AIMessageVM.h create mode 100644 keyBoard/Class/AiTalk/VM/AIMessageVM.m diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 182d8fb..b49be76 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -146,6 +146,14 @@ 048FFD242F28A836005D62AE /* KBChatLimitPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */; }; 048FFD272F28C6CF005D62AE /* KBImagePositionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD262F28C6CF005D62AE /* KBImagePositionButton.m */; }; 048FFD2A2F28E99A005D62AE /* KBCommentModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD292F28E99A005D62AE /* KBCommentModel.m */; }; + 048FFD2D2F29F356005D62AE /* KBAIMessageVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */; }; + 048FFD302F29F3C3005D62AE /* KBAIMessageZanVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */; }; + 048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */; }; + 048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD362F29F400005D62AE /* KBAIMessageListVC.m */; }; + 048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD352F29F88E005D62AE /* AIMessageVM.m */; }; + 048FFD372F29F410005D62AE /* KBAIMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD392F29F410005D62AE /* KBAIMessageCell.m */; }; + 048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */; }; + 048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */; }; 0498BD622EDFFC12006CC1D5 /* KBMyVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */; }; 0498BD652EE0116D006CC1D5 /* KBEmailLoginVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD642EE0116D006CC1D5 /* KBEmailLoginVC.m */; }; 0498BD682EE01180006CC1D5 /* KBEmailRegistVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD672EE01180006CC1D5 /* KBEmailRegistVC.m */; }; @@ -550,6 +558,22 @@ 048FFD262F28C6CF005D62AE /* KBImagePositionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBImagePositionButton.m; sourceTree = ""; }; 048FFD282F28E99A005D62AE /* KBCommentModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCommentModel.h; sourceTree = ""; }; 048FFD292F28E99A005D62AE /* KBCommentModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCommentModel.m; sourceTree = ""; }; + 048FFD2B2F29F356005D62AE /* KBAIMessageVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageVC.h; sourceTree = ""; }; + 048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageVC.m; sourceTree = ""; }; + 048FFD2E2F29F3C3005D62AE /* KBAIMessageZanVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageZanVC.h; sourceTree = ""; }; + 048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageZanVC.m; sourceTree = ""; }; + 048FFD312F29F3D2005D62AE /* KBAIMessageChatingVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageChatingVC.h; sourceTree = ""; }; + 048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageChatingVC.m; sourceTree = ""; }; + 048FFD342F29F88E005D62AE /* AIMessageVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AIMessageVM.h; sourceTree = ""; }; + 048FFD352F29F400005D62AE /* KBAIMessageListVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageListVC.h; sourceTree = ""; }; + 048FFD352F29F88E005D62AE /* AIMessageVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AIMessageVM.m; sourceTree = ""; }; + 048FFD362F29F400005D62AE /* KBAIMessageListVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageListVC.m; sourceTree = ""; }; + 048FFD382F29F410005D62AE /* KBAIMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageCell.h; sourceTree = ""; }; + 048FFD392F29F410005D62AE /* KBAIMessageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageCell.m; sourceTree = ""; }; + 048FFD3A2F29F500005D62AE /* KBLikedCompanionModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLikedCompanionModel.h; sourceTree = ""; }; + 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLikedCompanionModel.m; sourceTree = ""; }; + 048FFD3D2F29F600005D62AE /* KBChattedCompanionModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChattedCompanionModel.h; sourceTree = ""; }; + 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChattedCompanionModel.m; sourceTree = ""; }; 0498BD5E2EDF2157006CC1D5 /* KBBizCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBizCode.h; sourceTree = ""; }; 0498BD602EDFFC12006CC1D5 /* KBMyVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyVM.h; sourceTree = ""; }; 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyVM.m; sourceTree = ""; }; @@ -1011,6 +1035,10 @@ 048FFD1C2F277486005D62AE /* KBChatHistoryPageModel.m */, 048FFD282F28E99A005D62AE /* KBCommentModel.h */, 048FFD292F28E99A005D62AE /* KBCommentModel.m */, + 048FFD3A2F29F500005D62AE /* KBLikedCompanionModel.h */, + 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */, + 048FFD3D2F29F600005D62AE /* KBChattedCompanionModel.h */, + 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */, ); path = M; sourceTree = ""; @@ -1049,6 +1077,8 @@ 048FFD172F2763A5005D62AE /* KBVoiceInputBar.m */, 048FFD222F28A836005D62AE /* KBChatLimitPopView.h */, 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */, + 048FFD382F29F410005D62AE /* KBAIMessageCell.h */, + 048FFD392F29F410005D62AE /* KBAIMessageCell.m */, ); path = V; sourceTree = ""; @@ -1060,6 +1090,14 @@ 046086722F191B6900757C95 /* KBAiMainVC.m */, 048FFD092F273BFC005D62AE /* KBAIHomeVC.h */, 048FFD0A2F273BFC005D62AE /* KBAIHomeVC.m */, + 048FFD2B2F29F356005D62AE /* KBAIMessageVC.h */, + 048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */, + 048FFD2E2F29F3C3005D62AE /* KBAIMessageZanVC.h */, + 048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */, + 048FFD312F29F3D2005D62AE /* KBAIMessageChatingVC.h */, + 048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */, + 048FFD352F29F400005D62AE /* KBAIMessageListVC.h */, + 048FFD362F29F400005D62AE /* KBAIMessageListVC.m */, ); path = VC; sourceTree = ""; @@ -1101,6 +1139,8 @@ 04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */, 04E038ED2F21F0EC002CA5A0 /* AiVM.h */, 04E038EE2F21F0EC002CA5A0 /* AiVM.m */, + 048FFD342F29F88E005D62AE /* AIMessageVM.h */, + 048FFD352F29F88E005D62AE /* AIMessageVM.m */, ); path = VM; sourceTree = ""; @@ -2295,6 +2335,11 @@ 048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */, 046086732F191B6900757C95 /* KBAiMainVC.m in Sources */, 048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */, + 048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */, + 048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */, + 048FFD372F29F410005D62AE /* KBAIMessageCell.m in Sources */, + 048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */, + 048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */, 04791F952ED48028004E8522 /* KBFeedBackVC.m in Sources */, 04890A042EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m in Sources */, 04890A052EC0BBBB00FABA60 /* KBCategoryTitleImageView.m in Sources */, @@ -2326,6 +2371,7 @@ 0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */, 048FFD1D2F277486005D62AE /* KBChatHistoryPageModel.m in Sources */, 048FFD1E2F277486005D62AE /* KBChatHistoryModel.m in Sources */, + 048FFD302F29F3C3005D62AE /* KBAIMessageZanVC.m in Sources */, 0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */, 0450AC1B2EF11E4400B6AF06 /* KBStoreKitBridge.swift in Sources */, 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */, @@ -2342,6 +2388,7 @@ A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */, A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */, 471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */, + 048FFD2D2F29F356005D62AE /* KBAIMessageVC.m in Sources */, A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */, A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */, A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */, @@ -2379,6 +2426,7 @@ 04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */, 04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */, 048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */, + 048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */, 048908F22EC047FD00FABA60 /* KBShopHeadView.m in Sources */, 0498BD742EE02E3D006CC1D5 /* KBRegistVerEmailVC.m in Sources */, 049FB2292EC31BB000FAB05D /* KBChangeNicknamePopView.m in Sources */, diff --git a/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json b/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json new file mode 100644 index 0000000..36f36bc --- /dev/null +++ b/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ai_message_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ai_message_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@2x.png b/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..31958cfa5001386e575919bb8e63d37397dd0b39 GIT binary patch literal 2090 zcmV+_2-WwAP)b+s?bA>LYT zbGd)D(`Sr1Vu&S&Trj`_Q{nc^uE3TmKS(}ZZV-dKPAwU^hyx2uu&H4Qj3lc9Au><^ z)8ygu7ILQzOt7gzEoz#sVMSmMRe4w5U*1gqxQ!arq9(OX)iWcIdt!vp$H=*V)I3q6 zCbi)J7oUpzDiH8@gxE*^v<(h$fm4HAd=c1AC_LdhsBJ6;7dXMKUQP`O1mJBU`1IRS z+>((a_fM{j5k~da^8yw+UCr57rxrxUr!)v1E8jHaZu5hl!$(z8TisU+1 z^Gq0A;arPsT>^R6@T960Y-dBH&J z-o5+Fd-m-4h&f}-p%(6FsDX1A$fEX@ni+>W>({Rz5g636LvC*Fji{)o5Apo`{Op!( zfdsEj)d8qneza~H@2>ps_n6NlNeXNQd&H*R!u{{X+Fq@**+$;k!yp+kp`F*at* zn5cyd7xs3dmKz2d(Bc|01oDV|$u;88;o!l86L#+0xjHyFxN}8CMcJiGm$D7$%aFbkS!<@grf54O}Q>K$+%sbGc1x+;qpLYP%1#$ZH>DYk-2fkTV zRaLQj_wKL5!^6M2c=2MPYtrq&rOF&J#F9gN

<@!-mpE3fzp zLaZJ_hsm8YXU_Xt5ZLCU+F*ntkc}Um+p|rZHbn&n1_tpum^N+N5wmDBXU;s5mzS3r z6cp4>52v@x=6i|nguoCllct6vBO@b7&=c`kadB}aeKRg`Wd{x%I7-g2VZ(;dI^ZD) z47Llz<*Cc62lvs^rAt$+;;ml2Iu#7Cz_g0vmKTgbmN#x==DULj4`wy*ckkZ40xO@M zQISs(49*Qq`Zl~Z0t0OTxKg#YY(uA{q@5QLPA2WKD|$G-@g49Yg;YE`Ae|3@P@{75tyf_GP!AM-L7qClC;3iS+HP1 z9iIcO_c~F_XY>EK62#9UE8*8`{LB8 zQwitJolDYQ%UQz>thBVWi^iB|n~{-mi8$^#_qhufE}YQMp)FgsY#%*(^f$yk-v0gje>TQE+vMcr--yvAp6Na} zbm-7;$BrEvzhudhRB8}o($RvZnmrlbUsf-LI%{he?N=uB6r8HBKh5d8d;k9Z+r-Ak z@`p^nl#~>gQ1^M8eYBvdMj&|CbZ~6hwtV^WV?{+p`4ZRT%C+BDZP>6OoWD!(Zqu&X zpp&Ohtz}EGpr9Z}ubY#q?UbPbEiOFc66NH94ry{njGZ(+?l&W{Yp2dV^p0Gtg}{#V z^z`kE7cXAeA-H|$-Me@5^eKLB@iO$r6LDy9&ASU^fQ=&COE-Y{uEYs)(xnp+y`t|#IZTz6iaaqTX9p~fp8FToP zgEQRGK;Pw_0vXKIAw%xCNl-cNIPgP2IK%znS@p3lflQs(Az%Ki7+m38i?26<6^cAA z=bBd2s11&Ag>x;U-UJdz;*aXwmp4{}8yw+UC&&5(lJK7niE^(1ntNIXC%D1UD~E;z zk_bS8I$Y;!8im0HPH?N2kS_uO;J0;H9iCKbo~pqCE^um)h_3l&aRpzB7RDikqZV`V1i8zO<6O$0*$KN69%#GG{_U~ z7q$H30h?OfKaj7|7*3xt=7=Gd9C8r{7MKdRXa55L0RR6zfw*}9000I_L_t&o0GWvB UOYWs^ga7~l07*qoM6N<$f;-0zqW}N^ literal 0 HcmV?d00001 diff --git a/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@3x.png b/keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..defc74385229f41420a6cf53003a14e112e4edbc GIT binary patch literal 3935 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 tT=*{l00960JCw + +NS_ASSUME_NONNULL_BEGIN + +/// 聊过天的 AI 角色模型(Chatting 列表) +@interface KBChattedCompanionModel : NSObject + +/// 角色 ID +@property (nonatomic, assign) NSInteger companionId; +/// 角色名称 +@property (nonatomic, copy) NSString *name; +/// 头像 URL +@property (nonatomic, copy) NSString *avatarUrl; +/// 封面图 URL +@property (nonatomic, copy) NSString *coverImageUrl; +/// 性别 +@property (nonatomic, copy) NSString *gender; +/// 年龄范围 +@property (nonatomic, copy) NSString *ageRange; +/// 简短描述 +@property (nonatomic, copy) NSString *shortDesc; +/// 介绍文本 +@property (nonatomic, copy) NSString *introText; +/// 性格标签 +@property (nonatomic, copy) NSString *personalityTags; +/// 说话风格 +@property (nonatomic, copy) NSString *speakingStyle; +/// 排序 +@property (nonatomic, assign) NSInteger sortOrder; +/// 热度分数 +@property (nonatomic, assign) NSInteger popularityScore; +/// 开场白 +@property (nonatomic, copy) NSString *prologue; +/// 开场白音频 +@property (nonatomic, copy) NSString *prologueAudio; +/// 点赞数 +@property (nonatomic, assign) NSInteger likeCount; +/// 评论数 +@property (nonatomic, assign) NSInteger commentCount; +/// 是否已点赞 +@property (nonatomic, assign) BOOL liked; +/// 创建时间 +@property (nonatomic, copy) NSString *createdAt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m b/keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m new file mode 100644 index 0000000..1dee38b --- /dev/null +++ b/keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m @@ -0,0 +1,19 @@ +// +// KBChattedCompanionModel.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBChattedCompanionModel.h" +#import + +@implementation KBChattedCompanionModel + ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"companionId": @"id" + }; +} + +@end diff --git a/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h b/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h new file mode 100644 index 0000000..3105f1f --- /dev/null +++ b/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h @@ -0,0 +1,54 @@ +// +// KBLikedCompanionModel.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 点赞过的 AI 角色模型(Thumbs Up 列表) +@interface KBLikedCompanionModel : NSObject + +/// 角色 ID +@property (nonatomic, assign) NSInteger companionId; +/// 角色名称 +@property (nonatomic, copy) NSString *name; +/// 头像 URL +@property (nonatomic, copy) NSString *avatarUrl; +/// 封面图 URL +@property (nonatomic, copy) NSString *coverImageUrl; +/// 性别 +@property (nonatomic, copy) NSString *gender; +/// 年龄范围 +@property (nonatomic, copy) NSString *ageRange; +/// 简短描述 +@property (nonatomic, copy) NSString *shortDesc; +/// 介绍文本 +@property (nonatomic, copy) NSString *introText; +/// 性格标签 +@property (nonatomic, copy) NSString *personalityTags; +/// 说话风格 +@property (nonatomic, copy) NSString *speakingStyle; +/// 排序 +@property (nonatomic, assign) NSInteger sortOrder; +/// 热度分数 +@property (nonatomic, assign) NSInteger popularityScore; +/// 开场白 +@property (nonatomic, copy) NSString *prologue; +/// 开场白音频 +@property (nonatomic, copy) NSString *prologueAudio; +/// 点赞数 +@property (nonatomic, assign) NSInteger likeCount; +/// 评论数 +@property (nonatomic, assign) NSInteger commentCount; +/// 是否已点赞 +@property (nonatomic, assign) BOOL liked; +/// 创建时间 +@property (nonatomic, copy) NSString *createdAt; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m b/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m new file mode 100644 index 0000000..208df40 --- /dev/null +++ b/keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m @@ -0,0 +1,19 @@ +// +// KBLikedCompanionModel.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBLikedCompanionModel.h" +#import + +@implementation KBLikedCompanionModel + ++ (NSDictionary *)mj_replacedKeyFromPropertyName { + return @{ + @"companionId": @"id" + }; +} + +@end diff --git a/keyBoard/Class/AiTalk/V/KBAIMessageCell.h b/keyBoard/Class/AiTalk/V/KBAIMessageCell.h new file mode 100644 index 0000000..0438a74 --- /dev/null +++ b/keyBoard/Class/AiTalk/V/KBAIMessageCell.h @@ -0,0 +1,47 @@ +// +// KBAIMessageCell.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class KBAIMessageCell; + +@protocol KBAIMessageCellDelegate + +@optional +/// 点击删除按钮 +- (void)messageCell:(KBAIMessageCell *)cell didTapDeleteAtIndexPath:(NSIndexPath *)indexPath; + +@end + +@interface KBAIMessageCell : UITableViewCell + +@property (nonatomic, weak) id delegate; +@property (nonatomic, strong) NSIndexPath *indexPath; + +/// 头像 +@property (nonatomic, strong, readonly) UIImageView *avatarImageView; +/// 昵称 +@property (nonatomic, strong, readonly) UILabel *nameLabel; +/// 消息内容 +@property (nonatomic, strong, readonly) UILabel *contentLabel; +/// 时间 +@property (nonatomic, strong, readonly) UILabel *timeLabel; +/// 置顶图标(只显示,不做功能) +@property (nonatomic, strong, readonly) UIImageView *pinIconView; + +/// 配置数据 +- (void)configWithAvatar:(NSString *)avatarUrl + name:(NSString *)name + content:(NSString *)content + time:(NSString *)time + isPinned:(BOOL)isPinned; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/V/KBAIMessageCell.m b/keyBoard/Class/AiTalk/V/KBAIMessageCell.m new file mode 100644 index 0000000..57c36a8 --- /dev/null +++ b/keyBoard/Class/AiTalk/V/KBAIMessageCell.m @@ -0,0 +1,157 @@ +// +// KBAIMessageCell.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageCell.h" +#import +#import + +@interface KBAIMessageCell () + +@property (nonatomic, strong, readwrite) UIImageView *avatarImageView; +@property (nonatomic, strong, readwrite) UILabel *nameLabel; +@property (nonatomic, strong, readwrite) UILabel *contentLabel; +@property (nonatomic, strong, readwrite) UILabel *timeLabel; +@property (nonatomic, strong, readwrite) UIImageView *pinIconView; + +@end + +@implementation KBAIMessageCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.selectionStyle = UITableViewCellSelectionStyleNone; + self.backgroundColor = [UIColor whiteColor]; + [self setupSubviews]; + } + return self; +} + +- (void)setupSubviews { + [self.contentView addSubview:self.avatarImageView]; + [self.contentView addSubview:self.nameLabel]; + [self.contentView addSubview:self.contentLabel]; + [self.contentView addSubview:self.timeLabel]; + [self.contentView addSubview:self.pinIconView]; + + [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(16); + make.centerY.equalTo(self.contentView); + make.width.height.mas_equalTo(50); + }]; + + [self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.avatarImageView.mas_right).offset(12); + make.top.equalTo(self.avatarImageView).offset(2); + make.right.lessThanOrEqualTo(self.timeLabel.mas_left).offset(-8); + }]; + + [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.nameLabel); + make.top.equalTo(self.nameLabel.mas_bottom).offset(4); + make.right.lessThanOrEqualTo(self.timeLabel.mas_left).offset(-8); + }]; + + [self.pinIconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-16); + make.top.equalTo(self.nameLabel); + make.width.height.mas_equalTo(16); + }]; + + [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-16); + make.bottom.equalTo(self.contentLabel); + }]; +} + +- (void)configWithAvatar:(NSString *)avatarUrl + name:(NSString *)name + content:(NSString *)content + time:(NSString *)time + isPinned:(BOOL)isPinned { + if (avatarUrl.length > 0) { + [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:avatarUrl] + placeholderImage:KBAvatarPlaceholderImage]; + } else { + self.avatarImageView.image = KBAvatarPlaceholderImage; + } + self.nameLabel.text = name; + self.contentLabel.text = content; + self.timeLabel.text = time; + self.pinIconView.hidden = !isPinned; + + // 如果有置顶图标,时间标签需要调整位置 + if (isPinned) { + [self.timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-16); + make.bottom.equalTo(self.contentLabel); + }]; + } else { + [self.timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.contentView).offset(-16); + make.bottom.equalTo(self.contentLabel); + }]; + } +} + +#pragma mark - Lazy Load + +- (UIImageView *)avatarImageView { + if (!_avatarImageView) { + _avatarImageView = [[UIImageView alloc] init]; + _avatarImageView.contentMode = UIViewContentModeScaleAspectFill; + _avatarImageView.layer.cornerRadius = 25; + _avatarImageView.layer.masksToBounds = YES; + _avatarImageView.backgroundColor = [UIColor colorWithHex:0xF5F5F5]; + } + return _avatarImageView; +} + +- (UILabel *)nameLabel { + if (!_nameLabel) { + _nameLabel = [[UILabel alloc] init]; + _nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + _nameLabel.textColor = [UIColor colorWithHex:0x1B1F1A]; + } + return _nameLabel; +} + +- (UILabel *)contentLabel { + if (!_contentLabel) { + _contentLabel = [[UILabel alloc] init]; + _contentLabel.font = [UIFont systemFontOfSize:14]; + _contentLabel.textColor = [UIColor colorWithHex:0x9F9F9F]; + _contentLabel.lineBreakMode = NSLineBreakByTruncatingTail; + } + return _contentLabel; +} + +- (UILabel *)timeLabel { + if (!_timeLabel) { + _timeLabel = [[UILabel alloc] init]; + _timeLabel.font = [UIFont systemFontOfSize:12]; + _timeLabel.textColor = [UIColor colorWithHex:0x9F9F9F]; + _timeLabel.textAlignment = NSTextAlignmentRight; + } + return _timeLabel; +} + +- (UIImageView *)pinIconView { + if (!_pinIconView) { + _pinIconView = [[UIImageView alloc] init]; + _pinIconView.contentMode = UIViewContentModeScaleAspectFit; + // 使用系统图标或自定义图标 + if (@available(iOS 13.0, *)) { + _pinIconView.image = [UIImage systemImageNamed:@"pin.fill"]; + _pinIconView.tintColor = [UIColor colorWithHex:0x9F9F9F]; + } + _pinIconView.hidden = YES; + } + return _pinIconView; +} + +@end diff --git a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m index 86dc119..659604d 100644 --- a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m +++ b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m @@ -17,6 +17,7 @@ #import "KBVipPay.h" #import "KBUserSessionManager.h" #import "LSTPopView.h" +#import "KBAIMessageVC.h" #import @interface KBAIHomeVC () @@ -68,6 +69,9 @@ /// 是否正在等待 AI 回复(用于禁止滚动) @property (nonatomic, assign) BOOL isWaitingForAIResponse; +/// 右上角消息按钮 +@property (nonatomic, strong) UIButton *messageButton; + @end @implementation KBAIHomeVC @@ -113,6 +117,14 @@ make.edges.equalTo(self.view); }]; + // 右上角消息按钮 + [self.view addSubview:self.messageButton]; + [self.messageButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.view).offset(KB_STATUSBAR_HEIGHT + 10); + make.right.equalTo(self.view).offset(-16); + make.width.height.mas_equalTo(32); + }]; + // 底部毛玻璃背景 [self.view addSubview:self.bottomBackgroundView]; [self.bottomBackgroundView addSubview:self.bottomBlurEffectView]; @@ -540,6 +552,22 @@ return _bottomMaskLayer; } +- (UIButton *)messageButton { + if (!_messageButton) { + _messageButton = [UIButton buttonWithType:UIButtonTypeCustom]; + [_messageButton setImage:[UIImage imageNamed:@"ai_message_icon"] forState:UIControlStateNormal]; + [_messageButton addTarget:self action:@selector(messageButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + } + return _messageButton; +} + +#pragma mark - Actions + +- (void)messageButtonTapped { + KBAIMessageVC *vc = [[KBAIMessageVC alloc] init]; + [self.navigationController pushViewController:vc animated:YES]; +} + #pragma mark - KBVoiceToTextManagerDelegate - (void)voiceToTextManager:(KBVoiceToTextManager *)manager diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h b/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h new file mode 100644 index 0000000..313368f --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h @@ -0,0 +1,17 @@ +// +// KBAIMessageChatingVC.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageListVC.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Chatting 消息列表 +@interface KBAIMessageChatingVC : KBAIMessageListVC + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m b/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m new file mode 100644 index 0000000..1fae0f6 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m @@ -0,0 +1,99 @@ +// +// KBAIMessageChatingVC.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageChatingVC.h" +#import "AiVM.h" +#import "KBChattedCompanionModel.h" +#import "KBHUD.h" + +@interface KBAIMessageChatingVC () + +@property (nonatomic, strong) AiVM *viewModel; +@property (nonatomic, strong) NSMutableArray *chattedList; + +@end + +@implementation KBAIMessageChatingVC + +#pragma mark - Lifecycle + +- (void)viewDidLoad { + self.listType = 1; // Chatting + [super viewDidLoad]; +} + +#pragma mark - 2:数据加载 + +- (void)loadData { + [KBHUD show]; + __weak typeof(self) weakSelf = self; + [self.viewModel fetchChattedCompanionsWithCompletion:^(NSArray * _Nullable list, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [KBHUD dismiss]; + if (error) { + [KBHUD showError:error.localizedDescription]; + return; + } + + [weakSelf.chattedList removeAllObjects]; + if (list.count > 0) { + [weakSelf.chattedList addObjectsFromArray:list]; + } + [weakSelf.dataArray removeAllObjects]; + + // 转换为通用数据格式 + for (KBChattedCompanionModel *model in weakSelf.chattedList) { + NSMutableDictionary *item = [NSMutableDictionary dictionary]; + item[@"avatar"] = model.avatarUrl ?: @""; + item[@"name"] = model.name ?: @""; + item[@"content"] = model.shortDesc ?: @""; + item[@"time"] = model.createdAt ?: @""; + item[@"isPinned"] = @NO; + item[@"companionId"] = @(model.companionId); + [weakSelf.dataArray addObject:item]; + } + + [weakSelf.tableView reloadData]; + }); + }]; +} + +#pragma mark - 删除 + +- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row >= self.chattedList.count) { + return; + } + + // TODO: 如果有删除聊天记录的接口,在这里调用 + // 目前先只做本地删除 + if (indexPath.row < self.chattedList.count) { + [self.chattedList removeObjectAtIndex:indexPath.row]; + } + if (indexPath.row < self.dataArray.count) { + [self.dataArray removeObjectAtIndex:indexPath.row]; + } + [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; +} + +#pragma mark - Lazy Load + +- (AiVM *)viewModel { + if (!_viewModel) { + _viewModel = [[AiVM alloc] init]; + } + return _viewModel; +} + +- (NSMutableArray *)chattedList { + if (!_chattedList) { + _chattedList = [NSMutableArray array]; + } + return _chattedList; +} + +@end diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h b/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h new file mode 100644 index 0000000..e061221 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h @@ -0,0 +1,33 @@ +// +// KBAIMessageListVC.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 消息列表基类,供 ZanVC 和 ChatingVC 继承 +@interface KBAIMessageListVC : UIViewController + +/// 列表类型:0 = Thumbs Up, 1 = Chatting +@property (nonatomic, assign) NSInteger listType; + +/// 数据源 +@property (nonatomic, strong) NSMutableArray *dataArray; + +/// TableView +@property (nonatomic, strong) UITableView *tableView; + +/// 加载数据(子类重写) +- (void)loadData; + +/// 删除某条数据(子类重写) +- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m b/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m new file mode 100644 index 0000000..2d3ade3 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m @@ -0,0 +1,142 @@ +// +// KBAIMessageListVC.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageListVC.h" +#import "KBAIMessageCell.h" +#import + +@interface KBAIMessageListVC () + +@end + +@implementation KBAIMessageListVC + +#pragma mark - Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + /// 1:控件初始化 + [self setupUI]; + + /// 2:加载数据 + [self loadData]; +} + +#pragma mark - 1:控件初始化 + +- (void)setupUI { + [self.view addSubview:self.tableView]; + [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self.view); + }]; +} + +#pragma mark - 2:数据加载 + +- (void)loadData { + // 子类重写 +} + +- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath { + // 子类重写 +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.dataArray.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + KBAIMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KBAIMessageCell" forIndexPath:indexPath]; + cell.indexPath = indexPath; + + // 子类配置数据 + if (indexPath.row < self.dataArray.count) { + NSDictionary *item = self.dataArray[indexPath.row]; + [cell configWithAvatar:item[@"avatar"] + name:item[@"name"] + content:item[@"content"] + time:item[@"time"] + isPinned:[item[@"isPinned"] boolValue]]; + } + + return cell; +} + +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 76; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + // 子类处理点击事件 +} + +/// 左滑删除 +- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) { + + __weak typeof(self) weakSelf = self; + + // 删除按钮 + UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive + title:nil + handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { + [weakSelf deleteItemAtIndexPath:indexPath]; + completionHandler(YES); + }]; + deleteAction.backgroundColor = [UIColor colorWithHex:0xF44336]; + if (@available(iOS 13.0, *)) { + deleteAction.image = [UIImage systemImageNamed:@"trash.fill"]; + } + + UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]]; + config.performsFirstActionWithFullSwipe = NO; + return config; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + return YES; +} + +#pragma mark - JXCategoryListContentViewDelegate + +- (UIView *)listView { + return self.view; +} + +#pragma mark - Lazy Load + +- (UITableView *)tableView { + if (!_tableView) { + _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.backgroundColor = [UIColor whiteColor]; + _tableView.showsVerticalScrollIndicator = NO; + [_tableView registerClass:[KBAIMessageCell class] forCellReuseIdentifier:@"KBAIMessageCell"]; + + if (@available(iOS 15.0, *)) { + _tableView.sectionHeaderTopPadding = 0; + } + } + return _tableView; +} + +- (NSMutableArray *)dataArray { + if (!_dataArray) { + _dataArray = [NSMutableArray array]; + } + return _dataArray; +} + +@end diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageVC.h b/keyBoard/Class/AiTalk/VC/KBAIMessageVC.h new file mode 100644 index 0000000..6e8b856 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageVC.h @@ -0,0 +1,17 @@ +// +// KBAIMessageVC.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "BaseViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +/// AI 消息主页面(Thumbs Up / Chatting 分页) +@interface KBAIMessageVC : BaseViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageVC.m b/keyBoard/Class/AiTalk/VC/KBAIMessageVC.m new file mode 100644 index 0000000..4ca77eb --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageVC.m @@ -0,0 +1,170 @@ +// +// KBAIMessageVC.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageVC.h" +#import +#import "KBAIMessageZanVC.h" +#import "KBAIMessageChatingVC.h" +#import + +@interface KBAIMessageVC () + +/// 分类标签视图 +@property (nonatomic, strong) JXCategoryTitleView *categoryView; + +/// 列表容器 +@property (nonatomic, strong) JXCategoryListContainerView *listContainerView; + +/// 右侧搜索按钮 +@property (nonatomic, strong) UIButton *searchButton; + +/// 标题数组 +@property (nonatomic, strong) NSArray *titles; + +@end + +@implementation KBAIMessageVC + +#pragma mark - Lifecycle + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + /// 1:控件初始化 + [self setupUI]; + + /// 2:绑定事件 + [self bindActions]; +} + +#pragma mark - 1:控件初始化 + +- (void)setupUI { + // 隐藏默认导航栏标题 + self.kb_titleLabel.hidden = YES; + + // 添加分类视图到导航栏位置 + [self.kb_navView addSubview:self.categoryView]; + [self.categoryView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.kb_backButton.mas_right).offset(0); + make.centerY.equalTo(self.kb_backButton); + make.height.mas_equalTo(44); + make.width.mas_equalTo(180); + }]; + + // 添加搜索按钮 + [self.kb_navView addSubview:self.searchButton]; + [self.searchButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.kb_navView).offset(-16); + make.centerY.equalTo(self.kb_backButton); + make.width.height.mas_equalTo(24); + }]; + + // 添加列表容器 + [self.view addSubview:self.listContainerView]; + [self.listContainerView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.kb_navView.mas_bottom); + make.left.right.bottom.equalTo(self.view); + }]; + + // 关联 categoryView 和 listContainerView + self.categoryView.listContainer = self.listContainerView; +} + +#pragma mark - 2:绑定事件 + +- (void)bindActions { + [self.searchButton addTarget:self action:@selector(searchButtonTapped) forControlEvents:UIControlEventTouchUpInside]; +} + +#pragma mark - Actions + +- (void)searchButtonTapped { + // TODO: 跳转搜索页面 + NSLog(@"搜索按钮点击"); +} + +#pragma mark - JXCategoryViewDelegate + +- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index { + NSLog(@"选中分类:%@", self.titles[index]); +} + +#pragma mark - JXCategoryListContainerViewDelegate + +- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView { + return self.titles.count; +} + +- (id)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index { + if (index == 0) { + return [[KBAIMessageZanVC alloc] init]; + } else { + return [[KBAIMessageChatingVC alloc] init]; + } +} + +#pragma mark - Lazy Load + +- (NSArray *)titles { + if (!_titles) { + _titles = @[KBLocalized(@"Thumbs Up"), KBLocalized(@"Chatting")]; + } + return _titles; +} + +- (JXCategoryTitleView *)categoryView { + if (!_categoryView) { + _categoryView = [[JXCategoryTitleView alloc] init]; + _categoryView.backgroundColor = [UIColor clearColor]; + _categoryView.titles = self.titles; + _categoryView.delegate = self; + + // 标题样式 + _categoryView.titleFont = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; + _categoryView.titleSelectedFont = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; + _categoryView.titleColor = [UIColor colorWithHex:0x9F9F9F]; + _categoryView.titleSelectedColor = [UIColor colorWithHex:0x1B1F1A]; + + // 不需要指示器 + _categoryView.indicators = @[]; + + // 间距设置 + _categoryView.cellSpacing = 20; + _categoryView.contentEdgeInsetLeft = 0; + _categoryView.contentEdgeInsetRight = 0; + _categoryView.averageCellSpacingEnabled = NO; + + // 禁用缩放和渐变 + _categoryView.cellWidthZoomEnabled = NO; + _categoryView.titleColorGradientEnabled = NO; + } + return _categoryView; +} + +- (JXCategoryListContainerView *)listContainerView { + if (!_listContainerView) { + _listContainerView = [[JXCategoryListContainerView alloc] initWithType:JXCategoryListContainerType_ScrollView delegate:self]; + _listContainerView.scrollView.bounces = NO; + } + return _listContainerView; +} + +- (UIButton *)searchButton { + if (!_searchButton) { + _searchButton = [UIButton buttonWithType:UIButtonTypeCustom]; + if (@available(iOS 13.0, *)) { + UIImage *searchImage = [UIImage systemImageNamed:@"magnifyingglass"]; + [_searchButton setImage:searchImage forState:UIControlStateNormal]; + _searchButton.tintColor = [UIColor colorWithHex:0x1B1F1A]; + } + } + return _searchButton; +} + +@end diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h b/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h new file mode 100644 index 0000000..7da70b7 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h @@ -0,0 +1,17 @@ +// +// KBAIMessageZanVC.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageListVC.h" + +NS_ASSUME_NONNULL_BEGIN + +/// Thumbs Up 消息列表 +@interface KBAIMessageZanVC : KBAIMessageListVC + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m b/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m new file mode 100644 index 0000000..2a4af44 --- /dev/null +++ b/keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m @@ -0,0 +1,110 @@ +// +// KBAIMessageZanVC.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "KBAIMessageZanVC.h" +#import "AiVM.h" +#import "KBLikedCompanionModel.h" +#import "KBHUD.h" + +@interface KBAIMessageZanVC () + +@property (nonatomic, strong) AiVM *viewModel; +@property (nonatomic, strong) NSMutableArray *likedList; + +@end + +@implementation KBAIMessageZanVC + +#pragma mark - Lifecycle + +- (void)viewDidLoad { + self.listType = 0; // Thumbs Up + [super viewDidLoad]; +} + +#pragma mark - 2:数据加载 + +- (void)loadData { + [KBHUD show]; + __weak typeof(self) weakSelf = self; + [self.viewModel fetchLikedCompanionsWithCompletion:^(NSArray * _Nullable list, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [KBHUD dismiss]; + if (error) { + [KBHUD showError:error.localizedDescription]; + return; + } + + [weakSelf.likedList removeAllObjects]; + if (list.count > 0) { + [weakSelf.likedList addObjectsFromArray:list]; + } + [weakSelf.dataArray removeAllObjects]; + + // 转换为通用数据格式 + for (KBLikedCompanionModel *model in weakSelf.likedList) { + NSMutableDictionary *item = [NSMutableDictionary dictionary]; + item[@"avatar"] = model.avatarUrl ?: @""; + item[@"name"] = model.name ?: @""; + item[@"content"] = model.shortDesc ?: @""; + item[@"time"] = model.createdAt ?: @""; + item[@"isPinned"] = @NO; + item[@"companionId"] = @(model.companionId); + [weakSelf.dataArray addObject:item]; + } + + [weakSelf.tableView reloadData]; + }); + }]; +} + +#pragma mark - 删除 + +- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row >= self.likedList.count) { + return; + } + + KBLikedCompanionModel *model = self.likedList[indexPath.row]; + + __weak typeof(self) weakSelf = self; + [self.viewModel likeCompanionWithCompanionId:model.companionId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + [KBHUD showError:error.localizedDescription]; + return; + } + + // 删除本地数据 + if (indexPath.row < weakSelf.likedList.count) { + [weakSelf.likedList removeObjectAtIndex:indexPath.row]; + } + if (indexPath.row < weakSelf.dataArray.count) { + [weakSelf.dataArray removeObjectAtIndex:indexPath.row]; + } + [weakSelf.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; + }); + }]; +} + +#pragma mark - Lazy Load + +- (AiVM *)viewModel { + if (!_viewModel) { + _viewModel = [[AiVM alloc] init]; + } + return _viewModel; +} + +- (NSMutableArray *)likedList { + if (!_likedList) { + _likedList = [NSMutableArray array]; + } + return _likedList; +} + +@end diff --git a/keyBoard/Class/AiTalk/VM/AIMessageVM.h b/keyBoard/Class/AiTalk/VM/AIMessageVM.h new file mode 100644 index 0000000..a67a73f --- /dev/null +++ b/keyBoard/Class/AiTalk/VM/AIMessageVM.h @@ -0,0 +1,16 @@ +// +// AIMessageVM.h +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AIMessageVM : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VM/AIMessageVM.m b/keyBoard/Class/AiTalk/VM/AIMessageVM.m new file mode 100644 index 0000000..eb6cca7 --- /dev/null +++ b/keyBoard/Class/AiTalk/VM/AIMessageVM.m @@ -0,0 +1,12 @@ +// +// AIMessageVM.m +// keyBoard +// +// Created by Mac on 2026/1/28. +// + +#import "AIMessageVM.h" + +@implementation AIMessageVM + +@end diff --git a/keyBoard/Class/AiTalk/VM/AiVM.h b/keyBoard/Class/AiTalk/VM/AiVM.h index cf8e59c..a95868f 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.h +++ b/keyBoard/Class/AiTalk/VM/AiVM.h @@ -9,6 +9,8 @@ #import "KBPersonaPageModel.h" #import "KBChatHistoryPageModel.h" #import "KBCommentModel.h" +#import "KBLikedCompanionModel.h" +#import "KBChattedCompanionModel.h" NS_ASSUME_NONNULL_BEGIN @@ -142,6 +144,16 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu - (void)likeCompanionWithCompanionId:(NSInteger)companionId completion:(void(^)(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error))completion; +#pragma mark - 点赞列表接口 + +/// 获取当前用户点赞过的 AI 角色列表(Thumbs Up) +/// @param completion 完成回调(返回点赞角色数组) +- (void)fetchLikedCompanionsWithCompletion:(void(^)(NSArray * _Nullable list, NSError * _Nullable error))completion; + +/// 获取当前用户聊过天的 AI 角色列表(Chatting) +/// @param completion 完成回调(返回聊天角色数组) +- (void)fetchChattedCompanionsWithCompletion:(void(^)(NSArray * _Nullable list, NSError * _Nullable error))completion; + @end NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/VM/AiVM.m b/keyBoard/Class/AiTalk/VM/AiVM.m index 03be096..be7ef9d 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.m +++ b/keyBoard/Class/AiTalk/VM/AiVM.m @@ -9,6 +9,8 @@ #import "KBAPI.h" #import "KBNetworkManager.h" #import "KBCommentModel.h" +#import "KBLikedCompanionModel.h" +#import "KBChattedCompanionModel.h" #import @implementation KBAiSyncData @@ -599,4 +601,94 @@ autoShowBusinessError:NO }]; } +#pragma mark - 点赞列表接口 + +- (void)fetchLikedCompanionsWithCompletion:(void (^)(NSArray * _Nullable, NSError * _Nullable))completion { + NSLog(@"[AiVM] /ai-companion/liked request"); + [[KBNetworkManager shared] + GET:@"/ai-companion/liked" + parameters:nil + headers:nil +autoShowBusinessError:NO + completion:^(NSDictionary *_Nullable json, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + NSLog(@"[AiVM] /ai-companion/liked failed: %@", error.localizedDescription ?: @""); + if (completion) { + completion(nil, error); + } + return; + } + + NSLog(@"[AiVM] /ai-companion/liked response: %@", json); + + NSInteger code = [json[@"code"] integerValue]; + if (code != 0) { + NSString *message = json[@"message"] ?: @"请求失败"; + NSError *bizError = [NSError errorWithDomain:@"AiVM" + code:code + userInfo:@{NSLocalizedDescriptionKey: message}]; + if (completion) { + completion(nil, bizError); + } + return; + } + + NSArray *dataArray = json[@"data"]; + if (![dataArray isKindOfClass:[NSArray class]]) { + dataArray = @[]; + } + + NSArray *list = [KBLikedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray]; + if (completion) { + completion(list, nil); + } + }]; +} + +- (void)fetchChattedCompanionsWithCompletion:(void (^)(NSArray * _Nullable, NSError * _Nullable))completion { + NSLog(@"[AiVM] /ai-companion/chatted request"); + [[KBNetworkManager shared] + GET:@"/ai-companion/chatted" + parameters:nil + headers:nil +autoShowBusinessError:NO + completion:^(NSDictionary *_Nullable json, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + NSLog(@"[AiVM] /ai-companion/chatted failed: %@", error.localizedDescription ?: @""); + if (completion) { + completion(nil, error); + } + return; + } + + NSLog(@"[AiVM] /ai-companion/chatted response: %@", json); + + NSInteger code = [json[@"code"] integerValue]; + if (code != 0) { + NSString *message = json[@"message"] ?: @"请求失败"; + NSError *bizError = [NSError errorWithDomain:@"AiVM" + code:code + userInfo:@{NSLocalizedDescriptionKey: message}]; + if (completion) { + completion(nil, bizError); + } + return; + } + + NSArray *dataArray = json[@"data"]; + if (![dataArray isKindOfClass:[NSArray class]]) { + dataArray = @[]; + } + + NSArray *list = [KBChattedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray]; + if (completion) { + completion(list, nil); + } + }]; +} + @end