From 26096abbcc504863afd0a0d36df68898581be39c Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Thu, 29 Jan 2026 16:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=AA=E4=BA=BA=E4=B8=BB?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- keyBoard.xcodeproj/project.pbxproj | 6 ++ .../Class/AiTalk/V/Chat/KBPersonaChatCell.m | 20 ++++- keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h | 10 +-- keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m | 83 +++++++++++++++---- keyBoard/Class/AiTalk/VM/AiVM.h | 8 ++ keyBoard/Class/AiTalk/VM/AiVM.m | 55 ++++++++++++ 6 files changed, 155 insertions(+), 27 deletions(-) diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 689f476..54d24fa 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -156,6 +156,7 @@ 048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */; }; 048FFD422F29F700005D62AE /* KBChatSessionResetModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */; }; 048FFD472F2B45D4005D62AE /* AIPersonInfoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD462F2B45D4005D62AE /* AIPersonInfoVC.m */; }; + 048FFD4A2F2B4AE4005D62AE /* KBAICompanionDetailModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD492F2B4AE4005D62AE /* KBAICompanionDetailModel.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 */; }; @@ -580,6 +581,8 @@ 048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatSessionResetModel.m; sourceTree = ""; }; 048FFD452F2B45D4005D62AE /* AIPersonInfoVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AIPersonInfoVC.h; sourceTree = ""; }; 048FFD462F2B45D4005D62AE /* AIPersonInfoVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AIPersonInfoVC.m; sourceTree = ""; }; + 048FFD482F2B4AE4005D62AE /* KBAICompanionDetailModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAICompanionDetailModel.h; sourceTree = ""; }; + 048FFD492F2B4AE4005D62AE /* KBAICompanionDetailModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAICompanionDetailModel.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 = ""; }; @@ -1049,6 +1052,8 @@ 048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */, 048FFD372F2A24C5005D62AE /* KBAIChatMessageCacheManager.h */, 048FFD382F2A24C5005D62AE /* KBAIChatMessageCacheManager.m */, + 048FFD482F2B4AE4005D62AE /* KBAICompanionDetailModel.h */, + 048FFD492F2B4AE4005D62AE /* KBAICompanionDetailModel.m */, ); path = M; sourceTree = ""; @@ -2409,6 +2414,7 @@ 0450AC102EF11E4400B6AF06 /* StoreKitDelegate.swift in Sources */, 0450AC112EF11E4400B6AF06 /* StoreKitService.swift in Sources */, 0450AC122EF11E4400B6AF06 /* TransactionConverter.swift in Sources */, + 048FFD4A2F2B4AE4005D62AE /* KBAICompanionDetailModel.m in Sources */, 0450AC132EF11E4400B6AF06 /* SubscriptionConverter.swift in Sources */, 0450AC142EF11E4400B6AF06 /* ProductConverter.swift in Sources */, 0450AC152EF11E4400B6AF06 /* TransactionHistory.swift in Sources */, diff --git a/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m b/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m index 410e762..87efc61 100644 --- a/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m +++ b/keyBoard/Class/AiTalk/V/Chat/KBPersonaChatCell.m @@ -15,7 +15,7 @@ #import #import #import - +#import "AIPersonInfoVC.h" /// 聊天会话被重置的通知 static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidResetNotification"; @@ -549,6 +549,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe _avatarImageView.layer.borderWidth = 3; _avatarImageView.layer.borderColor = [UIColor whiteColor].CGColor; _avatarImageView.clipsToBounds = YES; + _avatarImageView.userInteractionEnabled = YES; + + // 添加点击手势 + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(avatarTapped)]; + [_avatarImageView addGestureRecognizer:tap]; } return _avatarImageView; } @@ -628,6 +633,19 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe #pragma mark - Button Actions +- (void)avatarTapped { + NSLog(@"[KBPersonaChatCell] 头像点击,跳转到人设详情页"); + + if (self.persona.personaId <= 0) { + NSLog(@"[KBPersonaChatCell] personaId 无效,取消跳转"); + return; + } + + AIPersonInfoVC *vc = [[AIPersonInfoVC alloc] init]; + vc.companionId = self.persona.personaId; + [KB_CURRENT_NAV pushViewController:vc animated:YES]; +} + - (void)commentButtonTapped:(KBImagePositionButton *)sender { NSLog(@"[KBPersonaChatCell] 评论按钮点击"); diff --git a/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h b/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h index a23ac87..6b8ca25 100644 --- a/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h +++ b/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h @@ -11,14 +11,8 @@ NS_ASSUME_NONNULL_BEGIN @interface AIPersonInfoVC : BaseViewController -/// 背景图 URL -@property (nonatomic, copy) NSString *backgroundImageURL; -/// 人设名称 -@property (nonatomic, copy) NSString *personaName; -/// 人设介绍 -@property (nonatomic, copy) NSString *personaIntroduction; -/// 人设 ID(用于举报等) -@property (nonatomic, assign) NSInteger personaId; +/// AI 角色 ID(必传,用于请求详情) +@property (nonatomic, assign) NSInteger companionId; @end diff --git a/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m b/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m index 6ff6aae..3e3d2d4 100644 --- a/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m +++ b/keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m @@ -6,7 +6,9 @@ // #import "AIPersonInfoVC.h" -#import "AIReportVC.h" +//#import "AIReportVC.h" +#import "AiVM.h" +#import "KBAICompanionDetailModel.h" #import @interface AIPersonInfoVC () @@ -29,6 +31,10 @@ @property (nonatomic, strong) UILabel *introLabel; /// 底部 Go Chatting 按钮 @property (nonatomic, strong) UIButton *goChatButton; +/// AiVM 实例 +@property (nonatomic, strong) AiVM *aiVM; +/// 详情数据 +@property (nonatomic, strong) KBAICompanionDetailModel *detailModel; @end @@ -41,12 +47,13 @@ self.kb_navView.hidden = YES; self.view.backgroundColor = [UIColor blackColor]; + self.aiVM = [[AiVM alloc] init]; /// 1:控件初始化 [self setupUI]; /// 2:绑定事件 [self bindActions]; - /// 3:填充数据 + /// 3:加载数据 [self loadData]; } @@ -133,20 +140,55 @@ [self.view addGestureRecognizer:tap]; } -#pragma mark - 3:填充数据 +#pragma mark - 3:加载数据 - (void)loadData { - // 加载背景图 - if (self.backgroundImageURL.length > 0) { - [self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:self.backgroundImageURL] + if (self.companionId <= 0) { + NSLog(@"[AIPersonInfoVC] companionId 无效"); + return; + } + + [KBHUD show]; + + __weak typeof(self) weakSelf = self; + [self.aiVM fetchCompanionDetailWithCompanionId:self.companionId + completion:^(KBAICompanionDetailModel * _Nullable detail, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + [KBHUD dismiss]; + + if (error) { + NSLog(@"[AIPersonInfoVC] 获取详情失败: %@", error.localizedDescription); + [KBHUD showError:error.localizedDescription]; + return; + } + + if (!detail) { + NSLog(@"[AIPersonInfoVC] 详情数据为空"); + return; + } + + weakSelf.detailModel = detail; + [weakSelf updateUI]; + }); + }]; +} + +#pragma mark - 4:更新 UI + +- (void)updateUI { + // 加载背景图(优先使用 coverImageUrl,其次 avatarUrl) + NSString *imageUrl = self.detailModel.coverImageUrl.length > 0 ? self.detailModel.coverImageUrl : self.detailModel.avatarUrl; + if (imageUrl.length > 0) { + [self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:KBPlaceholderImage]; } // 设置名称 - self.nameLabel.text = self.personaName ?: @""; + self.nameLabel.text = self.detailModel.name ?: @""; - // 设置介绍 - self.introLabel.text = self.personaIntroduction ?: @""; + // 设置介绍(优先使用 introText,其次 shortDesc) + NSString *intro = self.detailModel.introText.length > 0 ? self.detailModel.introText : self.detailModel.shortDesc; + self.introLabel.text = intro ?: @""; } #pragma mark - Actions @@ -162,9 +204,9 @@ - (void)reportButtonTapped { self.reportPopView.hidden = YES; - AIReportVC *vc = [[AIReportVC alloc] init]; - vc.personaId = self.personaId; - [self.navigationController pushViewController:vc animated:YES]; +// AIReportVC *vc = [[AIReportVC alloc] init]; +// vc.personaId = self.companionId; +// [self.navigationController pushViewController:vc animated:YES]; } - (void)goChatButtonTapped { @@ -228,9 +270,11 @@ - (UIButton *)moreButton { if (!_moreButton) { _moreButton = [UIButton buttonWithType:UIButtonTypeCustom]; - // 使用三个点的图标,如果没有可以用系统的 - [_moreButton setImage:[UIImage imageNamed:@"ai_more_icon"] forState:UIControlStateNormal]; - if (![UIImage imageNamed:@"ai_more_icon"]) { + // 使用三个点的图标 + UIImage *moreImage = [UIImage imageNamed:@"ai_more_icon"]; + if (moreImage) { + [_moreButton setImage:moreImage forState:UIControlStateNormal]; + } else { // 如果没有图标,使用文字 [_moreButton setTitle:@"•••" forState:UIControlStateNormal]; [_moreButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; @@ -250,13 +294,16 @@ // 举报按钮 UIButton *reportBtn = [UIButton buttonWithType:UIButtonTypeCustom]; - [reportBtn setImage:[UIImage imageNamed:@"ai_report_icon"] forState:UIControlStateNormal]; + UIImage *reportIcon = [UIImage imageNamed:@"ai_report_icon"]; + if (reportIcon) { + [reportBtn setImage:reportIcon forState:UIControlStateNormal]; + reportBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 8, 0, 0); + reportBtn.titleEdgeInsets = UIEdgeInsetsMake(0, 16, 0, 0); + } [reportBtn setTitle:KBLocalized(@"Report") forState:UIControlStateNormal]; [reportBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; reportBtn.titleLabel.font = [UIFont systemFontOfSize:14]; reportBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - reportBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 8, 0, 0); - reportBtn.titleEdgeInsets = UIEdgeInsetsMake(0, 16, 0, 0); [reportBtn addTarget:self action:@selector(reportButtonTapped) forControlEvents:UIControlEventTouchUpInside]; [_reportPopView addSubview:reportBtn]; diff --git a/keyBoard/Class/AiTalk/VM/AiVM.h b/keyBoard/Class/AiTalk/VM/AiVM.h index 8071792..95e7481 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.h +++ b/keyBoard/Class/AiTalk/VM/AiVM.h @@ -164,6 +164,14 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu - (void)resetChatSessionWithCompanionId:(NSInteger)companionId completion:(void(^)(KBChatSessionResetResponse * _Nullable response, NSError * _Nullable error))completion; +#pragma mark - AI 角色详情接口 + +/// 根据 ID 获取 AI 角色详情 +/// @param companionId AI 角色 ID +/// @param completion 完成回调(返回角色详情) +- (void)fetchCompanionDetailWithCompanionId:(NSInteger)companionId + completion:(void(^)(KBAICompanionDetailModel * _Nullable detail, 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 212952f..43ddbb5 100644 --- a/keyBoard/Class/AiTalk/VM/AiVM.m +++ b/keyBoard/Class/AiTalk/VM/AiVM.m @@ -12,6 +12,7 @@ #import "KBLikedCompanionModel.h" #import "KBChattedCompanionModel.h" #import "KBChatSessionResetModel.h" +#import "KBAICompanionDetailModel.h" #import @implementation KBAiSyncData @@ -753,4 +754,58 @@ autoShowBusinessError:NO }]; } +#pragma mark - AI 角色详情接口 + +- (void)fetchCompanionDetailWithCompanionId:(NSInteger)companionId + completion:(void (^)(KBAICompanionDetailModel * _Nullable, NSError * _Nullable))completion { + NSString *path = [NSString stringWithFormat:@"/ai-companion/%ld", (long)companionId]; + + NSLog(@"[AiVM] %@ request", path); + [[KBNetworkManager shared] + GET:path + parameters:nil + headers:nil +autoShowBusinessError:NO + completion:^(NSDictionary *_Nullable json, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + NSLog(@"[AiVM] %@ failed: %@", path, error.localizedDescription ?: @""); + if (completion) { + completion(nil, error); + } + return; + } + + NSLog(@"[AiVM] %@ response: %@", path, 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; + } + + id dataObj = json[@"data"]; + if ([dataObj isKindOfClass:[NSDictionary class]]) { + KBAICompanionDetailModel *detail = [KBAICompanionDetailModel mj_objectWithKeyValues:dataObj]; + if (completion) { + completion(detail, nil); + } + } else { + NSError *parseError = [NSError errorWithDomain:@"AiVM" + code:-1 + userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}]; + if (completion) { + completion(nil, parseError); + } + } + }]; +} + @end