2
This commit is contained in:
@@ -146,6 +146,14 @@
|
||||
048FFD242F28A836005D62AE /* KBChatLimitPopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD232F28A836005D62AE /* KBChatLimitPopView.m */; };
|
||||
048FFD272F28C6CF005D62AE /* KBImagePositionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD262F28C6CF005D62AE /* KBImagePositionButton.m */; };
|
||||
048FFD2A2F28E99A005D62AE /* KBCommentModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD292F28E99A005D62AE /* KBCommentModel.m */; };
|
||||
048FFD2D2F29F356005D62AE /* KBAIMessageVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */; };
|
||||
048FFD302F29F3C3005D62AE /* KBAIMessageZanVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */; };
|
||||
048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */; };
|
||||
048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD362F29F400005D62AE /* KBAIMessageListVC.m */; };
|
||||
048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD352F29F88E005D62AE /* AIMessageVM.m */; };
|
||||
048FFD372F29F410005D62AE /* KBAIMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD392F29F410005D62AE /* KBAIMessageCell.m */; };
|
||||
048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */; };
|
||||
048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */; };
|
||||
0498BD622EDFFC12006CC1D5 /* KBMyVM.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD612EDFFC12006CC1D5 /* KBMyVM.m */; };
|
||||
0498BD652EE0116D006CC1D5 /* KBEmailLoginVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD642EE0116D006CC1D5 /* KBEmailLoginVC.m */; };
|
||||
0498BD682EE01180006CC1D5 /* KBEmailRegistVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0498BD672EE01180006CC1D5 /* KBEmailRegistVC.m */; };
|
||||
@@ -550,6 +558,22 @@
|
||||
048FFD262F28C6CF005D62AE /* KBImagePositionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBImagePositionButton.m; sourceTree = "<group>"; };
|
||||
048FFD282F28E99A005D62AE /* KBCommentModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCommentModel.h; sourceTree = "<group>"; };
|
||||
048FFD292F28E99A005D62AE /* KBCommentModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCommentModel.m; sourceTree = "<group>"; };
|
||||
048FFD2B2F29F356005D62AE /* KBAIMessageVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageVC.h; sourceTree = "<group>"; };
|
||||
048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageVC.m; sourceTree = "<group>"; };
|
||||
048FFD2E2F29F3C3005D62AE /* KBAIMessageZanVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageZanVC.h; sourceTree = "<group>"; };
|
||||
048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageZanVC.m; sourceTree = "<group>"; };
|
||||
048FFD312F29F3D2005D62AE /* KBAIMessageChatingVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageChatingVC.h; sourceTree = "<group>"; };
|
||||
048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageChatingVC.m; sourceTree = "<group>"; };
|
||||
048FFD342F29F88E005D62AE /* AIMessageVM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AIMessageVM.h; sourceTree = "<group>"; };
|
||||
048FFD352F29F400005D62AE /* KBAIMessageListVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageListVC.h; sourceTree = "<group>"; };
|
||||
048FFD352F29F88E005D62AE /* AIMessageVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AIMessageVM.m; sourceTree = "<group>"; };
|
||||
048FFD362F29F400005D62AE /* KBAIMessageListVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageListVC.m; sourceTree = "<group>"; };
|
||||
048FFD382F29F410005D62AE /* KBAIMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAIMessageCell.h; sourceTree = "<group>"; };
|
||||
048FFD392F29F410005D62AE /* KBAIMessageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAIMessageCell.m; sourceTree = "<group>"; };
|
||||
048FFD3A2F29F500005D62AE /* KBLikedCompanionModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLikedCompanionModel.h; sourceTree = "<group>"; };
|
||||
048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLikedCompanionModel.m; sourceTree = "<group>"; };
|
||||
048FFD3D2F29F600005D62AE /* KBChattedCompanionModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBChattedCompanionModel.h; sourceTree = "<group>"; };
|
||||
048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBChattedCompanionModel.m; 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>"; };
|
||||
0498BD612EDFFC12006CC1D5 /* KBMyVM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyVM.m; sourceTree = "<group>"; };
|
||||
@@ -1011,6 +1035,10 @@
|
||||
048FFD1C2F277486005D62AE /* KBChatHistoryPageModel.m */,
|
||||
048FFD282F28E99A005D62AE /* KBCommentModel.h */,
|
||||
048FFD292F28E99A005D62AE /* KBCommentModel.m */,
|
||||
048FFD3A2F29F500005D62AE /* KBLikedCompanionModel.h */,
|
||||
048FFD3B2F29F500005D62AE /* KBLikedCompanionModel.m */,
|
||||
048FFD3D2F29F600005D62AE /* KBChattedCompanionModel.h */,
|
||||
048FFD3E2F29F600005D62AE /* KBChattedCompanionModel.m */,
|
||||
);
|
||||
path = M;
|
||||
sourceTree = "<group>";
|
||||
@@ -1049,6 +1077,8 @@
|
||||
048FFD172F2763A5005D62AE /* KBVoiceInputBar.m */,
|
||||
048FFD222F28A836005D62AE /* KBChatLimitPopView.h */,
|
||||
048FFD232F28A836005D62AE /* KBChatLimitPopView.m */,
|
||||
048FFD382F29F410005D62AE /* KBAIMessageCell.h */,
|
||||
048FFD392F29F410005D62AE /* KBAIMessageCell.m */,
|
||||
);
|
||||
path = V;
|
||||
sourceTree = "<group>";
|
||||
@@ -1060,6 +1090,14 @@
|
||||
046086722F191B6900757C95 /* KBAiMainVC.m */,
|
||||
048FFD092F273BFC005D62AE /* KBAIHomeVC.h */,
|
||||
048FFD0A2F273BFC005D62AE /* KBAIHomeVC.m */,
|
||||
048FFD2B2F29F356005D62AE /* KBAIMessageVC.h */,
|
||||
048FFD2C2F29F356005D62AE /* KBAIMessageVC.m */,
|
||||
048FFD2E2F29F3C3005D62AE /* KBAIMessageZanVC.h */,
|
||||
048FFD2F2F29F3C3005D62AE /* KBAIMessageZanVC.m */,
|
||||
048FFD312F29F3D2005D62AE /* KBAIMessageChatingVC.h */,
|
||||
048FFD322F29F3D2005D62AE /* KBAIMessageChatingVC.m */,
|
||||
048FFD352F29F400005D62AE /* KBAIMessageListVC.h */,
|
||||
048FFD362F29F400005D62AE /* KBAIMessageListVC.m */,
|
||||
);
|
||||
path = VC;
|
||||
sourceTree = "<group>";
|
||||
@@ -1101,6 +1139,8 @@
|
||||
04E038E72F20E877002CA5A0 /* DeepgramWebSocketClient.m */,
|
||||
04E038ED2F21F0EC002CA5A0 /* AiVM.h */,
|
||||
04E038EE2F21F0EC002CA5A0 /* AiVM.m */,
|
||||
048FFD342F29F88E005D62AE /* AIMessageVM.h */,
|
||||
048FFD352F29F88E005D62AE /* AIMessageVM.m */,
|
||||
);
|
||||
path = VM;
|
||||
sourceTree = "<group>";
|
||||
@@ -2295,6 +2335,11 @@
|
||||
048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */,
|
||||
046086732F191B6900757C95 /* KBAiMainVC.m in Sources */,
|
||||
048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */,
|
||||
048FFD332F29F3D2005D62AE /* KBAIMessageChatingVC.m in Sources */,
|
||||
048FFD342F29F400005D62AE /* KBAIMessageListVC.m in Sources */,
|
||||
048FFD372F29F410005D62AE /* KBAIMessageCell.m in Sources */,
|
||||
048FFD3C2F29F500005D62AE /* KBLikedCompanionModel.m in Sources */,
|
||||
048FFD3F2F29F600005D62AE /* KBChattedCompanionModel.m in Sources */,
|
||||
04791F952ED48028004E8522 /* KBFeedBackVC.m in Sources */,
|
||||
04890A042EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m in Sources */,
|
||||
04890A052EC0BBBB00FABA60 /* KBCategoryTitleImageView.m in Sources */,
|
||||
@@ -2326,6 +2371,7 @@
|
||||
0450AC162EF11E4400B6AF06 /* StoreKitServiceDelegate.swift in Sources */,
|
||||
048FFD1D2F277486005D62AE /* KBChatHistoryPageModel.m in Sources */,
|
||||
048FFD1E2F277486005D62AE /* KBChatHistoryModel.m in Sources */,
|
||||
048FFD302F29F3C3005D62AE /* KBAIMessageZanVC.m in Sources */,
|
||||
0450AC172EF11E4400B6AF06 /* StoreKitState.swift in Sources */,
|
||||
0450AC1B2EF11E4400B6AF06 /* KBStoreKitBridge.swift in Sources */,
|
||||
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */,
|
||||
@@ -2342,6 +2388,7 @@
|
||||
A1F0C1C32FABCDEF12345678 /* KBInviteCodeModel.m in Sources */,
|
||||
A1F0C1D32FACAD0012345678 /* KBMaiPointReporter.m in Sources */,
|
||||
471CAD3574798685B72ADD55 /* KBMyTheme.m in Sources */,
|
||||
048FFD2D2F29F356005D62AE /* KBAIMessageVC.m in Sources */,
|
||||
A1F0C1B12F1234567890ABCD /* KBConsumptionRecord.m in Sources */,
|
||||
A1F0C1B22F1234567890ABCD /* KBConsumptionRecordCell.m in Sources */,
|
||||
A1F0C1B32F1234567890ABCD /* KBConsumptionRecordVC.m in Sources */,
|
||||
@@ -2379,6 +2426,7 @@
|
||||
04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */,
|
||||
04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */,
|
||||
048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */,
|
||||
048FFD362F29F88E005D62AE /* AIMessageVM.m in Sources */,
|
||||
048908F22EC047FD00FABA60 /* KBShopHeadView.m in Sources */,
|
||||
0498BD742EE02E3D006CC1D5 /* KBRegistVerEmailVC.m in Sources */,
|
||||
049FB2292EC31BB000FAB05D /* KBChangeNicknamePopView.m in Sources */,
|
||||
|
||||
22
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json
vendored
Normal file
22
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "ai_message_icon@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ai_message_icon@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@2x.png
vendored
Normal file
BIN
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@3x.png
vendored
Normal file
BIN
keyBoard/Assets.xcassets/AI/ai_message_icon.imageset/ai_message_icon@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
54
keyBoard/Class/AiTalk/M/KBChattedCompanionModel.h
Normal file
54
keyBoard/Class/AiTalk/M/KBChattedCompanionModel.h
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// KBChattedCompanionModel.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 聊过天的 AI 角色模型(Chatting 列表)
|
||||
@interface KBChattedCompanionModel : NSObject
|
||||
|
||||
/// 角色 ID
|
||||
@property (nonatomic, assign) NSInteger companionId;
|
||||
/// 角色名称
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
/// 头像 URL
|
||||
@property (nonatomic, copy) NSString *avatarUrl;
|
||||
/// 封面图 URL
|
||||
@property (nonatomic, copy) NSString *coverImageUrl;
|
||||
/// 性别
|
||||
@property (nonatomic, copy) NSString *gender;
|
||||
/// 年龄范围
|
||||
@property (nonatomic, copy) NSString *ageRange;
|
||||
/// 简短描述
|
||||
@property (nonatomic, copy) NSString *shortDesc;
|
||||
/// 介绍文本
|
||||
@property (nonatomic, copy) NSString *introText;
|
||||
/// 性格标签
|
||||
@property (nonatomic, copy) NSString *personalityTags;
|
||||
/// 说话风格
|
||||
@property (nonatomic, copy) NSString *speakingStyle;
|
||||
/// 排序
|
||||
@property (nonatomic, assign) NSInteger sortOrder;
|
||||
/// 热度分数
|
||||
@property (nonatomic, assign) NSInteger popularityScore;
|
||||
/// 开场白
|
||||
@property (nonatomic, copy) NSString *prologue;
|
||||
/// 开场白音频
|
||||
@property (nonatomic, copy) NSString *prologueAudio;
|
||||
/// 点赞数
|
||||
@property (nonatomic, assign) NSInteger likeCount;
|
||||
/// 评论数
|
||||
@property (nonatomic, assign) NSInteger commentCount;
|
||||
/// 是否已点赞
|
||||
@property (nonatomic, assign) BOOL liked;
|
||||
/// 创建时间
|
||||
@property (nonatomic, copy) NSString *createdAt;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
19
keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m
Normal file
19
keyBoard/Class/AiTalk/M/KBChattedCompanionModel.m
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// KBChattedCompanionModel.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBChattedCompanionModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@implementation KBChattedCompanionModel
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{
|
||||
@"companionId": @"id"
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
54
keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h
Normal file
54
keyBoard/Class/AiTalk/M/KBLikedCompanionModel.h
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// KBLikedCompanionModel.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 点赞过的 AI 角色模型(Thumbs Up 列表)
|
||||
@interface KBLikedCompanionModel : NSObject
|
||||
|
||||
/// 角色 ID
|
||||
@property (nonatomic, assign) NSInteger companionId;
|
||||
/// 角色名称
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
/// 头像 URL
|
||||
@property (nonatomic, copy) NSString *avatarUrl;
|
||||
/// 封面图 URL
|
||||
@property (nonatomic, copy) NSString *coverImageUrl;
|
||||
/// 性别
|
||||
@property (nonatomic, copy) NSString *gender;
|
||||
/// 年龄范围
|
||||
@property (nonatomic, copy) NSString *ageRange;
|
||||
/// 简短描述
|
||||
@property (nonatomic, copy) NSString *shortDesc;
|
||||
/// 介绍文本
|
||||
@property (nonatomic, copy) NSString *introText;
|
||||
/// 性格标签
|
||||
@property (nonatomic, copy) NSString *personalityTags;
|
||||
/// 说话风格
|
||||
@property (nonatomic, copy) NSString *speakingStyle;
|
||||
/// 排序
|
||||
@property (nonatomic, assign) NSInteger sortOrder;
|
||||
/// 热度分数
|
||||
@property (nonatomic, assign) NSInteger popularityScore;
|
||||
/// 开场白
|
||||
@property (nonatomic, copy) NSString *prologue;
|
||||
/// 开场白音频
|
||||
@property (nonatomic, copy) NSString *prologueAudio;
|
||||
/// 点赞数
|
||||
@property (nonatomic, assign) NSInteger likeCount;
|
||||
/// 评论数
|
||||
@property (nonatomic, assign) NSInteger commentCount;
|
||||
/// 是否已点赞
|
||||
@property (nonatomic, assign) BOOL liked;
|
||||
/// 创建时间
|
||||
@property (nonatomic, copy) NSString *createdAt;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
19
keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m
Normal file
19
keyBoard/Class/AiTalk/M/KBLikedCompanionModel.m
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// KBLikedCompanionModel.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBLikedCompanionModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@implementation KBLikedCompanionModel
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
return @{
|
||||
@"companionId": @"id"
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
47
keyBoard/Class/AiTalk/V/KBAIMessageCell.h
Normal file
47
keyBoard/Class/AiTalk/V/KBAIMessageCell.h
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// KBAIMessageCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class KBAIMessageCell;
|
||||
|
||||
@protocol KBAIMessageCellDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/// 点击删除按钮
|
||||
- (void)messageCell:(KBAIMessageCell *)cell didTapDeleteAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
@interface KBAIMessageCell : UITableViewCell
|
||||
|
||||
@property (nonatomic, weak) id<KBAIMessageCellDelegate> delegate;
|
||||
@property (nonatomic, strong) NSIndexPath *indexPath;
|
||||
|
||||
/// 头像
|
||||
@property (nonatomic, strong, readonly) UIImageView *avatarImageView;
|
||||
/// 昵称
|
||||
@property (nonatomic, strong, readonly) UILabel *nameLabel;
|
||||
/// 消息内容
|
||||
@property (nonatomic, strong, readonly) UILabel *contentLabel;
|
||||
/// 时间
|
||||
@property (nonatomic, strong, readonly) UILabel *timeLabel;
|
||||
/// 置顶图标(只显示,不做功能)
|
||||
@property (nonatomic, strong, readonly) UIImageView *pinIconView;
|
||||
|
||||
/// 配置数据
|
||||
- (void)configWithAvatar:(NSString *)avatarUrl
|
||||
name:(NSString *)name
|
||||
content:(NSString *)content
|
||||
time:(NSString *)time
|
||||
isPinned:(BOOL)isPinned;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
157
keyBoard/Class/AiTalk/V/KBAIMessageCell.m
Normal file
157
keyBoard/Class/AiTalk/V/KBAIMessageCell.m
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// KBAIMessageCell.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <SDWebImage/SDWebImage.h>
|
||||
|
||||
@interface KBAIMessageCell ()
|
||||
|
||||
@property (nonatomic, strong, readwrite) UIImageView *avatarImageView;
|
||||
@property (nonatomic, strong, readwrite) UILabel *nameLabel;
|
||||
@property (nonatomic, strong, readwrite) UILabel *contentLabel;
|
||||
@property (nonatomic, strong, readwrite) UILabel *timeLabel;
|
||||
@property (nonatomic, strong, readwrite) UIImageView *pinIconView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIMessageCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
[self setupSubviews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupSubviews {
|
||||
[self.contentView addSubview:self.avatarImageView];
|
||||
[self.contentView addSubview:self.nameLabel];
|
||||
[self.contentView addSubview:self.contentLabel];
|
||||
[self.contentView addSubview:self.timeLabel];
|
||||
[self.contentView addSubview:self.pinIconView];
|
||||
|
||||
[self.avatarImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView).offset(16);
|
||||
make.centerY.equalTo(self.contentView);
|
||||
make.width.height.mas_equalTo(50);
|
||||
}];
|
||||
|
||||
[self.nameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.avatarImageView.mas_right).offset(12);
|
||||
make.top.equalTo(self.avatarImageView).offset(2);
|
||||
make.right.lessThanOrEqualTo(self.timeLabel.mas_left).offset(-8);
|
||||
}];
|
||||
|
||||
[self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.nameLabel);
|
||||
make.top.equalTo(self.nameLabel.mas_bottom).offset(4);
|
||||
make.right.lessThanOrEqualTo(self.timeLabel.mas_left).offset(-8);
|
||||
}];
|
||||
|
||||
[self.pinIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.top.equalTo(self.nameLabel);
|
||||
make.width.height.mas_equalTo(16);
|
||||
}];
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.bottom.equalTo(self.contentLabel);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)configWithAvatar:(NSString *)avatarUrl
|
||||
name:(NSString *)name
|
||||
content:(NSString *)content
|
||||
time:(NSString *)time
|
||||
isPinned:(BOOL)isPinned {
|
||||
if (avatarUrl.length > 0) {
|
||||
[self.avatarImageView sd_setImageWithURL:[NSURL URLWithString:avatarUrl]
|
||||
placeholderImage:KBAvatarPlaceholderImage];
|
||||
} else {
|
||||
self.avatarImageView.image = KBAvatarPlaceholderImage;
|
||||
}
|
||||
self.nameLabel.text = name;
|
||||
self.contentLabel.text = content;
|
||||
self.timeLabel.text = time;
|
||||
self.pinIconView.hidden = !isPinned;
|
||||
|
||||
// 如果有置顶图标,时间标签需要调整位置
|
||||
if (isPinned) {
|
||||
[self.timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.bottom.equalTo(self.contentLabel);
|
||||
}];
|
||||
} else {
|
||||
[self.timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.contentView).offset(-16);
|
||||
make.bottom.equalTo(self.contentLabel);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (UIImageView *)avatarImageView {
|
||||
if (!_avatarImageView) {
|
||||
_avatarImageView = [[UIImageView alloc] init];
|
||||
_avatarImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_avatarImageView.layer.cornerRadius = 25;
|
||||
_avatarImageView.layer.masksToBounds = YES;
|
||||
_avatarImageView.backgroundColor = [UIColor colorWithHex:0xF5F5F5];
|
||||
}
|
||||
return _avatarImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)nameLabel {
|
||||
if (!_nameLabel) {
|
||||
_nameLabel = [[UILabel alloc] init];
|
||||
_nameLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
|
||||
_nameLabel.textColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
}
|
||||
return _nameLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)contentLabel {
|
||||
if (!_contentLabel) {
|
||||
_contentLabel = [[UILabel alloc] init];
|
||||
_contentLabel.font = [UIFont systemFontOfSize:14];
|
||||
_contentLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
_contentLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
}
|
||||
return _contentLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)timeLabel {
|
||||
if (!_timeLabel) {
|
||||
_timeLabel = [[UILabel alloc] init];
|
||||
_timeLabel.font = [UIFont systemFontOfSize:12];
|
||||
_timeLabel.textColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
_timeLabel.textAlignment = NSTextAlignmentRight;
|
||||
}
|
||||
return _timeLabel;
|
||||
}
|
||||
|
||||
- (UIImageView *)pinIconView {
|
||||
if (!_pinIconView) {
|
||||
_pinIconView = [[UIImageView alloc] init];
|
||||
_pinIconView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
// 使用系统图标或自定义图标
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_pinIconView.image = [UIImage systemImageNamed:@"pin.fill"];
|
||||
_pinIconView.tintColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
}
|
||||
_pinIconView.hidden = YES;
|
||||
}
|
||||
return _pinIconView;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -17,6 +17,7 @@
|
||||
#import "KBVipPay.h"
|
||||
#import "KBUserSessionManager.h"
|
||||
#import "LSTPopView.h"
|
||||
#import "KBAIMessageVC.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface KBAIHomeVC () <UICollectionViewDelegate, UICollectionViewDataSource, KBVoiceToTextManagerDelegate, KBVoiceRecordManagerDelegate, UIGestureRecognizerDelegate, KBChatLimitPopViewDelegate>
|
||||
@@ -68,6 +69,9 @@
|
||||
/// 是否正在等待 AI 回复(用于禁止滚动)
|
||||
@property (nonatomic, assign) BOOL isWaitingForAIResponse;
|
||||
|
||||
/// 右上角消息按钮
|
||||
@property (nonatomic, strong) UIButton *messageButton;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIHomeVC
|
||||
@@ -113,6 +117,14 @@
|
||||
make.edges.equalTo(self.view);
|
||||
}];
|
||||
|
||||
// 右上角消息按钮
|
||||
[self.view addSubview:self.messageButton];
|
||||
[self.messageButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.view).offset(KB_STATUSBAR_HEIGHT + 10);
|
||||
make.right.equalTo(self.view).offset(-16);
|
||||
make.width.height.mas_equalTo(32);
|
||||
}];
|
||||
|
||||
// 底部毛玻璃背景
|
||||
[self.view addSubview:self.bottomBackgroundView];
|
||||
[self.bottomBackgroundView addSubview:self.bottomBlurEffectView];
|
||||
@@ -540,6 +552,22 @@
|
||||
return _bottomMaskLayer;
|
||||
}
|
||||
|
||||
- (UIButton *)messageButton {
|
||||
if (!_messageButton) {
|
||||
_messageButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_messageButton setImage:[UIImage imageNamed:@"ai_message_icon"] forState:UIControlStateNormal];
|
||||
[_messageButton addTarget:self action:@selector(messageButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _messageButton;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)messageButtonTapped {
|
||||
KBAIMessageVC *vc = [[KBAIMessageVC alloc] init];
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - KBVoiceToTextManagerDelegate
|
||||
|
||||
- (void)voiceToTextManager:(KBVoiceToTextManager *)manager
|
||||
|
||||
17
keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h
Normal file
17
keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// KBAIMessageChatingVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageListVC.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Chatting 消息列表
|
||||
@interface KBAIMessageChatingVC : KBAIMessageListVC
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
99
keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m
Normal file
99
keyBoard/Class/AiTalk/VC/KBAIMessageChatingVC.m
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// KBAIMessageChatingVC.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageChatingVC.h"
|
||||
#import "AiVM.h"
|
||||
#import "KBChattedCompanionModel.h"
|
||||
#import "KBHUD.h"
|
||||
|
||||
@interface KBAIMessageChatingVC ()
|
||||
|
||||
@property (nonatomic, strong) AiVM *viewModel;
|
||||
@property (nonatomic, strong) NSMutableArray<KBChattedCompanionModel *> *chattedList;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIMessageChatingVC
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
self.listType = 1; // Chatting
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
#pragma mark - 2:数据加载
|
||||
|
||||
- (void)loadData {
|
||||
[KBHUD show];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.viewModel fetchChattedCompanionsWithCompletion:^(NSArray<KBChattedCompanionModel *> * _Nullable list, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[KBHUD dismiss];
|
||||
if (error) {
|
||||
[KBHUD showError:error.localizedDescription];
|
||||
return;
|
||||
}
|
||||
|
||||
[weakSelf.chattedList removeAllObjects];
|
||||
if (list.count > 0) {
|
||||
[weakSelf.chattedList addObjectsFromArray:list];
|
||||
}
|
||||
[weakSelf.dataArray removeAllObjects];
|
||||
|
||||
// 转换为通用数据格式
|
||||
for (KBChattedCompanionModel *model in weakSelf.chattedList) {
|
||||
NSMutableDictionary *item = [NSMutableDictionary dictionary];
|
||||
item[@"avatar"] = model.avatarUrl ?: @"";
|
||||
item[@"name"] = model.name ?: @"";
|
||||
item[@"content"] = model.shortDesc ?: @"";
|
||||
item[@"time"] = model.createdAt ?: @"";
|
||||
item[@"isPinned"] = @NO;
|
||||
item[@"companionId"] = @(model.companionId);
|
||||
[weakSelf.dataArray addObject:item];
|
||||
}
|
||||
|
||||
[weakSelf.tableView reloadData];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 删除
|
||||
|
||||
- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row >= self.chattedList.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 如果有删除聊天记录的接口,在这里调用
|
||||
// 目前先只做本地删除
|
||||
if (indexPath.row < self.chattedList.count) {
|
||||
[self.chattedList removeObjectAtIndex:indexPath.row];
|
||||
}
|
||||
if (indexPath.row < self.dataArray.count) {
|
||||
[self.dataArray removeObjectAtIndex:indexPath.row];
|
||||
}
|
||||
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (AiVM *)viewModel {
|
||||
if (!_viewModel) {
|
||||
_viewModel = [[AiVM alloc] init];
|
||||
}
|
||||
return _viewModel;
|
||||
}
|
||||
|
||||
- (NSMutableArray<KBChattedCompanionModel *> *)chattedList {
|
||||
if (!_chattedList) {
|
||||
_chattedList = [NSMutableArray array];
|
||||
}
|
||||
return _chattedList;
|
||||
}
|
||||
|
||||
@end
|
||||
33
keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h
Normal file
33
keyBoard/Class/AiTalk/VC/KBAIMessageListVC.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// KBAIMessageListVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <JXCategoryView/JXCategoryListContainerView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 消息列表基类,供 ZanVC 和 ChatingVC 继承
|
||||
@interface KBAIMessageListVC : UIViewController <JXCategoryListContentViewDelegate>
|
||||
|
||||
/// 列表类型:0 = Thumbs Up, 1 = Chatting
|
||||
@property (nonatomic, assign) NSInteger listType;
|
||||
|
||||
/// 数据源
|
||||
@property (nonatomic, strong) NSMutableArray *dataArray;
|
||||
|
||||
/// TableView
|
||||
@property (nonatomic, strong) UITableView *tableView;
|
||||
|
||||
/// 加载数据(子类重写)
|
||||
- (void)loadData;
|
||||
|
||||
/// 删除某条数据(子类重写)
|
||||
- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
142
keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m
Normal file
142
keyBoard/Class/AiTalk/VC/KBAIMessageListVC.m
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// KBAIMessageListVC.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageListVC.h"
|
||||
#import "KBAIMessageCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface KBAIMessageListVC () <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIMessageListVC
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
/// 1:控件初始化
|
||||
[self setupUI];
|
||||
|
||||
/// 2:加载数据
|
||||
[self loadData];
|
||||
}
|
||||
|
||||
#pragma mark - 1:控件初始化
|
||||
|
||||
- (void)setupUI {
|
||||
[self.view addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 2:数据加载
|
||||
|
||||
- (void)loadData {
|
||||
// 子类重写
|
||||
}
|
||||
|
||||
- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// 子类重写
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.dataArray.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBAIMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KBAIMessageCell" forIndexPath:indexPath];
|
||||
cell.indexPath = indexPath;
|
||||
|
||||
// 子类配置数据
|
||||
if (indexPath.row < self.dataArray.count) {
|
||||
NSDictionary *item = self.dataArray[indexPath.row];
|
||||
[cell configWithAvatar:item[@"avatar"]
|
||||
name:item[@"name"]
|
||||
content:item[@"content"]
|
||||
time:item[@"time"]
|
||||
isPinned:[item[@"isPinned"] boolValue]];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 76;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
// 子类处理点击事件
|
||||
}
|
||||
|
||||
/// 左滑删除
|
||||
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) {
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
|
||||
// 删除按钮
|
||||
UIContextualAction *deleteAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
|
||||
title:nil
|
||||
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
|
||||
[weakSelf deleteItemAtIndexPath:indexPath];
|
||||
completionHandler(YES);
|
||||
}];
|
||||
deleteAction.backgroundColor = [UIColor colorWithHex:0xF44336];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
deleteAction.image = [UIImage systemImageNamed:@"trash.fill"];
|
||||
}
|
||||
|
||||
UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteAction]];
|
||||
config.performsFirstActionWithFullSwipe = NO;
|
||||
return config;
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryListContentViewDelegate
|
||||
|
||||
- (UIView *)listView {
|
||||
return self.view;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableView.backgroundColor = [UIColor whiteColor];
|
||||
_tableView.showsVerticalScrollIndicator = NO;
|
||||
[_tableView registerClass:[KBAIMessageCell class] forCellReuseIdentifier:@"KBAIMessageCell"];
|
||||
|
||||
if (@available(iOS 15.0, *)) {
|
||||
_tableView.sectionHeaderTopPadding = 0;
|
||||
}
|
||||
}
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)dataArray {
|
||||
if (!_dataArray) {
|
||||
_dataArray = [NSMutableArray array];
|
||||
}
|
||||
return _dataArray;
|
||||
}
|
||||
|
||||
@end
|
||||
17
keyBoard/Class/AiTalk/VC/KBAIMessageVC.h
Normal file
17
keyBoard/Class/AiTalk/VC/KBAIMessageVC.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// KBAIMessageVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "BaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// AI 消息主页面(Thumbs Up / Chatting 分页)
|
||||
@interface KBAIMessageVC : BaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
170
keyBoard/Class/AiTalk/VC/KBAIMessageVC.m
Normal file
170
keyBoard/Class/AiTalk/VC/KBAIMessageVC.m
Normal file
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// KBAIMessageVC.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageVC.h"
|
||||
#import <JXCategoryView/JXCategoryView.h>
|
||||
#import "KBAIMessageZanVC.h"
|
||||
#import "KBAIMessageChatingVC.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface KBAIMessageVC () <JXCategoryViewDelegate, JXCategoryListContainerViewDelegate>
|
||||
|
||||
/// 分类标签视图
|
||||
@property (nonatomic, strong) JXCategoryTitleView *categoryView;
|
||||
|
||||
/// 列表容器
|
||||
@property (nonatomic, strong) JXCategoryListContainerView *listContainerView;
|
||||
|
||||
/// 右侧搜索按钮
|
||||
@property (nonatomic, strong) UIButton *searchButton;
|
||||
|
||||
/// 标题数组
|
||||
@property (nonatomic, strong) NSArray<NSString *> *titles;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIMessageVC
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
/// 1:控件初始化
|
||||
[self setupUI];
|
||||
|
||||
/// 2:绑定事件
|
||||
[self bindActions];
|
||||
}
|
||||
|
||||
#pragma mark - 1:控件初始化
|
||||
|
||||
- (void)setupUI {
|
||||
// 隐藏默认导航栏标题
|
||||
self.kb_titleLabel.hidden = YES;
|
||||
|
||||
// 添加分类视图到导航栏位置
|
||||
[self.kb_navView addSubview:self.categoryView];
|
||||
[self.categoryView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.kb_backButton.mas_right).offset(0);
|
||||
make.centerY.equalTo(self.kb_backButton);
|
||||
make.height.mas_equalTo(44);
|
||||
make.width.mas_equalTo(180);
|
||||
}];
|
||||
|
||||
// 添加搜索按钮
|
||||
[self.kb_navView addSubview:self.searchButton];
|
||||
[self.searchButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.kb_navView).offset(-16);
|
||||
make.centerY.equalTo(self.kb_backButton);
|
||||
make.width.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// 添加列表容器
|
||||
[self.view addSubview:self.listContainerView];
|
||||
[self.listContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.kb_navView.mas_bottom);
|
||||
make.left.right.bottom.equalTo(self.view);
|
||||
}];
|
||||
|
||||
// 关联 categoryView 和 listContainerView
|
||||
self.categoryView.listContainer = self.listContainerView;
|
||||
}
|
||||
|
||||
#pragma mark - 2:绑定事件
|
||||
|
||||
- (void)bindActions {
|
||||
[self.searchButton addTarget:self action:@selector(searchButtonTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)searchButtonTapped {
|
||||
// TODO: 跳转搜索页面
|
||||
NSLog(@"搜索按钮点击");
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryViewDelegate
|
||||
|
||||
- (void)categoryView:(JXCategoryBaseView *)categoryView didSelectedItemAtIndex:(NSInteger)index {
|
||||
NSLog(@"选中分类:%@", self.titles[index]);
|
||||
}
|
||||
|
||||
#pragma mark - JXCategoryListContainerViewDelegate
|
||||
|
||||
- (NSInteger)numberOfListsInlistContainerView:(JXCategoryListContainerView *)listContainerView {
|
||||
return self.titles.count;
|
||||
}
|
||||
|
||||
- (id<JXCategoryListContentViewDelegate>)listContainerView:(JXCategoryListContainerView *)listContainerView initListForIndex:(NSInteger)index {
|
||||
if (index == 0) {
|
||||
return [[KBAIMessageZanVC alloc] init];
|
||||
} else {
|
||||
return [[KBAIMessageChatingVC alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (NSArray<NSString *> *)titles {
|
||||
if (!_titles) {
|
||||
_titles = @[KBLocalized(@"Thumbs Up"), KBLocalized(@"Chatting")];
|
||||
}
|
||||
return _titles;
|
||||
}
|
||||
|
||||
- (JXCategoryTitleView *)categoryView {
|
||||
if (!_categoryView) {
|
||||
_categoryView = [[JXCategoryTitleView alloc] init];
|
||||
_categoryView.backgroundColor = [UIColor clearColor];
|
||||
_categoryView.titles = self.titles;
|
||||
_categoryView.delegate = self;
|
||||
|
||||
// 标题样式
|
||||
_categoryView.titleFont = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
|
||||
_categoryView.titleSelectedFont = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
|
||||
_categoryView.titleColor = [UIColor colorWithHex:0x9F9F9F];
|
||||
_categoryView.titleSelectedColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
|
||||
// 不需要指示器
|
||||
_categoryView.indicators = @[];
|
||||
|
||||
// 间距设置
|
||||
_categoryView.cellSpacing = 20;
|
||||
_categoryView.contentEdgeInsetLeft = 0;
|
||||
_categoryView.contentEdgeInsetRight = 0;
|
||||
_categoryView.averageCellSpacingEnabled = NO;
|
||||
|
||||
// 禁用缩放和渐变
|
||||
_categoryView.cellWidthZoomEnabled = NO;
|
||||
_categoryView.titleColorGradientEnabled = NO;
|
||||
}
|
||||
return _categoryView;
|
||||
}
|
||||
|
||||
- (JXCategoryListContainerView *)listContainerView {
|
||||
if (!_listContainerView) {
|
||||
_listContainerView = [[JXCategoryListContainerView alloc] initWithType:JXCategoryListContainerType_ScrollView delegate:self];
|
||||
_listContainerView.scrollView.bounces = NO;
|
||||
}
|
||||
return _listContainerView;
|
||||
}
|
||||
|
||||
- (UIButton *)searchButton {
|
||||
if (!_searchButton) {
|
||||
_searchButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImage *searchImage = [UIImage systemImageNamed:@"magnifyingglass"];
|
||||
[_searchButton setImage:searchImage forState:UIControlStateNormal];
|
||||
_searchButton.tintColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
}
|
||||
}
|
||||
return _searchButton;
|
||||
}
|
||||
|
||||
@end
|
||||
17
keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h
Normal file
17
keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// KBAIMessageZanVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageListVC.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Thumbs Up 消息列表
|
||||
@interface KBAIMessageZanVC : KBAIMessageListVC
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
110
keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m
Normal file
110
keyBoard/Class/AiTalk/VC/KBAIMessageZanVC.m
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// KBAIMessageZanVC.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "KBAIMessageZanVC.h"
|
||||
#import "AiVM.h"
|
||||
#import "KBLikedCompanionModel.h"
|
||||
#import "KBHUD.h"
|
||||
|
||||
@interface KBAIMessageZanVC ()
|
||||
|
||||
@property (nonatomic, strong) AiVM *viewModel;
|
||||
@property (nonatomic, strong) NSMutableArray<KBLikedCompanionModel *> *likedList;
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBAIMessageZanVC
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
self.listType = 0; // Thumbs Up
|
||||
[super viewDidLoad];
|
||||
}
|
||||
|
||||
#pragma mark - 2:数据加载
|
||||
|
||||
- (void)loadData {
|
||||
[KBHUD show];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.viewModel fetchLikedCompanionsWithCompletion:^(NSArray<KBLikedCompanionModel *> * _Nullable list, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[KBHUD dismiss];
|
||||
if (error) {
|
||||
[KBHUD showError:error.localizedDescription];
|
||||
return;
|
||||
}
|
||||
|
||||
[weakSelf.likedList removeAllObjects];
|
||||
if (list.count > 0) {
|
||||
[weakSelf.likedList addObjectsFromArray:list];
|
||||
}
|
||||
[weakSelf.dataArray removeAllObjects];
|
||||
|
||||
// 转换为通用数据格式
|
||||
for (KBLikedCompanionModel *model in weakSelf.likedList) {
|
||||
NSMutableDictionary *item = [NSMutableDictionary dictionary];
|
||||
item[@"avatar"] = model.avatarUrl ?: @"";
|
||||
item[@"name"] = model.name ?: @"";
|
||||
item[@"content"] = model.shortDesc ?: @"";
|
||||
item[@"time"] = model.createdAt ?: @"";
|
||||
item[@"isPinned"] = @NO;
|
||||
item[@"companionId"] = @(model.companionId);
|
||||
[weakSelf.dataArray addObject:item];
|
||||
}
|
||||
|
||||
[weakSelf.tableView reloadData];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 删除
|
||||
|
||||
- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row >= self.likedList.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
KBLikedCompanionModel *model = self.likedList[indexPath.row];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.viewModel likeCompanionWithCompanionId:model.companionId completion:^(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (error) {
|
||||
[KBHUD showError:error.localizedDescription];
|
||||
return;
|
||||
}
|
||||
|
||||
// 删除本地数据
|
||||
if (indexPath.row < weakSelf.likedList.count) {
|
||||
[weakSelf.likedList removeObjectAtIndex:indexPath.row];
|
||||
}
|
||||
if (indexPath.row < weakSelf.dataArray.count) {
|
||||
[weakSelf.dataArray removeObjectAtIndex:indexPath.row];
|
||||
}
|
||||
[weakSelf.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (AiVM *)viewModel {
|
||||
if (!_viewModel) {
|
||||
_viewModel = [[AiVM alloc] init];
|
||||
}
|
||||
return _viewModel;
|
||||
}
|
||||
|
||||
- (NSMutableArray<KBLikedCompanionModel *> *)likedList {
|
||||
if (!_likedList) {
|
||||
_likedList = [NSMutableArray array];
|
||||
}
|
||||
return _likedList;
|
||||
}
|
||||
|
||||
@end
|
||||
16
keyBoard/Class/AiTalk/VM/AIMessageVM.h
Normal file
16
keyBoard/Class/AiTalk/VM/AIMessageVM.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// AIMessageVM.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AIMessageVM : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
12
keyBoard/Class/AiTalk/VM/AIMessageVM.m
Normal file
12
keyBoard/Class/AiTalk/VM/AIMessageVM.m
Normal file
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// AIMessageVM.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/28.
|
||||
//
|
||||
|
||||
#import "AIMessageVM.h"
|
||||
|
||||
@implementation AIMessageVM
|
||||
|
||||
@end
|
||||
@@ -9,6 +9,8 @@
|
||||
#import "KBPersonaPageModel.h"
|
||||
#import "KBChatHistoryPageModel.h"
|
||||
#import "KBCommentModel.h"
|
||||
#import "KBLikedCompanionModel.h"
|
||||
#import "KBChattedCompanionModel.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -142,6 +144,16 @@ typedef void (^AiVMSpeechTranscribeCompletion)(KBAiSpeechTranscribeResponse *_Nu
|
||||
- (void)likeCompanionWithCompanionId:(NSInteger)companionId
|
||||
completion:(void(^)(KBCommentLikeResponse * _Nullable response, NSError * _Nullable error))completion;
|
||||
|
||||
#pragma mark - 点赞列表接口
|
||||
|
||||
/// 获取当前用户点赞过的 AI 角色列表(Thumbs Up)
|
||||
/// @param completion 完成回调(返回点赞角色数组)
|
||||
- (void)fetchLikedCompanionsWithCompletion:(void(^)(NSArray<KBLikedCompanionModel *> * _Nullable list, NSError * _Nullable error))completion;
|
||||
|
||||
/// 获取当前用户聊过天的 AI 角色列表(Chatting)
|
||||
/// @param completion 完成回调(返回聊天角色数组)
|
||||
- (void)fetchChattedCompanionsWithCompletion:(void(^)(NSArray<KBChattedCompanionModel *> * _Nullable list, NSError * _Nullable error))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#import "KBAPI.h"
|
||||
#import "KBNetworkManager.h"
|
||||
#import "KBCommentModel.h"
|
||||
#import "KBLikedCompanionModel.h"
|
||||
#import "KBChattedCompanionModel.h"
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
@implementation KBAiSyncData
|
||||
@@ -599,4 +601,94 @@ autoShowBusinessError:NO
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - 点赞列表接口
|
||||
|
||||
- (void)fetchLikedCompanionsWithCompletion:(void (^)(NSArray<KBLikedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
|
||||
NSLog(@"[AiVM] /ai-companion/liked request");
|
||||
[[KBNetworkManager shared]
|
||||
GET:@"/ai-companion/liked"
|
||||
parameters:nil
|
||||
headers:nil
|
||||
autoShowBusinessError:NO
|
||||
completion:^(NSDictionary *_Nullable json,
|
||||
NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"[AiVM] /ai-companion/liked failed: %@", error.localizedDescription ?: @"");
|
||||
if (completion) {
|
||||
completion(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[AiVM] /ai-companion/liked response: %@", json);
|
||||
|
||||
NSInteger code = [json[@"code"] integerValue];
|
||||
if (code != 0) {
|
||||
NSString *message = json[@"message"] ?: @"请求失败";
|
||||
NSError *bizError = [NSError errorWithDomain:@"AiVM"
|
||||
code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey: message}];
|
||||
if (completion) {
|
||||
completion(nil, bizError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *dataArray = json[@"data"];
|
||||
if (![dataArray isKindOfClass:[NSArray class]]) {
|
||||
dataArray = @[];
|
||||
}
|
||||
|
||||
NSArray<KBLikedCompanionModel *> *list = [KBLikedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
|
||||
if (completion) {
|
||||
completion(list, nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fetchChattedCompanionsWithCompletion:(void (^)(NSArray<KBChattedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
|
||||
NSLog(@"[AiVM] /ai-companion/chatted request");
|
||||
[[KBNetworkManager shared]
|
||||
GET:@"/ai-companion/chatted"
|
||||
parameters:nil
|
||||
headers:nil
|
||||
autoShowBusinessError:NO
|
||||
completion:^(NSDictionary *_Nullable json,
|
||||
NSURLResponse *_Nullable response,
|
||||
NSError *_Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"[AiVM] /ai-companion/chatted failed: %@", error.localizedDescription ?: @"");
|
||||
if (completion) {
|
||||
completion(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSLog(@"[AiVM] /ai-companion/chatted response: %@", json);
|
||||
|
||||
NSInteger code = [json[@"code"] integerValue];
|
||||
if (code != 0) {
|
||||
NSString *message = json[@"message"] ?: @"请求失败";
|
||||
NSError *bizError = [NSError errorWithDomain:@"AiVM"
|
||||
code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey: message}];
|
||||
if (completion) {
|
||||
completion(nil, bizError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *dataArray = json[@"data"];
|
||||
if (![dataArray isKindOfClass:[NSArray class]]) {
|
||||
dataArray = @[];
|
||||
}
|
||||
|
||||
NSArray<KBChattedCompanionModel *> *list = [KBChattedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
|
||||
if (completion) {
|
||||
completion(list, nil);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user