添加语音websocket等,还没测试
This commit is contained in:
296
keyBoard/Class/AiTalk/V/KBAiChatView.m
Normal file
296
keyBoard/Class/AiTalk/V/KBAiChatView.m
Normal file
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// KBAiChatView.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2026/1/15.
|
||||
//
|
||||
|
||||
#import "KBAiChatView.h"
|
||||
|
||||
#pragma mark - KBAiChatMessage
|
||||
|
||||
@implementation KBAiChatMessage
|
||||
|
||||
+ (instancetype)userMessageWithText:(NSString *)text {
|
||||
KBAiChatMessage *message = [[KBAiChatMessage alloc] init];
|
||||
message.type = KBAiChatMessageTypeUser;
|
||||
message.text = text;
|
||||
message.isComplete = YES;
|
||||
return message;
|
||||
}
|
||||
|
||||
+ (instancetype)assistantMessageWithText:(NSString *)text {
|
||||
KBAiChatMessage *message = [[KBAiChatMessage alloc] init];
|
||||
message.type = KBAiChatMessageTypeAssistant;
|
||||
message.text = text;
|
||||
message.isComplete = NO;
|
||||
return message;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - KBAiChatBubbleCell
|
||||
|
||||
@interface KBAiChatBubbleCell : UITableViewCell
|
||||
@property(nonatomic, strong) UIView *bubbleView;
|
||||
@property(nonatomic, strong) UILabel *messageLabel;
|
||||
@property(nonatomic, assign) KBAiChatMessageType messageType;
|
||||
@end
|
||||
|
||||
@implementation KBAiChatBubbleCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style
|
||||
reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
// 气泡视图
|
||||
self.bubbleView = [[UIView alloc] init];
|
||||
self.bubbleView.layer.cornerRadius = 16;
|
||||
self.bubbleView.layer.masksToBounds = YES;
|
||||
self.bubbleView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.contentView addSubview:self.bubbleView];
|
||||
|
||||
// 消息标签
|
||||
self.messageLabel = [[UILabel alloc] init];
|
||||
self.messageLabel.numberOfLines = 0;
|
||||
self.messageLabel.font = [UIFont systemFontOfSize:16];
|
||||
self.messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.bubbleView addSubview:self.messageLabel];
|
||||
|
||||
// 消息标签约束
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.messageLabel.topAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.topAnchor
|
||||
constant:10],
|
||||
[self.messageLabel.bottomAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.bottomAnchor
|
||||
constant:-10],
|
||||
[self.messageLabel.leadingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.leadingAnchor
|
||||
constant:12],
|
||||
[self.messageLabel.trailingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.trailingAnchor
|
||||
constant:-12],
|
||||
]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configureWithMessage:(KBAiChatMessage *)message {
|
||||
self.messageLabel.text = message.text;
|
||||
self.messageType = message.type;
|
||||
|
||||
// 移除旧约束
|
||||
for (NSLayoutConstraint *constraint in self.bubbleView.constraints) {
|
||||
if (constraint.firstAttribute == NSLayoutAttributeWidth) {
|
||||
constraint.active = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据消息类型设置样式
|
||||
if (message.type == KBAiChatMessageTypeUser) {
|
||||
// 用户消息:右对齐,主题色背景
|
||||
self.bubbleView.backgroundColor = [UIColor systemBlueColor];
|
||||
self.messageLabel.textColor = [UIColor whiteColor];
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:self.bubbleView.constraints];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.bubbleView.topAnchor
|
||||
constraintEqualToAnchor:self.contentView.topAnchor
|
||||
constant:4],
|
||||
[self.bubbleView.bottomAnchor
|
||||
constraintEqualToAnchor:self.contentView.bottomAnchor
|
||||
constant:-4],
|
||||
[self.bubbleView.trailingAnchor
|
||||
constraintEqualToAnchor:self.contentView.trailingAnchor
|
||||
constant:-16],
|
||||
[self.bubbleView.widthAnchor
|
||||
constraintLessThanOrEqualToAnchor:self.contentView.widthAnchor
|
||||
multiplier:0.75],
|
||||
|
||||
[self.messageLabel.topAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.topAnchor
|
||||
constant:10],
|
||||
[self.messageLabel.bottomAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.bottomAnchor
|
||||
constant:-10],
|
||||
[self.messageLabel.leadingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.leadingAnchor
|
||||
constant:12],
|
||||
[self.messageLabel.trailingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.trailingAnchor
|
||||
constant:-12],
|
||||
]];
|
||||
} else {
|
||||
// AI 消息:左对齐,浅灰色背景
|
||||
self.bubbleView.backgroundColor = [UIColor systemGray5Color];
|
||||
self.messageLabel.textColor = [UIColor labelColor];
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:self.bubbleView.constraints];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.bubbleView.topAnchor
|
||||
constraintEqualToAnchor:self.contentView.topAnchor
|
||||
constant:4],
|
||||
[self.bubbleView.bottomAnchor
|
||||
constraintEqualToAnchor:self.contentView.bottomAnchor
|
||||
constant:-4],
|
||||
[self.bubbleView.leadingAnchor
|
||||
constraintEqualToAnchor:self.contentView.leadingAnchor
|
||||
constant:16],
|
||||
[self.bubbleView.widthAnchor
|
||||
constraintLessThanOrEqualToAnchor:self.contentView.widthAnchor
|
||||
multiplier:0.75],
|
||||
|
||||
[self.messageLabel.topAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.topAnchor
|
||||
constant:10],
|
||||
[self.messageLabel.bottomAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.bottomAnchor
|
||||
constant:-10],
|
||||
[self.messageLabel.leadingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.leadingAnchor
|
||||
constant:12],
|
||||
[self.messageLabel.trailingAnchor
|
||||
constraintEqualToAnchor:self.bubbleView.trailingAnchor
|
||||
constant:-12],
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - KBAiChatView
|
||||
|
||||
@interface KBAiChatView () <UITableViewDataSource, UITableViewDelegate>
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) NSMutableArray<KBAiChatMessage *> *messages;
|
||||
@end
|
||||
|
||||
@implementation KBAiChatView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self setup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setup {
|
||||
self.messages = [[NSMutableArray alloc] init];
|
||||
|
||||
self.tableView = [[UITableView alloc] initWithFrame:self.bounds
|
||||
style:UITableViewStylePlain];
|
||||
self.tableView.autoresizingMask =
|
||||
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
self.tableView.dataSource = self;
|
||||
self.tableView.delegate = self;
|
||||
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.tableView.backgroundColor = [UIColor clearColor];
|
||||
self.tableView.estimatedRowHeight = 60;
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension;
|
||||
[self.tableView registerClass:[KBAiChatBubbleCell class]
|
||||
forCellReuseIdentifier:@"ChatCell"];
|
||||
[self addSubview:self.tableView];
|
||||
}
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
- (void)addUserMessage:(NSString *)text {
|
||||
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
|
||||
[self.messages addObject:message];
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self scrollToBottom];
|
||||
}
|
||||
|
||||
- (void)addAssistantMessage:(NSString *)text {
|
||||
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text];
|
||||
[self.messages addObject:message];
|
||||
|
||||
[self.tableView reloadData];
|
||||
[self scrollToBottom];
|
||||
}
|
||||
|
||||
- (void)updateLastAssistantMessage:(NSString *)text {
|
||||
// 查找最后一条 AI 消息
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && !message.isComplete) {
|
||||
message.text = text;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没找到,添加新消息
|
||||
[self addAssistantMessage:text];
|
||||
}
|
||||
|
||||
- (void)markLastAssistantMessageComplete {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant) {
|
||||
message.isComplete = YES;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearMessages {
|
||||
[self.messages removeAllObjects];
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
- (void)scrollToBottom {
|
||||
if (self.messages.count == 0)
|
||||
return;
|
||||
|
||||
NSIndexPath *lastIndexPath =
|
||||
[NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0];
|
||||
[self.tableView scrollToRowAtIndexPath:lastIndexPath
|
||||
atScrollPosition:UITableViewScrollPositionBottom
|
||||
animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView
|
||||
numberOfRowsInSection:(NSInteger)section {
|
||||
return self.messages.count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView
|
||||
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBAiChatBubbleCell *cell =
|
||||
[tableView dequeueReusableCellWithIdentifier:@"ChatCell"
|
||||
forIndexPath:indexPath];
|
||||
|
||||
KBAiChatMessage *message = self.messages[indexPath.row];
|
||||
[cell configureWithMessage:message];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView
|
||||
estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 60;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user