This commit is contained in:
2026-01-27 21:32:52 +08:00
parent db869552e4
commit 3fd7d2af2e
6 changed files with 433 additions and 7 deletions

View File

@@ -0,0 +1,75 @@
//
// KBCommentModel.h
// keyBoard
//
// Created by Kiro on 2026/1/27.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - 评论项模型
@interface KBCommentItem : NSObject
/// 评论 ID
@property (nonatomic, assign) NSInteger commentId;
/// AI 陪聊角色 ID
@property (nonatomic, assign) NSInteger companionId;
/// 评论内容
@property (nonatomic, copy, nullable) NSString *content;
/// 创建时间
@property (nonatomic, copy, nullable) NSString *createdAt;
/// 点赞数
@property (nonatomic, assign) NSInteger likeCount;
/// 是否已点赞0=未点赞1=已点赞)
@property (nonatomic, assign) NSInteger liked;
/// 父评论 ID一级评论为 null
@property (nonatomic, strong, nullable) NSNumber *parentId;
/// 根评论 ID用于标识评论线程
@property (nonatomic, strong, nullable) NSNumber *rootId;
/// 回复列表
@property (nonatomic, strong, nullable) NSArray<KBCommentItem *> *replies;
/// 回复数量
@property (nonatomic, assign) NSInteger replyCount;
/// 用户头像
@property (nonatomic, copy, nullable) NSString *userAvatar;
/// 用户 ID
@property (nonatomic, assign) NSInteger userId;
/// 用户名
@property (nonatomic, copy, nullable) NSString *userName;
@end
#pragma mark - 评论分页模型
@interface KBCommentPageModel : NSObject
/// 当前页码
@property (nonatomic, assign) NSInteger current;
/// 总页数
@property (nonatomic, assign) NSInteger pages;
/// 每页大小
@property (nonatomic, assign) NSInteger size;
/// 总记录数
@property (nonatomic, assign) NSInteger total;
/// 评论列表
@property (nonatomic, strong, nullable) NSArray<KBCommentItem *> *records;
@end
#pragma mark - 点赞响应模型
@interface KBCommentLikeResponse : NSObject
/// 响应码0=成功)
@property (nonatomic, assign) NSInteger code;
/// 响应消息
@property (nonatomic, copy, nullable) NSString *message;
/// 点赞结果true=点赞成功false=取消点赞成功)
@property (nonatomic, assign) BOOL data;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,42 @@
//
// KBCommentModel.m
// keyBoard
//
// Created by Kiro on 2026/1/27.
//
#import "KBCommentModel.h"
#import <MJExtension/MJExtension.h>
@implementation KBCommentItem
+ (void)load {
// id OC
[self mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
return @{
@"commentId": @"id"
};
}];
}
+ (NSDictionary *)mj_objectClassInArray {
return @{
@"replies": [KBCommentItem class]
};
}
@end
@implementation KBCommentPageModel
+ (NSDictionary *)mj_objectClassInArray {
return @{
@"records": [KBCommentItem class]
};
}
@end
@implementation KBCommentLikeResponse
@end

View File

@@ -11,8 +11,10 @@
#import "KBChatHistoryPageModel.h"
#import "AiVM.h"
#import "KBImagePositionButton.h"
#import "KBAICommentView.h"
#import <Masonry/Masonry.h>
#import <SDWebImage/SDWebImage.h>
#import <LSTPopView/LSTPopView.h>
@interface KBPersonaChatCell () <KBChatTableViewDelegate>
@@ -55,6 +57,9 @@
///
@property (nonatomic, strong) KBImagePositionButton *likeButton;
///
@property (nonatomic, weak) LSTPopView *popView;
@end
@implementation KBPersonaChatCell
@@ -159,7 +164,6 @@
self.currentPage = 1;
self.hasMoreHistory = YES;
self.messages = [NSMutableArray array];
self.aiVM = [[AiVM alloc] init];
// UI
[self.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:persona.coverImageUrl]
@@ -452,17 +456,106 @@
#pragma mark - Button Actions
- (void)commentButtonTapped:(KBImagePositionButton *)sender {
sender.selected = !sender.selected;
NSLog(@"[KBPersonaChatCell] 评论按钮点击,选中状态:%d", sender.selected);
NSLog(@"[KBPersonaChatCell] 评论按钮点击");
// TODO:
//
[self showComment];
}
- (void)likeButtonTapped:(KBImagePositionButton *)sender {
sender.selected = !sender.selected;
NSLog(@"[KBPersonaChatCell] 喜欢按钮点击,选中状态:%d", sender.selected);
NSLog(@"[KBPersonaChatCell] 喜欢按钮点击");
// TODO:
NSInteger personaId = self.persona.personaId;
//
sender.enabled = NO;
__weak typeof(self) weakSelf = self;
[self.aiVM likeCompanionWithCompanionId:personaId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
//
sender.enabled = YES;
if (error) {
NSLog(@"[KBPersonaChatCell] 点赞失败:%@", error.localizedDescription);
// TODO:
return;
}
if (response && response.code == 0) {
//
NSInteger currentLikeCount = [strongSelf.persona.likeCount integerValue];
// response.data: true false
if (response.data) {
// 1
currentLikeCount += 1;
sender.selected = YES;
NSLog(@"[KBPersonaChatCell] 点赞成功,新喜欢数:%ld", (long)currentLikeCount);
} else {
// 10
currentLikeCount = MAX(0, currentLikeCount - 1);
sender.selected = NO;
NSLog(@"[KBPersonaChatCell] 取消点赞成功,新喜欢数:%ld", (long)currentLikeCount);
}
//
strongSelf.persona.likeCount = [NSString stringWithFormat:@"%ld", (long)currentLikeCount];
strongSelf.persona.liked = sender.selected;
//
[sender setTitle:strongSelf.persona.likeCount forState:UIControlStateNormal];
} else {
NSLog(@"[KBPersonaChatCell] 点赞失败:%@", response.message ?: @"未知错误");
// TODO:
}
});
}];
}
#pragma mark - Comment View
- (void)showComment {
//
if (self.popView) {
[self.popView dismiss];
}
CGFloat customViewHeight = KB_SCREEN_HEIGHT * 0.8;
KBAICommentView *customView = [[KBAICommentView alloc]
initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)];
// ID
// customView.companionId = self.persona.personaId;
LSTPopView *popView = [LSTPopView initWithCustomView:customView
parentView:nil
popStyle:LSTPopStyleSmoothFromBottom
dismissStyle:LSTDismissStyleSmoothToBottom];
self.popView = popView;
popView.priority = 1000;
popView.isAvoidKeyboard = NO;
popView.hemStyle = LSTHemStyleBottom;
popView.dragStyle = LSTDragStyleY_Positive;
popView.dragDistance = customViewHeight * 0.5;
popView.sweepStyle = LSTSweepStyleY_Positive;
popView.swipeVelocity = 1600;
popView.sweepDismissStyle = LSTSweepDismissStyleSmooth;
[popView pop];
}
- (AiVM *)aiVM{
if (!_aiVM) {
_aiVM = [[AiVM alloc] init];
}
return _aiVM;
}
@end

View File

@@ -8,6 +8,7 @@
#import <Foundation/Foundation.h>
#import "KBPersonaPageModel.h"
#import "KBChatHistoryPageModel.h"
#import "KBCommentModel.h"
NS_ASSUME_NONNULL_BEGIN
@@ -105,6 +106,42 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu
pageSize:(NSInteger)pageSize
completion:(void(^)(KBChatHistoryPageModel * _Nullable pageModel, NSError * _Nullable error))completion;
#pragma mark - 评论相关接口
/// 发表评论
/// @param companionId AI 陪聊角色 ID
/// @param content 评论内容
/// @param parentId 父评论 ID一级评论传 NULL
/// @param rootId 根评论 ID用于标识一级评论
/// @param completion 完成回调(返回 code 200 表示成功)
- (void)addCommentWithCompanionId:(NSInteger)companionId
content:(NSString *)content
parentId:(nullable NSNumber *)parentId
rootId:(NSInteger)rootId
completion:(void(^)(NSInteger code, NSError * _Nullable error))completion;
/// 分页查询评论列表
/// @param companionId AI 陪聊角色 ID
/// @param pageNum 页码(从 1 开始,默认 1
/// @param pageSize 每页大小(默认 20
/// @param completion 完成回调(返回评论分页模型)
- (void)fetchCommentsWithCompanionId:(NSInteger)companionId
pageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void(^)(KBCommentPageModel * _Nullable pageModel, NSError * _Nullable error))completion;
/// 点赞/取消点赞评论
/// @param commentId 评论 ID
/// @param completion 完成回调(返回点赞响应模型)
- (void)likeCommentWithCommentId:(NSInteger)commentId
completion:(void(^)(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error))completion;
/// 点赞/取消点赞 AI 角色
/// @param companionId AI 角色 ID
/// @param completion 完成回调(返回点赞响应模型)
- (void)likeCompanionWithCompanionId:(NSInteger)companionId
completion:(void(^)(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error))completion;
@end
NS_ASSUME_NONNULL_END

View File

@@ -8,6 +8,7 @@
#import "AiVM.h"
#import "KBAPI.h"
#import "KBNetworkManager.h"
#import "KBCommentModel.h"
#import <MJExtension/MJExtension.h>
@implementation KBAiSyncData
@@ -426,4 +427,176 @@ autoShowBusinessError:NO
}];
}
#pragma mark -
- (void)addCommentWithCompanionId:(NSInteger)companionId
content:(NSString *)content
parentId:(nullable NSNumber *)parentId
rootId:(NSInteger)rootId
completion:(void (^)(NSInteger, NSError * _Nullable))completion {
if (content.length == 0) {
NSError *error = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
if (completion) {
completion(-1, error);
}
return;
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"companionId"] = @(companionId);
params[@"content"] = content;
params[@"rootId"] = @(rootId);
if (parentId) {
params[@"parentId"] = parentId;
}
NSLog(@"[AiVM] /ai-companion/comment/add request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/add"
jsonBody:[params copy]
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/add failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(-1, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (completion) {
completion(code, nil);
}
}];
}
- (void)fetchCommentsWithCompanionId:(NSInteger)companionId
pageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBCommentPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId),
@"pageNum": @(pageNum > 0 ? pageNum : 1),
@"pageSize": @(pageSize > 0 ? pageSize : 20)
};
NSLog(@"[AiVM] /ai-companion/comment/page request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/page"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/page failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/page response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBCommentPageModel *pageModel = [KBCommentPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
- (void)likeCommentWithCommentId:(NSInteger)commentId
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"commentId": @(commentId)
};
NSLog(@"[AiVM] /ai-companion/comment/like request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/like"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/like failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/like response: %@", json);
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
if (completion) {
completion(likeResponse, nil);
}
}];
}
- (void)likeCompanionWithCompanionId:(NSInteger)companionId
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId)
};
NSLog(@"[AiVM] /ai-companion/like request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/like"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/like failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/like response: %@", json);
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
if (completion) {
completion(likeResponse, nil);
}
}];
}
@end