// // KBAiChatView.m // keyBoard // // Created by Mac on 2026/1/15. // #import "KBAiChatView.h" #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 () @property(nonatomic, strong) UITableView *tableView; @property(nonatomic, strong) NSMutableArray *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