From b9663037f59cab9071792db0d1e9aa8f5e7ff3a1 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Tue, 3 Feb 2026 16:54:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BE=A7=E8=BE=B9=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- keyBoard.xcodeproj/project.pbxproj | 6 + .../AI/ai_more_icon.imageset/Contents.json | 22 + .../ai_more_icon.imageset/ai_more_icon@2x.png | Bin 0 -> 1545 bytes .../ai_more_icon.imageset/ai_more_icon@3x.png | Bin 0 -> 3121 bytes .../AI/ai_role_sel.imageset/Contents.json | 22 + .../ai_role_sel.imageset/ai_role_sel@2x.png | Bin 0 -> 1479 bytes .../ai_role_sel.imageset/ai_role_sel@3x.png | Bin 0 -> 2627 bytes .../AI/ai_search_icon.imageset/Contents.json | 22 + .../ai_search_icon@2x.png | Bin 0 -> 923 bytes .../ai_search_icon@3x.png | Bin 0 -> 1641 bytes .../right_arrow_icon.imageset/Contents.json | 22 + .../right_arrow_icon@2x.png | Bin 0 -> 474 bytes .../right_arrow_icon@3x.png | Bin 0 -> 858 bytes .../AiTalk/V/PopView/KBAIPersonaSidebarView.h | 51 +++ .../AiTalk/V/PopView/KBAIPersonaSidebarView.m | 417 ++++++++++++++++++ keyBoard/Class/AiTalk/VC/KBAIHomeVC.m | 168 ++++++- 16 files changed, 728 insertions(+), 2 deletions(-) create mode 100644 keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@2x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@3x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_role_sel.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/AI/ai_role_sel.imageset/ai_role_sel@2x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_role_sel.imageset/ai_role_sel@3x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_search_icon.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/AI/ai_search_icon.imageset/ai_search_icon@2x.png create mode 100644 keyBoard/Assets.xcassets/AI/ai_search_icon.imageset/ai_search_icon@3x.png create mode 100644 keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/Contents.json create mode 100644 keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@2x.png create mode 100644 keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@3x.png create mode 100644 keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.h create mode 100644 keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.m diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 6e1768c..f2745d5 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -230,6 +230,7 @@ 04E038EF2F21F0EC002CA5A0 /* AiVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E038EE2F21F0EC002CA5A0 /* AiVM.m */; }; 04E0394B2F236E75002CA5A0 /* KBChatUserMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0394A2F236E75002CA5A0 /* KBChatUserMessageCell.m */; }; 0F2A10032F3C0001002CA5A0 /* KBChatMessageActionPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F2A10022F3C0001002CA5A0 /* KBChatMessageActionPopView.m */; }; + 0F2A10132F3C0002002CA5A0 /* KBAIPersonaSidebarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */; }; 04E0394C2F236E75002CA5A0 /* KBChatTimeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E039482F236E75002CA5A0 /* KBChatTimeCell.m */; }; 04E0394D2F236E75002CA5A0 /* KBChatAssistantMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E039432F236E75002CA5A0 /* KBChatAssistantMessageCell.m */; }; 04E0394E2F236E75002CA5A0 /* KBChatTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E039452F236E75002CA5A0 /* KBChatTableView.m */; }; @@ -563,6 +564,8 @@ 048FFD1C2F277486005D62AE /* KBChatHistoryPageModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatHistoryPageModel.m; sourceTree = ""; }; 048FFD222F28A836005D62AE /* KBChatLimitPopView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatLimitPopView.h; sourceTree = ""; }; 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatLimitPopView.m; sourceTree = ""; }; + 0F2A10112F3C0002002CA5A0 /* KBAIPersonaSidebarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIPersonaSidebarView.h; sourceTree = ""; }; + 0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIPersonaSidebarView.m; sourceTree = ""; }; 048FFD252F28C6CF005D62AE /* KBImagePositionButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBImagePositionButton.h; sourceTree = ""; }; 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 = ""; }; @@ -1449,6 +1452,8 @@ children = ( 048FFD222F28A836005D62AE /* KBChatLimitPopView.h */, 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */, + 0F2A10112F3C0002002CA5A0 /* KBAIPersonaSidebarView.h */, + 0F2A10122F3C0002002CA5A0 /* KBAIPersonaSidebarView.m */, ); path = PopView; sourceTree = ""; @@ -2419,6 +2424,7 @@ 046086D92F1A093400757C95 /* KBAICommentHeaderView.m in Sources */, 04E0394B2F236E75002CA5A0 /* KBChatUserMessageCell.m in Sources */, 0F2A10032F3C0001002CA5A0 /* KBChatMessageActionPopView.m in Sources */, + 0F2A10132F3C0002002CA5A0 /* KBAIPersonaSidebarView.m in Sources */, 04E0394C2F236E75002CA5A0 /* KBChatTimeCell.m in Sources */, 04E0394D2F236E75002CA5A0 /* KBChatAssistantMessageCell.m in Sources */, 04E0394E2F236E75002CA5A0 /* KBChatTableView.m in Sources */, diff --git a/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/Contents.json b/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/Contents.json new file mode 100644 index 0000000..2297ee2 --- /dev/null +++ b/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ai_more_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ai_more_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@2x.png b/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7418f5fb97522722f60db1e3083a522300c021e6 GIT binary patch literal 1545 zcmV+k2KM=hP)rzl>C1Au#KMdW)Jpa&)_{`1U{l&PRpN`kIC<~u?KsxCwm9&7b1|)#0%>Fik#1n zhCMa*WN$dY#cOk4fi!m^QaD*$IbDYWqJ})Hi za}A9!xWYL{wn!k~HJnsA@Q4TC4EF(=wm|L{o*O_kYXNsOq^xa$Pb$NET7%m*H#a}( z_xqpuI(S#dZnyh#uh;u@d3pIY7p@`)cQmB1tUzvRA5SwClZlCmnchw>hboJK&w+OX zh+0@!IMZ+S56{ibEwbKC#SCabOH!K^NP_#mB;j-EjCES=cKiLTk9Wog*5tsI^+ODz z$Zwq6ILpYIIP(Zc16q<&BakEZu_UsG7epY2Emu}nx-&B~pD?l}PM&#$qXkVZBk*B0 z;qgR+!-klGJR)d`sQLN%@0l~QW=6+ z=7FAQ;uXXv1B(-Q(SedAtc#7Cot-^RY<+$GEMhEpvLsF(oJ7tg!LSRO4k7Ni>N@gZ za~&SpW;RZ*&I+;V>FHCvnpv!w6DJRDb#?VC6Au{CbR2>F@Dos1AG)}>_>)9->7-XD z_qBjH;^e{cYBG=T69_yOQCPbK(vYVSVi3igku`Jj;Fw4HF$5lsEUsM!c|_0%QEO{! zA2PBgPM&!UKZ-zZZ=!l|0NA%`+r91F?HjnV4G@7C@>3_L`1P#CnmDnVeguKzF-^9% zw$?ZR?Ao#zcpGchh(T1l)xNp3wDdV^ldCz7BamC0;69U+lOJ;%=Ih{Hn;@#w>3qNe z_+x+mTx;-VALcHsoda)b1IHZ%ZU+`6a2|h(4v(@J_@q!5 z=4Cjg2MiqefuR3l8-dsT@qfF+qF7px znfT%|C1#4II|w9jj>`e_M3WJCLE>)74~n4$P3dnkx25bq`9U$XpeYrIuzz$3x^3eC zuy3n;AD8OXil@uafR+Sf-l8mS>hiDL%Xs`LIsnRI;7w!ZWjKQ|BRKG8?J*(_EeSL$ zkicKMc-^}3_+#Iuxz^y-J2B=itepd&6jop0{&yB;3*@`ww=AGxbGV}+rELo&abA~o zdD9Hea3A1ZBoM%_x?Gkw#o!9(9N#j59I-zL&3jtINE;mC3g;ZrGJyao{7#b_@=-Oo z!4a;799;q_xT?#y^3n#%?U%s`Zg4E+P$iH;8hoP(?{f_!VQ_&H+?<4b1k!-Nt*X=*d&2=PUcEyEnudJW@XuM?^>E*3;`eG~ zk3ZF%J=t4Re~S@FckUMc&}3PTP##Y-9jeij-t57DPGHaA4PphFjku{r=$8uoEZ>kv z5oxJVMlC()MNfLOhf;A?SD@LI&x9fNg@T-Lzia$o&W~Drejwl18fQ+792l_FP>VQv v(Npkxb{7Bu|Nm*6F5LhC00v1!K~w_(i@Bgn3B0>H00000NkvXXu0mjfVJp~v literal 0 HcmV?d00001 diff --git a/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@3x.png b/keyBoard/Assets.xcassets/AI/ai_more_icon.imageset/ai_more_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ee5c6462b06c775a41696255a107cb9020a952 GIT binary patch literal 3121 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 literal 0 HcmV?d00001 diff --git a/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/Contents.json b/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/Contents.json new file mode 100644 index 0000000..53f4b80 --- /dev/null +++ b/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "right_arrow_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "right_arrow_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@2x.png b/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..14f46711d474e998189cec01d9a3d33caf845276 GIT binary patch literal 474 zcmV<00VV#4P)hnx39R=ZZOK}vw+S+tOePz;rtb6*s(5xlfh39>6dk- zK8-0us!?&pqCPQj?tzfk;p6J?la)QM!3a~s7)^*ucG_5rfF-@ zXtXN9(%TBH5pK|N2M7JY>c@i`64dDqA)C!EV1y?W{OEeEGSvt0GvXd&c$kDO>^w$r zqw5}kOTl2U(t~Uz7=V84O8_ro-b3tvwQm3b0RR8CfXsRT000I_L_t&o0O8w~rP$i- Q82|tP07*qoM6N<$g1ueI*8l(j literal 0 HcmV?d00001 diff --git a/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@3x.png b/keyBoard/Assets.xcassets/AI/right_arrow_icon.imageset/right_arrow_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8aef3276cd147b498d97ba85bc1cd931ab28e147 GIT binary patch literal 858 zcmV-g1Eu_lP)LOBzAOaz5Fj}(@c zBuNSl4i4ITdwXf>2rrst*;bC_>>S5=&1UnvrlzK=!C;U-tQVchWNyof{83RA4YDhW z^1@PMd2nbWzu%wIRPCCHHche&y9vVM9K+p2=r~*38nYba4JsueuIY#nIY1@b- zpkunz>6}NM&a1lm21Hv>Ez|?zekmrMAbERfhmnyWisIkZ->Xg#-DOP74@3q=K2GCa zg6;}7WQ<%R?rB7R?C$Q)C6mcv(78ag2vJZoG*Dk(-?@84N*WO}>@0gco;i!z(gG^d z)f{3iX0G3AwFbw=#%hpP=<2>kNGiPB+S)SNY_>g@%iRZ28X|OlUDs!DGA?36rYs}k z=Jk5lY&Kgz=Is)w79omZn6uDuV`F1i*+#@HolfULwIQqW=6@;*&m8K=mu-Yeuf>9{ z%F4<)O#T@{1byOJ{zf8^cv6;8EEcQATs_rvtq&7#CNv260oEFjnb%_C6|n{f+#L+A8Ti;KUf zwY>HWH8@8opr5iP-&|Q)d03oO&NI!>%oh+OK{ADNbRjX7m<$90TjeY+&iMxb0RR6b kat?g}000I_L_t&o0Jv_7DA+VJ3IG5A07*qoM6N<$g8k2t`2YX_ literal 0 HcmV?d00001 diff --git a/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.h b/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.h new file mode 100644 index 0000000..05c3ff3 --- /dev/null +++ b/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.h @@ -0,0 +1,51 @@ +// +// KBAIPersonaSidebarView.h +// keyBoard +// +// Created by Codex on 2026/2/3. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class KBPersonaModel; +@class KBAIPersonaSidebarView; + +@protocol KBAIPersonaSidebarViewDelegate +@optional +/// 侧边栏请求人设数据 +/// page 从 1 开始 +- (void)personaSidebarView:(KBAIPersonaSidebarView *)view + requestPersonasAtPage:(NSInteger)page; +/// 选择某个人设 +- (void)personaSidebarView:(KBAIPersonaSidebarView *)view + didSelectPersona:(KBPersonaModel *)persona; +@end + +/// 人设侧边栏(LSTPopView 内容视图) +@interface KBAIPersonaSidebarView : UIView + +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) NSInteger selectedPersonaId; +@property (nonatomic, assign, readonly) NSInteger currentPage; + +/// 更新人设列表 +/// reset=YES 表示重置并替换数据;reset=NO 表示追加 +/// currentPage 为当前已加载页数(从 1 开始) +- (void)updatePersonas:(NSArray *)personas + reset:(BOOL)reset + hasMore:(BOOL)hasMore + currentPage:(NSInteger)currentPage; +/// 请求数据(若为空) +- (void)requestPersonasIfNeeded; +/// 更新选中态 +- (void)updateSelectedPersonaId:(NSInteger)personaId; +/// 结束加载更多 +- (void)endLoadingMore; +/// 重置加载更多状态 +- (void)resetLoadMore; + +@end + +NS_ASSUME_NONNULL_END diff --git a/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.m b/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.m new file mode 100644 index 0000000..5e29ae9 --- /dev/null +++ b/keyBoard/Class/AiTalk/V/PopView/KBAIPersonaSidebarView.m @@ -0,0 +1,417 @@ +// +// KBAIPersonaSidebarView.m +// keyBoard +// +// Created by Codex on 2026/2/3. +// + +#import "KBAIPersonaSidebarView.h" +#import "KBPersonaModel.h" +#import +#import +#import + +#pragma mark - Cell + +@interface KBAIPersonaSidebarCell : UITableViewCell + +@property (nonatomic, strong) UIImageView *avatarImageView; +@property (nonatomic, strong) UILabel *nameLabel; +@property (nonatomic, strong) UILabel *descLabel; +@property (nonatomic, strong) UIImageView *checkImageView; +@property (nonatomic, strong) UIImageView *arrowImageView; +@property (nonatomic, strong) UIView *lineView; + +- (void)configureWithPersona:(KBPersonaModel *)persona selected:(BOOL)selected; + +@end + +@implementation KBAIPersonaSidebarCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style + reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + self.backgroundColor = [UIColor clearColor]; + self.selectionStyle = UITableViewCellSelectionStyleNone; + [self setupUI]; + } + return self; +} + +- (void)setupUI { + [self.contentView addSubview:self.avatarImageView]; + [self.contentView addSubview:self.nameLabel]; + [self.contentView addSubview:self.descLabel]; + [self.contentView addSubview:self.checkImageView]; + [self.contentView addSubview:self.arrowImageView]; + [self.contentView addSubview:self.lineView]; + + [self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.contentView).offset(16); + make.centerY.equalTo(self.contentView); + make.width.height.mas_equalTo(44); + }]; + + [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.contentView).offset(-60); + }]; + + [self.descLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.nameLabel); + make.top.equalTo(self.nameLabel.mas_bottom).offset(4); + make.right.lessThanOrEqualTo(self.contentView).offset(-60); + }]; + + [self.checkImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.contentView); + make.right.equalTo(self.contentView).offset(-16); + make.width.height.mas_equalTo(22); + }]; + + [self.arrowImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.contentView); + make.right.equalTo(self.contentView).offset(-18); + make.width.height.mas_equalTo(16); + }]; + + [self.lineView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.nameLabel); + make.right.equalTo(self.contentView).offset(-16); + make.bottom.equalTo(self.contentView); + make.height.mas_equalTo(0.5); + }]; +} + +- (void)configureWithPersona:(KBPersonaModel *)persona selected:(BOOL)selected { + if (!persona) { + return; + } + [self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:persona.avatarUrl] + placeholderImage:[UIImage imageNamed:@"placeholder_avatar"]]; + self.nameLabel.text = persona.name ?: @""; + NSString *desc = persona.shortDesc.length > 0 ? persona.shortDesc : persona.introText; + self.descLabel.text = desc ?: @""; + self.checkImageView.hidden = !selected; + self.arrowImageView.hidden = selected; +} + +#pragma mark - Lazy + +- (UIImageView *)avatarImageView { + if (!_avatarImageView) { + _avatarImageView = [[UIImageView alloc] init]; + _avatarImageView.contentMode = UIViewContentModeScaleAspectFill; + _avatarImageView.layer.cornerRadius = 22; + _avatarImageView.clipsToBounds = YES; + _avatarImageView.layer.borderWidth = 1; + _avatarImageView.layer.borderColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6].CGColor; + } + return _avatarImageView; +} + +- (UILabel *)nameLabel { + if (!_nameLabel) { + _nameLabel = [[UILabel alloc] init]; + _nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium]; + _nameLabel.textColor = [UIColor whiteColor]; + } + return _nameLabel; +} + +- (UILabel *)descLabel { + if (!_descLabel) { + _descLabel = [[UILabel alloc] init]; + _descLabel.font = [UIFont systemFontOfSize:12]; + _descLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7]; + _descLabel.lineBreakMode = NSLineBreakByTruncatingTail; + } + return _descLabel; +} + +- (UIImageView *)checkImageView { + if (!_checkImageView) { + _checkImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"ai_role_sel"]]; + } + return _checkImageView; +} + +- (UIImageView *)arrowImageView { + if (!_arrowImageView) { + _arrowImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"right_arrow_icon"]]; + } + return _arrowImageView; +} + +- (UIView *)lineView { + if (!_lineView) { + _lineView = [[UIView alloc] init]; + _lineView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.12]; + } + return _lineView; +} + +@end + +#pragma mark - View + +@interface KBAIPersonaSidebarView () + +@property (nonatomic, strong) UIVisualEffectView *blurView; +@property (nonatomic, strong) UIView *contentView; +@property (nonatomic, strong) UIView *searchContainer; +@property (nonatomic, strong) UIImageView *searchIconView; +@property (nonatomic, strong) UITextField *searchField; +@property (nonatomic, strong) UITableView *tableView; + +@property (nonatomic, strong) NSArray *personas; +@property (nonatomic, strong) NSArray *displayPersonas; +@property (nonatomic, assign) NSInteger currentPage; +@property (nonatomic, assign) BOOL hasMore; + +@end + +@implementation KBAIPersonaSidebarView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self setupUI]; + } + return self; +} + +- (void)setupUI { + self.backgroundColor = [UIColor clearColor]; + + [self addSubview:self.blurView]; + [self addSubview:self.contentView]; + + [self.contentView addSubview:self.searchContainer]; + [self.searchContainer addSubview:self.searchIconView]; + [self.searchContainer addSubview:self.searchField]; + [self.contentView addSubview:self.tableView]; + + [self.blurView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self); + }]; + + [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.equalTo(self); + }]; + + [self.searchContainer mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.contentView).offset(14); + make.left.equalTo(self.contentView).offset(16); + make.right.equalTo(self.contentView).offset(-16); + make.height.mas_equalTo(36); + }]; + + [self.searchIconView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.searchContainer).offset(12); + make.centerY.equalTo(self.searchContainer); + make.width.height.mas_equalTo(16); + }]; + + [self.searchField mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self.searchIconView.mas_right).offset(8); + make.right.equalTo(self.searchContainer).offset(-12); + make.centerY.equalTo(self.searchContainer); + make.height.mas_equalTo(28); + }]; + + [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.searchContainer.mas_bottom).offset(10); + make.left.right.bottom.equalTo(self.contentView); + }]; + + __weak typeof(self) weakSelf = self; + MJRefreshAutoNormalFooter *footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + if (!strongSelf.hasMore) { + [strongSelf.tableView.mj_footer endRefreshingWithNoMoreData]; + return; + } + strongSelf.currentPage += 1; + if ([strongSelf.delegate respondsToSelector:@selector(personaSidebarView:requestPersonasAtPage:)]) { + [strongSelf.delegate personaSidebarView:strongSelf requestPersonasAtPage:strongSelf.currentPage]; + } + }]; + footer.stateLabel.hidden = YES; + footer.backgroundColor = [UIColor clearColor]; + footer.automaticallyHidden = YES; + self.tableView.mj_footer = footer; +} + +#pragma mark - Public + +- (void)requestPersonasIfNeeded { + if (self.personas.count == 0) { + self.currentPage = 1; + self.hasMore = YES; + if ([self.delegate respondsToSelector:@selector(personaSidebarView:requestPersonasAtPage:)]) { + [self.delegate personaSidebarView:self requestPersonasAtPage:self.currentPage]; + } + return; + } + [self applyFilterAndReload]; +} + +- (void)updatePersonas:(NSArray *)personas + reset:(BOOL)reset + hasMore:(BOOL)hasMore + currentPage:(NSInteger)currentPage { + self.hasMore = hasMore; + NSInteger safePage = MAX(1, currentPage); + // HomeVC 传入的是全量列表,这里直接替换,避免重复 + self.personas = personas ?: @[]; + self.currentPage = safePage; + [self applyFilterAndReload]; + [self endLoadingMore]; +} + +- (void)updateSelectedPersonaId:(NSInteger)personaId { + self.selectedPersonaId = personaId; + [self.tableView reloadData]; +} + +- (void)endLoadingMore { + if ([self.tableView.mj_footer isRefreshing]) { + if (self.hasMore) { + [self.tableView.mj_footer endRefreshing]; + } else { + [self.tableView.mj_footer endRefreshingWithNoMoreData]; + } + } +} + +- (void)resetLoadMore { + [self.tableView.mj_footer resetNoMoreData]; +} + +#pragma mark - Search + +- (void)searchFieldChanged:(UITextField *)textField { + [self applyFilterAndReload]; +} + +- (void)applyFilterAndReload { + NSString *keyword = [self.searchField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (keyword.length == 0) { + self.displayPersonas = self.personas; + } else { + NSMutableArray *result = [NSMutableArray array]; + for (KBPersonaModel *persona in self.personas) { + NSString *name = persona.name ?: @""; + NSString *desc = persona.shortDesc ?: persona.introText ?: @""; + if ([name localizedCaseInsensitiveContainsString:keyword] || + [desc localizedCaseInsensitiveContainsString:keyword]) { + [result addObject:persona]; + } + } + self.displayPersonas = result; + } + [self.tableView reloadData]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.displayPersonas.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + KBAIPersonaSidebarCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KBAIPersonaSidebarCell" + forIndexPath:indexPath]; + KBPersonaModel *persona = self.displayPersonas[indexPath.row]; + BOOL selected = (persona.personaId == self.selectedPersonaId); + [cell configureWithPersona:persona selected:selected]; + return cell; +} + +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return 72.0; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.row >= self.displayPersonas.count) { + return; + } + KBPersonaModel *persona = self.displayPersonas[indexPath.row]; + self.selectedPersonaId = persona.personaId; + [self.tableView reloadData]; + if ([self.delegate respondsToSelector:@selector(personaSidebarView:didSelectPersona:)]) { + [self.delegate personaSidebarView:self didSelectPersona:persona]; + } +} + +#pragma mark - Lazy + +- (UIVisualEffectView *)blurView { + if (!_blurView) { + UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; + _blurView = [[UIVisualEffectView alloc] initWithEffect:effect]; + } + return _blurView; +} + +- (UIView *)contentView { + if (!_contentView) { + _contentView = [[UIView alloc] init]; + _contentView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.25]; + } + return _contentView; +} + +- (UIView *)searchContainer { + if (!_searchContainer) { + _searchContainer = [[UIView alloc] init]; + _searchContainer.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.12]; + _searchContainer.layer.cornerRadius = 18; + _searchContainer.clipsToBounds = YES; + } + return _searchContainer; +} + +- (UIImageView *)searchIconView { + if (!_searchIconView) { + _searchIconView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"magnifyingglass"]]; + _searchIconView.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8]; + } + return _searchIconView; +} + +- (UITextField *)searchField { + if (!_searchField) { + _searchField = [[UITextField alloc] init]; + _searchField.placeholder = KBLocalized(@"Search Role"); + _searchField.textColor = [UIColor whiteColor]; + _searchField.font = [UIFont systemFontOfSize:14]; + _searchField.clearButtonMode = UITextFieldViewModeWhileEditing; + [_searchField addTarget:self action:@selector(searchFieldChanged:) forControlEvents:UIControlEventEditingChanged]; + } + return _searchField; +} + +- (UITableView *)tableView { + if (!_tableView) { + _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.backgroundColor = [UIColor clearColor]; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.showsVerticalScrollIndicator = NO; + _tableView.delegate = self; + _tableView.dataSource = self; + [_tableView registerClass:[KBAIPersonaSidebarCell class] forCellReuseIdentifier:@"KBAIPersonaSidebarCell"]; + } + return _tableView; +} + +@end diff --git a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m index 0b3ca82..bf44884 100644 --- a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m +++ b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m @@ -19,10 +19,11 @@ #import "LSTPopView.h" #import "KBAIMessageVC.h" #import "KBAICommentInputView.h" +#import "KBAIPersonaSidebarView.h" #import #import -@interface KBAIHomeVC () +@interface KBAIHomeVC () /// 人设列表容器 @property (nonatomic, strong) UICollectionView *collectionView; @@ -90,11 +91,22 @@ /// 右上角消息按钮 @property (nonatomic, strong) UIButton *messageButton; +/// 左上角人设列表按钮 +@property (nonatomic, strong) UIButton *sidebarButton; + +/// 侧边栏 PopView +@property (nonatomic, weak) LSTPopView *sidebarPopView; +@property (nonatomic, strong) KBAIPersonaSidebarView *sidebarView; + +/// 侧边栏选中的人设 ID +@property (nonatomic, assign) NSInteger selectedPersonaId; @end @implementation KBAIHomeVC +static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; + #pragma mark - Keyboard Gate /// 查找当前 view 树里的 firstResponder @@ -194,6 +206,14 @@ make.right.equalTo(self.view).offset(-16); make.width.height.mas_equalTo(32); }]; + + // 左上角人设列表按钮 + [self.view addSubview:self.sidebarButton]; + [self.sidebarButton mas_makeConstraints:^(MASConstraintMaker *make) { + make.top.equalTo(self.view).offset(KB_STATUSBAR_HEIGHT + 10); + make.left.equalTo(self.view).offset(16); + make.width.height.mas_equalTo(32); + }]; // 底部毛玻璃背景 [self.view addSubview:self.bottomBackgroundView]; @@ -262,6 +282,7 @@ } self.isLoading = YES; + NSInteger oldCount = self.personas.count; __weak typeof(self) weakSelf = self; [self.aiVM fetchPersonasWithPageNum:self.currentPage @@ -283,9 +304,31 @@ weakSelf.hasMore = pageModel.hasMore; dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf.collectionView reloadData]; if (weakSelf.currentPage == 1) { + [weakSelf.collectionView reloadData]; [weakSelf preloadDataForIndexes:@[@0, @1, @2]]; + } else if (pageModel.records.count > 0) { + NSInteger newCount = weakSelf.personas.count; + NSMutableArray *indexPaths = [NSMutableArray array]; + for (NSInteger i = oldCount; i < newCount; i++) { + [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:0]]; + } + [UIView performWithoutAnimation:^{ + [weakSelf.collectionView performBatchUpdates:^{ + [weakSelf.collectionView insertItemsAtIndexPaths:indexPaths]; + } completion:nil]; + }]; + } + if (weakSelf.selectedPersonaId <= 0 && weakSelf.personas.count > 0) { + NSInteger index = MIN(MAX(weakSelf.currentIndex, 0), weakSelf.personas.count - 1); + [weakSelf storeSelectedPersonaId:weakSelf.personas[index].personaId]; + } + if (weakSelf.sidebarView) { + [weakSelf.sidebarView updatePersonas:weakSelf.personas + reset:(weakSelf.currentPage == 1) + hasMore:weakSelf.hasMore + currentPage:weakSelf.currentPage]; + [weakSelf.sidebarView updateSelectedPersonaId:[weakSelf storedSelectedPersonaId]]; } }); @@ -644,8 +687,44 @@ return nil; } +- (NSInteger)indexOfPersonaId:(NSInteger)personaId { + if (personaId <= 0) { + return NSNotFound; + } + for (NSInteger i = 0; i < self.personas.count; i++) { + KBPersonaModel *persona = self.personas[i]; + if (persona.personaId == personaId) { + return i; + } + } + return NSNotFound; +} + #pragma mark - Private +- (NSInteger)storedSelectedPersonaId { + NSInteger savedId = [[NSUserDefaults standardUserDefaults] integerForKey:KBAISelectedPersonaIdKey]; + if (savedId > 0) { + return savedId; + } + if (self.currentIndex >= 0 && self.currentIndex < self.personas.count) { + return self.personas[self.currentIndex].personaId; + } + return 0; +} + +- (void)storeSelectedPersonaId:(NSInteger)personaId { + if (personaId <= 0) { + return; + } + self.selectedPersonaId = personaId; + [[NSUserDefaults standardUserDefaults] setInteger:personaId forKey:KBAISelectedPersonaIdKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + if (self.sidebarView) { + [self.sidebarView updateSelectedPersonaId:personaId]; + } +} + - (void)updateCollectionViewScrollState { BOOL shouldEnable = !self.isWaitingForAIResponse && !self.isVoiceRecording @@ -766,6 +845,46 @@ [KB_CURRENT_NAV pushViewController:vc animated:true]; } +#pragma mark - KBAIPersonaSidebarViewDelegate + +- (void)personaSidebarView:(KBAIPersonaSidebarView *)view + requestPersonasAtPage:(NSInteger)page { + if (self.isLoading) { + [view endLoadingMore]; + return; + } + self.currentPage = MAX(1, page); + if (self.currentPage == 1) { + [self.personas removeAllObjects]; + [view resetLoadMore]; + } + [self loadPersonas]; +} + +- (void)personaSidebarView:(KBAIPersonaSidebarView *)view + didSelectPersona:(KBPersonaModel *)persona { + if (!persona) { + return; + } + + [self storeSelectedPersonaId:persona.personaId]; + + NSInteger index = [self indexOfPersonaId:persona.personaId]; + if (index != NSNotFound) { + self.currentIndex = index; + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; + [self.collectionView scrollToItemAtIndexPath:indexPath + atScrollPosition:UICollectionViewScrollPositionCenteredVertically + animated:NO]; + [self preloadAdjacentCellsForIndex:index]; + [self saveSelectedPersonaToAppGroup:persona]; + } + + if (self.sidebarPopView) { + [self.sidebarPopView dismiss]; + } +} + - (UIView *)bottomBackgroundView { if (!_bottomBackgroundView) { _bottomBackgroundView = [[UIView alloc] init]; @@ -807,6 +926,16 @@ return _messageButton; } +- (UIButton *)sidebarButton { + if (!_sidebarButton) { + _sidebarButton = [UIButton buttonWithType:UIButtonTypeCustom]; + UIImage *icon = [UIImage imageNamed:@"ai_more_icon"]; + [_sidebarButton setImage:icon forState:UIControlStateNormal]; + [_sidebarButton addTarget:self action:@selector(sidebarButtonTapped) forControlEvents:UIControlEventTouchUpInside]; + } + return _sidebarButton; +} + #pragma mark - Actions - (void)messageButtonTapped { @@ -814,6 +943,41 @@ [self.navigationController pushViewController:vc animated:YES]; } +- (void)sidebarButtonTapped { + [self showPersonaSidebar]; +} + +- (void)showPersonaSidebar { + if (!self.sidebarView) { + CGFloat width = KB_SCREEN_WIDTH * 0.7; + CGFloat height = KB_SCREEN_HEIGHT; + self.sidebarView = [[KBAIPersonaSidebarView alloc] initWithFrame:CGRectMake(0, 0, width, height)]; + self.sidebarView.delegate = self; + } + + self.sidebarView.selectedPersonaId = [self storedSelectedPersonaId]; + [self.sidebarView updatePersonas:self.personas + reset:YES + hasMore:self.hasMore + currentPage:self.currentPage]; + [self.sidebarView requestPersonasIfNeeded]; + + if (self.sidebarPopView) { + [self.sidebarPopView dismiss]; + } + + LSTPopView *popView = [LSTPopView initWithCustomView:self.sidebarView + parentView:nil + popStyle:LSTPopStyleSmoothFromLeft + dismissStyle:LSTDismissStyleSmoothToLeft]; + popView.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.35]; + popView.hemStyle = LSTHemStyleLeft; + popView.isClickBgDismiss = YES; + popView.isAvoidKeyboard = NO; + self.sidebarPopView = popView; + [popView pop]; +} + /// 文本输入发送 - 直接调用 handleTranscribedText - (void)handleCommentInputSend:(NSString *)text { NSString *trimmedText = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];