1
This commit is contained in:
@@ -155,6 +155,7 @@
|
|||||||
048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */; };
|
048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */; };
|
||||||
048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */; };
|
048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */; };
|
||||||
048FFD422F29F700005D62AE /* KBChatSessionResetModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */; };
|
048FFD422F29F700005D62AE /* KBChatSessionResetModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */; };
|
||||||
|
048FFD472F2B45D4005D62AE /* AIPersonInfoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD462F2B45D4005D62AE /* AIPersonInfoVC.m */; };
|
||||||
0498BD622EDFFC12006CC1D5 /* KBMyVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */; };
|
0498BD622EDFFC12006CC1D5 /* KBMyVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */; };
|
||||||
0498BD652EE0116D006CC1D5 /* KBEmailLoginVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD642EE0116D006CC1D5 /* KBEmailLoginVC.m */; };
|
0498BD652EE0116D006CC1D5 /* KBEmailLoginVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD642EE0116D006CC1D5 /* KBEmailLoginVC.m */; };
|
||||||
0498BD682EE01180006CC1D5 /* KBEmailRegistVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD672EE01180006CC1D5 /* KBEmailRegistVC.m */; };
|
0498BD682EE01180006CC1D5 /* KBEmailRegistVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD672EE01180006CC1D5 /* KBEmailRegistVC.m */; };
|
||||||
@@ -577,6 +578,8 @@
|
|||||||
048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChattedCompanionModel.m; sourceTree = "<group>"; };
|
048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChattedCompanionModel.m; sourceTree = "<group>"; };
|
||||||
048FFD402F29F700005D62AE /* KBChatSessionResetModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatSessionResetModel.h; sourceTree = "<group>"; };
|
048FFD402F29F700005D62AE /* KBChatSessionResetModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChatSessionResetModel.h; sourceTree = "<group>"; };
|
||||||
048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatSessionResetModel.m; sourceTree = "<group>"; };
|
048FFD412F29F700005D62AE /* KBChatSessionResetModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChatSessionResetModel.m; sourceTree = "<group>"; };
|
||||||
|
048FFD452F2B45D4005D62AE /* AIPersonInfoVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AIPersonInfoVC.h; sourceTree = "<group>"; };
|
||||||
|
048FFD462F2B45D4005D62AE /* AIPersonInfoVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AIPersonInfoVC.m; sourceTree = "<group>"; };
|
||||||
0498BD5E2EDF2157006CC1D5 /* KBBizCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBizCode.h; sourceTree = "<group>"; };
|
0498BD5E2EDF2157006CC1D5 /* KBBizCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBizCode.h; sourceTree = "<group>"; };
|
||||||
0498BD602EDFFC12006CC1D5 /* KBMyVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyVM.h; sourceTree = "<group>"; };
|
0498BD602EDFFC12006CC1D5 /* KBMyVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyVM.h; sourceTree = "<group>"; };
|
||||||
0498BD612EDFFC12006CC1D5 /* KBMyVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyVM.m; sourceTree = "<group>"; };
|
0498BD612EDFFC12006CC1D5 /* KBMyVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyVM.m; sourceTree = "<group>"; };
|
||||||
@@ -1083,6 +1086,8 @@
|
|||||||
048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */,
|
048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */,
|
||||||
048FFD352F29F400005D62AE /* KBAIMessageListVC.h */,
|
048FFD352F29F400005D62AE /* KBAIMessageListVC.h */,
|
||||||
048FFD362F29F400005D62AE /* KBAIMessageListVC.m */,
|
048FFD362F29F400005D62AE /* KBAIMessageListVC.m */,
|
||||||
|
048FFD452F2B45D4005D62AE /* AIPersonInfoVC.h */,
|
||||||
|
048FFD462F2B45D4005D62AE /* AIPersonInfoVC.m */,
|
||||||
);
|
);
|
||||||
path = VC;
|
path = VC;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2393,6 +2398,7 @@
|
|||||||
04D1F6B32EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */,
|
04D1F6B32EDFF10A00B12345 /* KBSkinInstallBridge.m in Sources */,
|
||||||
04122F912EC73AF700EF7AB3 /* KBVipPay.m in Sources */,
|
04122F912EC73AF700EF7AB3 /* KBVipPay.m in Sources */,
|
||||||
0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */,
|
0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */,
|
||||||
|
048FFD472F2B45D4005D62AE /* AIPersonInfoVC.m in Sources */,
|
||||||
047C650D2EBC8A840035E841 /* KBPanModalView.m in Sources */,
|
047C650D2EBC8A840035E841 /* KBPanModalView.m in Sources */,
|
||||||
0450AC0A2EF11E4400B6AF06 /* StoreKitManager.swift in Sources */,
|
0450AC0A2EF11E4400B6AF06 /* StoreKitManager.swift in Sources */,
|
||||||
0450AC0B2EF11E4400B6AF06 /* StoreKitStateConverter.swift in Sources */,
|
0450AC0B2EF11E4400B6AF06 /* StoreKitStateConverter.swift in Sources */,
|
||||||
|
|||||||
63
keyBoard/Class/AiTalk/M/KBAICompanionDetailModel.h
Normal file
63
keyBoard/Class/AiTalk/M/KBAICompanionDetailModel.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// KBAICompanionDetailModel.h
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/// AI 角色详情 Model
|
||||||
|
@interface KBAICompanionDetailModel : NSObject
|
||||||
|
|
||||||
|
/// 角色 ID
|
||||||
|
@property (nonatomic, assign) NSInteger companionId;
|
||||||
|
/// 名称
|
||||||
|
@property (nonatomic, copy, nullable) NSString *name;
|
||||||
|
/// 头像 URL
|
||||||
|
@property (nonatomic, copy, nullable) NSString *avatarUrl;
|
||||||
|
/// 封面图 URL
|
||||||
|
@property (nonatomic, copy, nullable) NSString *coverImageUrl;
|
||||||
|
/// 性别
|
||||||
|
@property (nonatomic, copy, nullable) NSString *gender;
|
||||||
|
/// 年龄范围
|
||||||
|
@property (nonatomic, copy, nullable) NSString *ageRange;
|
||||||
|
/// 简短描述
|
||||||
|
@property (nonatomic, copy, nullable) NSString *shortDesc;
|
||||||
|
/// 介绍文本
|
||||||
|
@property (nonatomic, copy, nullable) NSString *introText;
|
||||||
|
/// 性格标签
|
||||||
|
@property (nonatomic, copy, nullable) NSString *personalityTags;
|
||||||
|
/// 说话风格
|
||||||
|
@property (nonatomic, copy, nullable) NSString *speakingStyle;
|
||||||
|
/// 排序
|
||||||
|
@property (nonatomic, assign) NSInteger sortOrder;
|
||||||
|
/// 热度分数
|
||||||
|
@property (nonatomic, assign) NSInteger popularityScore;
|
||||||
|
/// 开场白
|
||||||
|
@property (nonatomic, copy, nullable) NSString *prologue;
|
||||||
|
/// 开场白音频
|
||||||
|
@property (nonatomic, copy, nullable) NSString *prologueAudio;
|
||||||
|
/// 点赞数
|
||||||
|
@property (nonatomic, assign) NSInteger likeCount;
|
||||||
|
/// 评论数
|
||||||
|
@property (nonatomic, assign) NSInteger commentCount;
|
||||||
|
/// 当前用户是否已点赞
|
||||||
|
@property (nonatomic, assign) BOOL liked;
|
||||||
|
/// 创建时间
|
||||||
|
@property (nonatomic, copy, nullable) NSString *createdAt;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
/// AI 角色详情响应 Model
|
||||||
|
@interface KBAICompanionDetailResponse : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, assign) NSInteger code;
|
||||||
|
@property (nonatomic, strong, nullable) KBAICompanionDetailModel *data;
|
||||||
|
@property (nonatomic, copy, nullable) NSString *message;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
23
keyBoard/Class/AiTalk/M/KBAICompanionDetailModel.m
Normal file
23
keyBoard/Class/AiTalk/M/KBAICompanionDetailModel.m
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// KBAICompanionDetailModel.m
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "KBAICompanionDetailModel.h"
|
||||||
|
#import <MJExtension/MJExtension.h>
|
||||||
|
|
||||||
|
@implementation KBAICompanionDetailModel
|
||||||
|
|
||||||
|
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||||
|
return @{
|
||||||
|
@"companionId": @"id"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation KBAICompanionDetailResponse
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
#import "KBAIReplyModel.h"
|
#import "KBAIReplyModel.h"
|
||||||
#import "KBCommentModel.h"
|
#import "KBCommentModel.h"
|
||||||
#import "AiVM.h"
|
#import "AiVM.h"
|
||||||
|
#import "KBUserSessionManager.h"
|
||||||
|
#import "KBUser.h"
|
||||||
#import <MJExtension/MJExtension.h>
|
#import <MJExtension/MJExtension.h>
|
||||||
#import <Masonry/Masonry.h>
|
#import <Masonry/Masonry.h>
|
||||||
#import <MJRefresh/MJRefresh.h>
|
#import <MJRefresh/MJRefresh.h>
|
||||||
@@ -56,6 +58,81 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
|||||||
|
|
||||||
@implementation KBAICommentView
|
@implementation KBAICommentView
|
||||||
|
|
||||||
|
#pragma mark - Local Model Builders
|
||||||
|
|
||||||
|
- (NSString *)currentUserName {
|
||||||
|
KBUser *user = [KBUserSessionManager shared].currentUser;
|
||||||
|
if (user.nickName.length > 0) {
|
||||||
|
return user.nickName;
|
||||||
|
}
|
||||||
|
return @"我";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)currentUserId {
|
||||||
|
KBUser *user = [KBUserSessionManager shared].currentUser;
|
||||||
|
return user.userId ?: @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)currentUserAvatarUrl {
|
||||||
|
KBUser *user = [KBUserSessionManager shared].currentUser;
|
||||||
|
return user.avatarUrl ?: @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)generateTempIdString {
|
||||||
|
long long ms = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
|
||||||
|
// 使用负数避免与后端 ID 冲突
|
||||||
|
long long tmp = -ms;
|
||||||
|
return [NSString stringWithFormat:@"%lld", tmp];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (KBAICommentModel *)buildLocalNewCommentWithText:(NSString *)text
|
||||||
|
serverItem:(KBCommentItem *_Nullable)serverItem
|
||||||
|
tableWidth:(CGFloat)tableWidth {
|
||||||
|
KBAICommentModel *comment = [[KBAICommentModel alloc] init];
|
||||||
|
NSString *cid = nil;
|
||||||
|
if (serverItem && serverItem.commentId > 0) {
|
||||||
|
cid = [NSString stringWithFormat:@"%ld", (long)serverItem.commentId];
|
||||||
|
} else {
|
||||||
|
cid = [self generateTempIdString];
|
||||||
|
}
|
||||||
|
comment.commentId = cid;
|
||||||
|
comment.userId = [self currentUserId];
|
||||||
|
comment.userName = [self currentUserName];
|
||||||
|
comment.avatarUrl = [self currentUserAvatarUrl];
|
||||||
|
comment.content = text ?: @"";
|
||||||
|
comment.likeCount = 0;
|
||||||
|
comment.liked = NO;
|
||||||
|
comment.createTime = [[NSDate date] timeIntervalSince1970];
|
||||||
|
comment.replies = @[];
|
||||||
|
comment.cachedHeaderHeight =
|
||||||
|
[comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (KBAIReplyModel *)buildLocalNewReplyWithText:(NSString *)text
|
||||||
|
serverItem:(KBCommentItem *_Nullable)serverItem
|
||||||
|
replyToUserName:(NSString *)replyToUserName
|
||||||
|
tableWidth:(CGFloat)tableWidth {
|
||||||
|
KBAIReplyModel *reply = [[KBAIReplyModel alloc] init];
|
||||||
|
NSString *rid = nil;
|
||||||
|
if (serverItem && serverItem.commentId > 0) {
|
||||||
|
rid = [NSString stringWithFormat:@"%ld", (long)serverItem.commentId];
|
||||||
|
} else {
|
||||||
|
rid = [self generateTempIdString];
|
||||||
|
}
|
||||||
|
reply.replyId = rid;
|
||||||
|
reply.userId = [self currentUserId];
|
||||||
|
reply.userName = [self currentUserName];
|
||||||
|
reply.avatarUrl = [self currentUserAvatarUrl];
|
||||||
|
reply.content = text ?: @"";
|
||||||
|
reply.replyToUserName = replyToUserName ?: @"";
|
||||||
|
reply.likeCount = 0;
|
||||||
|
reply.liked = NO;
|
||||||
|
reply.createTime = [[NSDate date] timeIntervalSince1970];
|
||||||
|
reply.cachedCellHeight = [reply calculateCellHeightWithMaxWidth:tableWidth];
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
#pragma mark - Lifecycle
|
||||||
|
|
||||||
- (instancetype)initWithFrame:(CGRect)frame {
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||||||
@@ -289,7 +366,36 @@ static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
|||||||
|
|
||||||
for (KBCommentItem *item in pageModel.records) {
|
for (KBCommentItem *item in pageModel.records) {
|
||||||
// 转换为 KBAICommentModel(使用 MJExtension)
|
// 转换为 KBAICommentModel(使用 MJExtension)
|
||||||
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[item mj_keyValues]];
|
// 注意:KBCommentItem 通过 MJExtension 将后端字段 id 映射为了 commentId。
|
||||||
|
// 这里如果直接用 mj_keyValues,会导致字典里只有 commentId,KBAICommentModel/KBAIReplyModel
|
||||||
|
// 的映射(commentId/replyId -> id)拿不到值,最终 commentId/replyId 为空,进而影响发送回复时的 parentId/rootId。
|
||||||
|
NSMutableDictionary *itemKV = [[item mj_keyValues] mutableCopy];
|
||||||
|
id commentIdVal = itemKV[@"commentId"];
|
||||||
|
if (commentIdVal) {
|
||||||
|
itemKV[@"id"] = commentIdVal;
|
||||||
|
[itemKV removeObjectForKey:@"commentId"];
|
||||||
|
}
|
||||||
|
|
||||||
|
id repliesObj = itemKV[@"replies"];
|
||||||
|
if ([repliesObj isKindOfClass:[NSArray class]]) {
|
||||||
|
NSArray *replies = (NSArray *)repliesObj;
|
||||||
|
NSMutableArray *fixedReplies = [NSMutableArray arrayWithCapacity:replies.count];
|
||||||
|
for (id obj in replies) {
|
||||||
|
if (![obj isKindOfClass:[NSDictionary class]]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
NSMutableDictionary *replyKV = [((NSDictionary *)obj) mutableCopy];
|
||||||
|
id replyCommentIdVal = replyKV[@"commentId"];
|
||||||
|
if (replyCommentIdVal) {
|
||||||
|
replyKV[@"id"] = replyCommentIdVal;
|
||||||
|
[replyKV removeObjectForKey:@"commentId"];
|
||||||
|
}
|
||||||
|
[fixedReplies addObject:[replyKV copy]];
|
||||||
|
}
|
||||||
|
itemKV[@"replies"] = [fixedReplies copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:[itemKV copy]];
|
||||||
|
|
||||||
// 预先计算并缓存 Header 高度
|
// 预先计算并缓存 Header 高度
|
||||||
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
comment.cachedHeaderHeight = [comment calculateHeaderHeightWithMaxWidth:tableWidth];
|
||||||
@@ -805,9 +911,45 @@ static NSInteger const kRepliesLoadCount = 5;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
- (void)sendNewCommentWithText:(NSString *)text tableWidth:(CGFloat)tableWidth {
|
||||||
// TODO: 调用网络接口发送一级评论
|
|
||||||
NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
|
NSLog(@"[KBAICommentView] 发送一级评论:%@", text);
|
||||||
|
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
[self.aiVM addCommentWithCompanionId:self.companionId
|
||||||
|
content:text
|
||||||
|
parentId:nil
|
||||||
|
rootId:nil
|
||||||
|
completion:^(KBCommentItem * _Nullable newItem, NSInteger code, NSError * _Nullable error) {
|
||||||
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||||
|
if (!strongSelf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (error || code != 0) {
|
||||||
|
NSLog(@"[KBAICommentView] 发送一级评论失败:%@", error.localizedDescription ?: @"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地插入新评论到第一条,不再全量刷新
|
||||||
|
KBAICommentModel *localComment =
|
||||||
|
[strongSelf buildLocalNewCommentWithText:text
|
||||||
|
serverItem:newItem
|
||||||
|
tableWidth:tableWidth];
|
||||||
|
[strongSelf.comments insertObject:localComment atIndex:0];
|
||||||
|
strongSelf.totalCommentCount += 1;
|
||||||
|
[strongSelf updateTitle];
|
||||||
|
[strongSelf hideEmptyState];
|
||||||
|
|
||||||
|
[strongSelf.tableView beginUpdates];
|
||||||
|
[strongSelf.tableView
|
||||||
|
insertSections:[NSIndexSet indexSetWithIndex:0]
|
||||||
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
[strongSelf.tableView endUpdates];
|
||||||
|
|
||||||
|
[strongSelf.tableView setContentOffset:CGPointZero animated:YES];
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
// 示例代码:
|
// 示例代码:
|
||||||
// [self.aiVM sendCommentWithCompanionId:self.companionId
|
// [self.aiVM sendCommentWithCompanionId:self.companionId
|
||||||
// content:text
|
// content:text
|
||||||
@@ -840,9 +982,102 @@ static NSInteger const kRepliesLoadCount = 5;
|
|||||||
if (!comment)
|
if (!comment)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: 调用网络接口发送二级评论(回复)
|
|
||||||
NSLog(@"[KBAICommentView] 回复评论 %@:%@", comment.commentId, text);
|
NSLog(@"[KBAICommentView] 回复评论 %@:%@", comment.commentId, text);
|
||||||
|
|
||||||
|
NSInteger root = [comment.commentId integerValue];
|
||||||
|
NSNumber *rootId = @(root);
|
||||||
|
NSNumber *parentId = nil;
|
||||||
|
|
||||||
|
if (self.replyToReply && self.replyToReply.replyId.length > 0) {
|
||||||
|
parentId = @([self.replyToReply.replyId integerValue]);
|
||||||
|
} else {
|
||||||
|
parentId = @(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
[self.aiVM addCommentWithCompanionId:self.companionId
|
||||||
|
content:text
|
||||||
|
parentId:parentId
|
||||||
|
rootId:rootId
|
||||||
|
completion:^(KBCommentItem * _Nullable newItem, NSInteger code, NSError * _Nullable error) {
|
||||||
|
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||||
|
if (!strongSelf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (error || code != 0) {
|
||||||
|
NSLog(@"[KBAICommentView] 回复评论失败:%@", error.localizedDescription ?: @"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSInteger section = [strongSelf.comments indexOfObject:comment];
|
||||||
|
if (section == NSNotFound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSInteger oldTotalReplyCount = comment.totalReplyCount;
|
||||||
|
BOOL wasFooterHidden = (oldTotalReplyCount == 0);
|
||||||
|
BOOL wasFullyExpanded =
|
||||||
|
(comment.isRepliesExpanded &&
|
||||||
|
comment.displayedReplies.count == oldTotalReplyCount);
|
||||||
|
|
||||||
|
NSString *replyToUserName = @"";
|
||||||
|
if (strongSelf.replyToReply && strongSelf.replyToReply.userName.length > 0) {
|
||||||
|
replyToUserName = strongSelf.replyToReply.userName;
|
||||||
|
} else if (comment.userName.length > 0) {
|
||||||
|
replyToUserName = comment.userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
KBAIReplyModel *localReply =
|
||||||
|
[strongSelf buildLocalNewReplyWithText:text
|
||||||
|
serverItem:newItem
|
||||||
|
replyToUserName:replyToUserName
|
||||||
|
tableWidth:tableWidth];
|
||||||
|
|
||||||
|
NSArray<KBAIReplyModel *> *oldReplies = comment.replies ?: @[];
|
||||||
|
NSMutableArray<KBAIReplyModel *> *newReplies =
|
||||||
|
[NSMutableArray arrayWithArray:oldReplies];
|
||||||
|
[newReplies addObject:localReply];
|
||||||
|
comment.replies = [newReplies copy];
|
||||||
|
|
||||||
|
strongSelf.totalCommentCount += 1;
|
||||||
|
[strongSelf updateTitle];
|
||||||
|
|
||||||
|
// 若当前已完整展开,则直接插入新行;否则保持 displayedReplies 为前缀,避免破坏 loadMoreReplies 逻辑
|
||||||
|
if (wasFullyExpanded) {
|
||||||
|
[comment.displayedReplies addObject:localReply];
|
||||||
|
NSInteger newRowIndex = comment.displayedReplies.count - 1;
|
||||||
|
NSIndexPath *indexPath =
|
||||||
|
[NSIndexPath indexPathForRow:newRowIndex inSection:section];
|
||||||
|
[strongSelf.tableView beginUpdates];
|
||||||
|
[strongSelf.tableView insertRowsAtIndexPaths:@[ indexPath ]
|
||||||
|
withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
[strongSelf.tableView endUpdates];
|
||||||
|
|
||||||
|
KBAICommentFooterView *footerView =
|
||||||
|
(KBAICommentFooterView *)[strongSelf.tableView footerViewForSection:section];
|
||||||
|
if (footerView) {
|
||||||
|
[footerView configureWithComment:comment];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wasFooterHidden) {
|
||||||
|
[strongSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
|
||||||
|
withRowAnimation:UITableViewRowAnimationNone];
|
||||||
|
} else {
|
||||||
|
KBAICommentFooterView *footerView =
|
||||||
|
(KBAICommentFooterView *)[strongSelf.tableView footerViewForSection:section];
|
||||||
|
if (footerView) {
|
||||||
|
[footerView configureWithComment:comment];
|
||||||
|
} else {
|
||||||
|
[strongSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
|
||||||
|
withRowAnimation:UITableViewRowAnimationNone];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
// 示例代码:
|
// 示例代码:
|
||||||
// NSInteger parentId = [comment.commentId integerValue];
|
// NSInteger parentId = [comment.commentId integerValue];
|
||||||
// [self.aiVM replyCommentWithParentId:parentId
|
// [self.aiVM replyCommentWithParentId:parentId
|
||||||
|
|||||||
25
keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h
Normal file
25
keyBoard/Class/AiTalk/VC/AIPersonInfoVC.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// AIPersonInfoVC.h
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "BaseViewController.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
313
keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m
Normal file
313
keyBoard/Class/AiTalk/VC/AIPersonInfoVC.m
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
//
|
||||||
|
// AIPersonInfoVC.m
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AIPersonInfoVC.h"
|
||||||
|
#import "AIReportVC.h"
|
||||||
|
#import <SDWebImage/SDWebImage.h>
|
||||||
|
|
||||||
|
@interface AIPersonInfoVC ()
|
||||||
|
|
||||||
|
/// 背景图
|
||||||
|
@property (nonatomic, strong) UIImageView *backgroundImageView;
|
||||||
|
/// 底部磨砂渐变视图
|
||||||
|
@property (nonatomic, strong) UIVisualEffectView *blurEffectView;
|
||||||
|
/// 渐变遮罩层
|
||||||
|
@property (nonatomic, strong) CAGradientLayer *gradientMaskLayer;
|
||||||
|
/// 左上角关闭按钮
|
||||||
|
@property (nonatomic, strong) UIButton *closeButton;
|
||||||
|
/// 右上角更多按钮
|
||||||
|
@property (nonatomic, strong) UIButton *moreButton;
|
||||||
|
/// 举报弹窗视图
|
||||||
|
@property (nonatomic, strong) UIView *reportPopView;
|
||||||
|
/// 名称标签
|
||||||
|
@property (nonatomic, strong) UILabel *nameLabel;
|
||||||
|
/// 介绍标签
|
||||||
|
@property (nonatomic, strong) UILabel *introLabel;
|
||||||
|
/// 底部 Go Chatting 按钮
|
||||||
|
@property (nonatomic, strong) UIButton *goChatButton;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AIPersonInfoVC
|
||||||
|
|
||||||
|
#pragma mark - Lifecycle
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.kb_navView.hidden = YES;
|
||||||
|
self.view.backgroundColor = [UIColor blackColor];
|
||||||
|
|
||||||
|
/// 1:控件初始化
|
||||||
|
[self setupUI];
|
||||||
|
/// 2:绑定事件
|
||||||
|
[self bindActions];
|
||||||
|
/// 3:填充数据
|
||||||
|
[self loadData];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLayoutSubviews {
|
||||||
|
[super viewDidLayoutSubviews];
|
||||||
|
// 更新渐变遮罩 frame
|
||||||
|
self.gradientMaskLayer.frame = self.blurEffectView.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 1:控件初始化
|
||||||
|
|
||||||
|
- (void)setupUI {
|
||||||
|
// 背景图
|
||||||
|
[self.view addSubview:self.backgroundImageView];
|
||||||
|
[self.backgroundImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(self.view);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 底部磨砂渐变
|
||||||
|
[self.view addSubview:self.blurEffectView];
|
||||||
|
[self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.right.bottom.equalTo(self.view);
|
||||||
|
make.height.mas_equalTo(KB_SCREEN_HEIGHT * 0.75);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 左上角关闭按钮
|
||||||
|
[self.view addSubview:self.closeButton];
|
||||||
|
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.view).offset(16);
|
||||||
|
make.top.equalTo(self.view).offset(KB_STATUSBAR_HEIGHT + 10);
|
||||||
|
make.width.height.mas_equalTo(32);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 右上角更多按钮
|
||||||
|
[self.view addSubview:self.moreButton];
|
||||||
|
[self.moreButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.right.equalTo(self.view).offset(-16);
|
||||||
|
make.centerY.equalTo(self.closeButton);
|
||||||
|
make.width.height.mas_equalTo(32);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 名称标签
|
||||||
|
[self.view addSubview:self.nameLabel];
|
||||||
|
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.view).offset(20);
|
||||||
|
make.right.equalTo(self.view).offset(-20);
|
||||||
|
make.centerY.equalTo(self.view).offset(20);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 介绍标签
|
||||||
|
[self.view addSubview:self.introLabel];
|
||||||
|
[self.introLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.view).offset(20);
|
||||||
|
make.right.equalTo(self.view).offset(-20);
|
||||||
|
make.top.equalTo(self.nameLabel.mas_bottom).offset(16);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 底部 Go Chatting 按钮
|
||||||
|
[self.view addSubview:self.goChatButton];
|
||||||
|
[self.goChatButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.view).offset(40);
|
||||||
|
make.right.equalTo(self.view).offset(-40);
|
||||||
|
make.bottom.equalTo(self.view).offset(-KB_SAFE_BOTTOM - 30);
|
||||||
|
make.height.mas_equalTo(50);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 举报弹窗(初始隐藏)
|
||||||
|
[self.view addSubview:self.reportPopView];
|
||||||
|
self.reportPopView.hidden = YES;
|
||||||
|
[self.reportPopView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.right.equalTo(self.moreButton.mas_left).offset(8);
|
||||||
|
make.top.equalTo(self.moreButton.mas_bottom).offset(4);
|
||||||
|
make.width.mas_equalTo(100);
|
||||||
|
make.height.mas_equalTo(40);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 2:绑定事件
|
||||||
|
|
||||||
|
- (void)bindActions {
|
||||||
|
// 点击空白处隐藏弹窗
|
||||||
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)];
|
||||||
|
tap.cancelsTouchesInView = NO;
|
||||||
|
[self.view addGestureRecognizer:tap];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 3:填充数据
|
||||||
|
|
||||||
|
- (void)loadData {
|
||||||
|
// 加载背景图
|
||||||
|
if (self.backgroundImageURL.length > 0) {
|
||||||
|
[self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:self.backgroundImageURL]
|
||||||
|
placeholderImage:KBPlaceholderImage];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置名称
|
||||||
|
self.nameLabel.text = self.personaName ?: @"";
|
||||||
|
|
||||||
|
// 设置介绍
|
||||||
|
self.introLabel.text = self.personaIntroduction ?: @"";
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
- (void)closeButtonTapped {
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moreButtonTapped {
|
||||||
|
self.reportPopView.hidden = !self.reportPopView.hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reportButtonTapped {
|
||||||
|
self.reportPopView.hidden = YES;
|
||||||
|
|
||||||
|
AIReportVC *vc = [[AIReportVC alloc] init];
|
||||||
|
vc.personaId = self.personaId;
|
||||||
|
[self.navigationController pushViewController:vc animated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)goChatButtonTapped {
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handleBackgroundTap:(UITapGestureRecognizer *)tap {
|
||||||
|
CGPoint point = [tap locationInView:self.view];
|
||||||
|
|
||||||
|
// 如果点击的不是 moreButton 和 reportPopView 区域,则隐藏弹窗
|
||||||
|
if (!CGRectContainsPoint(self.moreButton.frame, point) &&
|
||||||
|
!CGRectContainsPoint(self.reportPopView.frame, point)) {
|
||||||
|
self.reportPopView.hidden = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lazy Load
|
||||||
|
|
||||||
|
- (UIImageView *)backgroundImageView {
|
||||||
|
if (!_backgroundImageView) {
|
||||||
|
_backgroundImageView = [[UIImageView alloc] init];
|
||||||
|
_backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||||
|
_backgroundImageView.clipsToBounds = YES;
|
||||||
|
}
|
||||||
|
return _backgroundImageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIVisualEffectView *)blurEffectView {
|
||||||
|
if (!_blurEffectView) {
|
||||||
|
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
|
||||||
|
_blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
|
||||||
|
_blurEffectView.layer.mask = self.gradientMaskLayer;
|
||||||
|
}
|
||||||
|
return _blurEffectView;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CAGradientLayer *)gradientMaskLayer {
|
||||||
|
if (!_gradientMaskLayer) {
|
||||||
|
_gradientMaskLayer = [CAGradientLayer layer];
|
||||||
|
// 从上到下:透明 -> 不透明
|
||||||
|
_gradientMaskLayer.colors = @[
|
||||||
|
(__bridge id)[UIColor clearColor].CGColor,
|
||||||
|
(__bridge id)[UIColor whiteColor].CGColor
|
||||||
|
];
|
||||||
|
_gradientMaskLayer.startPoint = CGPointMake(0.5, 0);
|
||||||
|
_gradientMaskLayer.endPoint = CGPointMake(0.5, 0.4);
|
||||||
|
_gradientMaskLayer.locations = @[@0, @1];
|
||||||
|
}
|
||||||
|
return _gradientMaskLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIButton *)closeButton {
|
||||||
|
if (!_closeButton) {
|
||||||
|
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[_closeButton setImage:[UIImage imageNamed:@"comment_close_icon"] forState:UIControlStateNormal];
|
||||||
|
[_closeButton addTarget:self action:@selector(closeButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _closeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIButton *)moreButton {
|
||||||
|
if (!_moreButton) {
|
||||||
|
_moreButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
// 使用三个点的图标,如果没有可以用系统的
|
||||||
|
[_moreButton setImage:[UIImage imageNamed:@"ai_more_icon"] forState:UIControlStateNormal];
|
||||||
|
if (![UIImage imageNamed:@"ai_more_icon"]) {
|
||||||
|
// 如果没有图标,使用文字
|
||||||
|
[_moreButton setTitle:@"•••" forState:UIControlStateNormal];
|
||||||
|
[_moreButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||||
|
_moreButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightBold];
|
||||||
|
}
|
||||||
|
[_moreButton addTarget:self action:@selector(moreButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _moreButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIView *)reportPopView {
|
||||||
|
if (!_reportPopView) {
|
||||||
|
_reportPopView = [[UIView alloc] init];
|
||||||
|
_reportPopView.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.9];
|
||||||
|
_reportPopView.layer.cornerRadius = 8;
|
||||||
|
_reportPopView.clipsToBounds = YES;
|
||||||
|
|
||||||
|
// 举报按钮
|
||||||
|
UIButton *reportBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
[reportBtn setImage:[UIImage imageNamed:@"ai_report_icon"] forState:UIControlStateNormal];
|
||||||
|
[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];
|
||||||
|
[reportBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(_reportPopView);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return _reportPopView;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UILabel *)nameLabel {
|
||||||
|
if (!_nameLabel) {
|
||||||
|
_nameLabel = [[UILabel alloc] init];
|
||||||
|
_nameLabel.font = [UIFont systemFontOfSize:28 weight:UIFontWeightBold];
|
||||||
|
_nameLabel.textColor = [UIColor whiteColor];
|
||||||
|
_nameLabel.textAlignment = NSTextAlignmentLeft;
|
||||||
|
}
|
||||||
|
return _nameLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UILabel *)introLabel {
|
||||||
|
if (!_introLabel) {
|
||||||
|
_introLabel = [[UILabel alloc] init];
|
||||||
|
_introLabel.font = [UIFont systemFontOfSize:14];
|
||||||
|
_introLabel.textColor = [UIColor colorWithWhite:1.0 alpha:0.8];
|
||||||
|
_introLabel.textAlignment = NSTextAlignmentLeft;
|
||||||
|
_introLabel.numberOfLines = 0;
|
||||||
|
}
|
||||||
|
return _introLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIButton *)goChatButton {
|
||||||
|
if (!_goChatButton) {
|
||||||
|
_goChatButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
|
||||||
|
// 设置背景图(如果有的话)
|
||||||
|
UIImage *bgImage = [UIImage imageNamed:@"ai_go_chat_bg"];
|
||||||
|
if (bgImage) {
|
||||||
|
[_goChatButton setBackgroundImage:bgImage forState:UIControlStateNormal];
|
||||||
|
} else {
|
||||||
|
// 没有背景图时使用渐变色
|
||||||
|
_goChatButton.backgroundColor = [UIColor colorWithRed:0.8 green:1.0 blue:0.6 alpha:1.0];
|
||||||
|
_goChatButton.layer.cornerRadius = 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_goChatButton setTitle:KBLocalized(@"Go Chatting") forState:UIControlStateNormal];
|
||||||
|
[_goChatButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||||
|
_goChatButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
||||||
|
[_goChatButton addTarget:self action:@selector(goChatButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _goChatButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
19
keyBoard/Class/AiTalk/VC/AIReportVC.h
Normal file
19
keyBoard/Class/AiTalk/VC/AIReportVC.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// AIReportVC.h
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "BaseViewController.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface AIReportVC : BaseViewController
|
||||||
|
|
||||||
|
/// 被举报的人设 ID
|
||||||
|
@property (nonatomic, assign) NSInteger personaId;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
160
keyBoard/Class/AiTalk/VC/AIReportVC.m
Normal file
160
keyBoard/Class/AiTalk/VC/AIReportVC.m
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
//
|
||||||
|
// AIReportVC.m
|
||||||
|
// keyBoard
|
||||||
|
//
|
||||||
|
// Created by Mac on 2026/1/29.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "AIReportVC.h"
|
||||||
|
|
||||||
|
@interface AIReportVC ()
|
||||||
|
|
||||||
|
/// 举报原因列表
|
||||||
|
@property (nonatomic, strong) NSArray<NSString *> *reportReasons;
|
||||||
|
/// 当前选中的索引
|
||||||
|
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||||
|
/// 举报原因 TableView
|
||||||
|
@property (nonatomic, strong) UITableView *tableView;
|
||||||
|
/// 提交按钮
|
||||||
|
@property (nonatomic, strong) UIButton *submitButton;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AIReportVC
|
||||||
|
|
||||||
|
#pragma mark - Lifecycle
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
self.view.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:1.0];
|
||||||
|
self.kb_titleLabel.text = KBLocalized(@"Report");
|
||||||
|
self.selectedIndex = -1;
|
||||||
|
|
||||||
|
/// 1:初始化数据
|
||||||
|
[self initData];
|
||||||
|
/// 2:控件初始化
|
||||||
|
[self setupUI];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 1:初始化数据
|
||||||
|
|
||||||
|
- (void)initData {
|
||||||
|
self.reportReasons = @[
|
||||||
|
KBLocalized(@"Inappropriate content"),
|
||||||
|
KBLocalized(@"Spam or advertising"),
|
||||||
|
KBLocalized(@"Harassment or bullying"),
|
||||||
|
KBLocalized(@"False information"),
|
||||||
|
KBLocalized(@"Intellectual property violation"),
|
||||||
|
KBLocalized(@"Other")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 2:控件初始化
|
||||||
|
|
||||||
|
- (void)setupUI {
|
||||||
|
[self.view addSubview:self.tableView];
|
||||||
|
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.equalTo(self.kb_navView.mas_bottom).offset(20);
|
||||||
|
make.left.right.equalTo(self.view);
|
||||||
|
make.bottom.equalTo(self.submitButton.mas_top).offset(-20);
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self.view addSubview:self.submitButton];
|
||||||
|
[self.submitButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.left.equalTo(self.view).offset(40);
|
||||||
|
make.right.equalTo(self.view).offset(-40);
|
||||||
|
make.bottom.equalTo(self.view).offset(-KB_SAFE_BOTTOM - 30);
|
||||||
|
make.height.mas_equalTo(50);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDataSource
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||||
|
return self.reportReasons.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportCell" forIndexPath:indexPath];
|
||||||
|
cell.backgroundColor = [UIColor clearColor];
|
||||||
|
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||||
|
cell.textLabel.text = self.reportReasons[indexPath.row];
|
||||||
|
cell.textLabel.textColor = [UIColor whiteColor];
|
||||||
|
cell.textLabel.font = [UIFont systemFontOfSize:16];
|
||||||
|
|
||||||
|
// 选中状态
|
||||||
|
if (indexPath.row == self.selectedIndex) {
|
||||||
|
cell.accessoryType = UITableViewCellAccessoryCheckmark;
|
||||||
|
cell.tintColor = [UIColor colorWithRed:0.8 green:1.0 blue:0.6 alpha:1.0];
|
||||||
|
} else {
|
||||||
|
cell.accessoryType = UITableViewCellAccessoryNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - UITableViewDelegate
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
self.selectedIndex = indexPath.row;
|
||||||
|
[tableView reloadData];
|
||||||
|
|
||||||
|
// 更新提交按钮状态
|
||||||
|
self.submitButton.enabled = YES;
|
||||||
|
self.submitButton.alpha = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
return 56;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
- (void)submitButtonTapped {
|
||||||
|
if (self.selectedIndex < 0) {
|
||||||
|
[KBHUD showError:KBLocalized(@"Please select a reason")];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason = self.reportReasons[self.selectedIndex];
|
||||||
|
NSLog(@"[AIReportVC] 举报人设 ID: %ld, 原因: %@", (long)self.personaId, reason);
|
||||||
|
|
||||||
|
// TODO: 调用举报接口
|
||||||
|
[KBHUD showSuccess:KBLocalized(@"Report submitted")];
|
||||||
|
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lazy Load
|
||||||
|
|
||||||
|
- (UITableView *)tableView {
|
||||||
|
if (!_tableView) {
|
||||||
|
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||||
|
_tableView.backgroundColor = [UIColor clearColor];
|
||||||
|
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||||
|
_tableView.delegate = self;
|
||||||
|
_tableView.dataSource = self;
|
||||||
|
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"ReportCell"];
|
||||||
|
}
|
||||||
|
return _tableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIButton *)submitButton {
|
||||||
|
if (!_submitButton) {
|
||||||
|
_submitButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||||
|
_submitButton.backgroundColor = [UIColor colorWithRed:0.8 green:1.0 blue:0.6 alpha:1.0];
|
||||||
|
_submitButton.layer.cornerRadius = 25;
|
||||||
|
[_submitButton setTitle:KBLocalized(@"Submit") forState:UIControlStateNormal];
|
||||||
|
[_submitButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||||
|
_submitButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
||||||
|
_submitButton.enabled = NO;
|
||||||
|
_submitButton.alpha = 0.5;
|
||||||
|
[_submitButton addTarget:self action:@selector(submitButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _submitButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#import "KBLikedCompanionModel.h"
|
#import "KBLikedCompanionModel.h"
|
||||||
#import "KBChattedCompanionModel.h"
|
#import "KBChattedCompanionModel.h"
|
||||||
#import "KBChatSessionResetModel.h"
|
#import "KBChatSessionResetModel.h"
|
||||||
|
#import "KBAICompanionDetailModel.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@@ -116,12 +117,12 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu
|
|||||||
/// @param content 评论内容
|
/// @param content 评论内容
|
||||||
/// @param parentId 父评论 ID(一级评论传 NULL)
|
/// @param parentId 父评论 ID(一级评论传 NULL)
|
||||||
/// @param rootId 根评论 ID(用于标识一级评论)
|
/// @param rootId 根评论 ID(用于标识一级评论)
|
||||||
/// @param completion 完成回调(返回 code 200 表示成功)
|
/// @param completion 完成回调(newItem 可能为空,取决于后端是否返回 data)
|
||||||
- (void)addCommentWithCompanionId:(NSInteger)companionId
|
- (void)addCommentWithCompanionId:(NSInteger)companionId
|
||||||
content:(NSString *)content
|
content:(NSString *)content
|
||||||
parentId:(nullable NSNumber *)parentId
|
parentId:(nullable NSNumber *)parentId
|
||||||
rootId:(NSInteger)rootId
|
rootId:(nullable NSNumber *)rootId
|
||||||
completion:(void(^)(NSInteger code, NSError * _Nullable error))completion;
|
completion:(void(^)(KBCommentItem * _Nullable newItem, NSInteger code, NSError * _Nullable error))completion;
|
||||||
|
|
||||||
/// 分页查询评论列表
|
/// 分页查询评论列表
|
||||||
/// @param companionId AI 陪聊角色 ID
|
/// @param companionId AI 陪聊角色 ID
|
||||||
|
|||||||
@@ -435,14 +435,14 @@ autoShowBusinessError:NO
|
|||||||
- (void)addCommentWithCompanionId:(NSInteger)companionId
|
- (void)addCommentWithCompanionId:(NSInteger)companionId
|
||||||
content:(NSString *)content
|
content:(NSString *)content
|
||||||
parentId:(nullable NSNumber *)parentId
|
parentId:(nullable NSNumber *)parentId
|
||||||
rootId:(NSInteger)rootId
|
rootId:(nullable NSNumber *)rootId
|
||||||
completion:(void (^)(NSInteger, NSError * _Nullable))completion {
|
completion:(void (^)(KBCommentItem * _Nullable, NSInteger, NSError * _Nullable))completion {
|
||||||
if (content.length == 0) {
|
if (content.length == 0) {
|
||||||
NSError *error = [NSError errorWithDomain:@"AiVM"
|
NSError *error = [NSError errorWithDomain:@"AiVM"
|
||||||
code:-1
|
code:-1
|
||||||
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
|
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
|
||||||
if (completion) {
|
if (completion) {
|
||||||
completion(-1, error);
|
completion(nil, -1, error);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -450,7 +450,9 @@ autoShowBusinessError:NO
|
|||||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||||
params[@"companionId"] = @(companionId);
|
params[@"companionId"] = @(companionId);
|
||||||
params[@"content"] = content;
|
params[@"content"] = content;
|
||||||
params[@"rootId"] = @(rootId);
|
if (rootId) {
|
||||||
|
params[@"rootId"] = rootId;
|
||||||
|
}
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
params[@"parentId"] = parentId;
|
params[@"parentId"] = parentId;
|
||||||
}
|
}
|
||||||
@@ -467,15 +469,40 @@ autoShowBusinessError:NO
|
|||||||
if (error) {
|
if (error) {
|
||||||
NSLog(@"[AiVM] /ai-companion/comment/add failed: %@", error.localizedDescription ?: @"");
|
NSLog(@"[AiVM] /ai-companion/comment/add failed: %@", error.localizedDescription ?: @"");
|
||||||
if (completion) {
|
if (completion) {
|
||||||
completion(-1, error);
|
completion(nil, -1, error);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
|
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
|
||||||
NSInteger code = [json[@"code"] integerValue];
|
NSInteger code = [json[@"code"] integerValue];
|
||||||
|
if (code != 0) {
|
||||||
|
NSString *message = json[@"message"] ?: @"请求失败";
|
||||||
|
NSError *bizError = [NSError errorWithDomain:@"AiVM"
|
||||||
|
code:code
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: message}];
|
||||||
if (completion) {
|
if (completion) {
|
||||||
completion(code, nil);
|
completion(nil, code, bizError);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KBCommentItem *newItem = nil;
|
||||||
|
id dataObj = json[@"data"];
|
||||||
|
if ([dataObj isKindOfClass:[NSDictionary class]]) {
|
||||||
|
newItem = [KBCommentItem mj_objectWithKeyValues:(NSDictionary *)dataObj];
|
||||||
|
} else if ([dataObj isKindOfClass:[NSNumber class]]) {
|
||||||
|
KBCommentItem *tmp = [[KBCommentItem alloc] init];
|
||||||
|
tmp.commentId = [(NSNumber *)dataObj integerValue];
|
||||||
|
tmp.companionId = companionId;
|
||||||
|
tmp.content = content;
|
||||||
|
tmp.parentId = parentId;
|
||||||
|
tmp.rootId = rootId;
|
||||||
|
newItem = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completion) {
|
||||||
|
completion(newItem, code, nil);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user