tableview倒置
This commit is contained in:
@@ -24,6 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, weak) id<KBChatTableViewDelegate> delegate;
|
||||
|
||||
/// 是否倒置列表(倒置后:视觉底部为 row=0,历史消息向上追加)
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
|
||||
/// 添加用户消息
|
||||
- (void)addUserMessage:(NSString *)text;
|
||||
|
||||
@@ -84,6 +87,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)prependHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||
openingMessage:(nullable KBAiChatMessage *)openingMessage;
|
||||
|
||||
/// 追加历史消息(倒置模式使用:把更老的消息插入到 openingMessage 之前/末尾)
|
||||
- (void)appendHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||
openingMessage:(nullable KBAiChatMessage *)openingMessage;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -127,6 +127,60 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
- (void)setInverted:(BOOL)inverted {
|
||||
if (_inverted == inverted) {
|
||||
return;
|
||||
}
|
||||
_inverted = inverted;
|
||||
|
||||
self.tableView.transform = inverted ? CGAffineTransformMakeScale(1, -1) : CGAffineTransformIdentity;
|
||||
[self updateContentBottomInset:self.contentBottomInset];
|
||||
[self.tableView reloadData];
|
||||
[self.tableView layoutIfNeeded];
|
||||
}
|
||||
|
||||
static inline CGFloat KBChatAbsTimeInterval(NSTimeInterval interval) {
|
||||
return interval < 0 ? -interval : interval;
|
||||
}
|
||||
|
||||
- (BOOL)shouldInsertTimestampBetweenMessage:(KBAiChatMessage *)message
|
||||
andReference:(KBAiChatMessage *)reference {
|
||||
if (!message.timestamp || !reference.timestamp) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSTimeInterval interval = KBChatAbsTimeInterval([message.timestamp timeIntervalSinceDate:reference.timestamp]);
|
||||
if (interval >= kTimestampInterval) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSCalendar *calendar = [NSCalendar currentCalendar];
|
||||
NSDateComponents *a = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
|
||||
fromDate:reference.timestamp];
|
||||
NSDateComponents *b = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
|
||||
fromDate:message.timestamp];
|
||||
return ![a isEqual:b];
|
||||
}
|
||||
|
||||
- (NSInteger)firstNonTimeIndex {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
if (self.messages[i].type != KBAiChatMessageTypeTime) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (NSInteger)lastNonTimeIndexBeforeIndex:(NSInteger)index {
|
||||
NSInteger maxIndex = MIN(index, self.messages.count);
|
||||
for (NSInteger i = maxIndex - 1; i >= 0; i--) {
|
||||
if (self.messages[i].type != KBAiChatMessageTypeTime) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (void)addUserMessage:(NSString *)text {
|
||||
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
|
||||
[self addMessage:message autoScroll:YES];
|
||||
@@ -138,17 +192,31 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}
|
||||
|
||||
- (void)updateLastUserMessage:(NSString *)text {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
|
||||
// 刷新该行
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,24 +240,44 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
- (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) {
|
||||
NSLog(@"[KBChatTableView] 更新最后一条 AI 消息 - 索引: %ld, 文本长度: %lu, needsTypewriter: %d",
|
||||
(long)i, (unsigned long)text.length, message.needsTypewriterEffect);
|
||||
message.text = text;
|
||||
|
||||
// 直接更新 Cell 的文本,不刷新整个 Cell(避免打断打字机效果)
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
if ([cell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
NSLog(@"[KBChatTableView] 找到 Cell,直接配置消息");
|
||||
// 直接调用 configureWithMessage,让 Cell 自己决定是否使用打字机效果
|
||||
[cell configureWithMessage:message];
|
||||
} else {
|
||||
NSLog(@"[KBChatTableView] 未找到 Cell 或类型不匹配");
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && !message.isComplete) {
|
||||
NSLog(@"[KBChatTableView] 更新最后一条 AI 消息 - 索引: %ld, 文本长度: %lu, needsTypewriter: %d",
|
||||
(long)i, (unsigned long)text.length, message.needsTypewriterEffect);
|
||||
message.text = text;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
if ([cell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
NSLog(@"[KBChatTableView] 找到 Cell,直接配置消息");
|
||||
[cell configureWithMessage:message];
|
||||
cell.contentView.transform = self.inverted ? CGAffineTransformMakeScale(1, -1) : CGAffineTransformIdentity;
|
||||
} else {
|
||||
NSLog(@"[KBChatTableView] 未找到 Cell 或类型不匹配");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && !message.isComplete) {
|
||||
NSLog(@"[KBChatTableView] 更新最后一条 AI 消息 - 索引: %ld, 文本长度: %lu, needsTypewriter: %d",
|
||||
(long)i, (unsigned long)text.length, message.needsTypewriterEffect);
|
||||
message.text = text;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
|
||||
if ([cell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
NSLog(@"[KBChatTableView] 找到 Cell,直接配置消息");
|
||||
[cell configureWithMessage:message];
|
||||
} else {
|
||||
NSLog(@"[KBChatTableView] 未找到 Cell 或类型不匹配");
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,33 +287,63 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}
|
||||
|
||||
- (void)markLastAssistantMessageComplete {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant) {
|
||||
NSLog(@"[KBChatTableView] 标记消息完成 - 索引: %ld, 文本: %@", (long)i, message.text);
|
||||
message.isComplete = YES;
|
||||
message.needsTypewriterEffect = NO; // 完成后不再需要打字机效果
|
||||
|
||||
// 刷新 Cell 以显示完整文本
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant) {
|
||||
NSLog(@"[KBChatTableView] 标记消息完成 - 索引: %ld, 文本: %@", (long)i, message.text);
|
||||
message.isComplete = YES;
|
||||
message.needsTypewriterEffect = NO;
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant) {
|
||||
NSLog(@"[KBChatTableView] 标记消息完成 - 索引: %ld, 文本: %@", (long)i, message.text);
|
||||
message.isComplete = YES;
|
||||
message.needsTypewriterEffect = NO; // 完成后不再需要打字机效果
|
||||
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)markLastUserMessageLoadingComplete {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser) {
|
||||
message.isLoading = NO;
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView layoutIfNeeded];
|
||||
[self scrollToBottomAnimated:NO];
|
||||
return;
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser) {
|
||||
message.isLoading = NO;
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView layoutIfNeeded];
|
||||
[self scrollToBottomAnimated:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser) {
|
||||
message.isLoading = NO;
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView layoutIfNeeded];
|
||||
[self scrollToBottomAnimated:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,14 +355,41 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}
|
||||
|
||||
- (void)removeLoadingAssistantMessage {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
NSLog(@"[KBChatTableView] 移除 loading AI 消息,索引: %ld", (long)i);
|
||||
break;
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
if (self.playingCellIndexPath) {
|
||||
if (self.playingCellIndexPath.row == i) {
|
||||
[self stopPlayingAudio];
|
||||
} else if (self.playingCellIndexPath.row > i) {
|
||||
self.playingCellIndexPath = [NSIndexPath indexPathForRow:self.playingCellIndexPath.row - 1 inSection:0];
|
||||
}
|
||||
}
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
NSLog(@"[KBChatTableView] 移除 loading AI 消息,索引: %ld", (long)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
if (self.playingCellIndexPath) {
|
||||
if (self.playingCellIndexPath.row == i) {
|
||||
[self stopPlayingAudio];
|
||||
} else if (self.playingCellIndexPath.row > i) {
|
||||
self.playingCellIndexPath = [NSIndexPath indexPathForRow:self.playingCellIndexPath.row - 1 inSection:0];
|
||||
}
|
||||
}
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
||||
NSLog(@"[KBChatTableView] 移除 loading AI 消息,索引: %ld", (long)i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,6 +403,12 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
// 关键修复:使用 layoutIfNeeded 确保布局完成后再滚动
|
||||
[self.tableView layoutIfNeeded];
|
||||
|
||||
if (self.inverted) {
|
||||
CGFloat minOffsetY = -self.tableView.contentInset.top;
|
||||
[self.tableView setContentOffset:CGPointMake(0, minOffsetY) animated:animated];
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算需要滚动到的位置
|
||||
CGFloat contentHeight = self.tableView.contentSize.height;
|
||||
@@ -299,7 +450,11 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
// 直接设置 contentInset
|
||||
UIEdgeInsets insets = UIEdgeInsetsZero;
|
||||
insets.bottom = bottomInset;
|
||||
if (self.inverted) {
|
||||
insets.top = bottomInset;
|
||||
} else {
|
||||
insets.bottom = bottomInset;
|
||||
}
|
||||
self.tableView.contentInset = insets;
|
||||
self.tableView.scrollIndicatorInsets = insets;
|
||||
|
||||
@@ -319,22 +474,46 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}
|
||||
|
||||
NSInteger oldCount = self.messages.count;
|
||||
[self insertMessageWithTimestamp:message];
|
||||
NSArray<KBAiChatMessage *> *itemsToInsert = nil;
|
||||
NSInteger insertIndex = oldCount;
|
||||
|
||||
if (self.inverted) {
|
||||
itemsToInsert = [self invertedItemsForNewMessageAtBeginning:message];
|
||||
insertIndex = 0;
|
||||
if (itemsToInsert.count > 0) {
|
||||
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, itemsToInsert.count)];
|
||||
[self.messages insertObjects:itemsToInsert atIndexes:set];
|
||||
}
|
||||
} else {
|
||||
[self insertMessageWithTimestamp:message];
|
||||
itemsToInsert = nil;
|
||||
insertIndex = oldCount;
|
||||
}
|
||||
|
||||
NSInteger newCount = self.messages.count;
|
||||
NSInteger insertedCount = newCount - oldCount;
|
||||
|
||||
if (insertedCount > 0) {
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
for (NSInteger i = oldCount; i < newCount; i++) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
|
||||
if (self.inverted && self.playingCellIndexPath) {
|
||||
self.playingCellIndexPath = [NSIndexPath indexPathForRow:self.playingCellIndexPath.row + insertedCount inSection:0];
|
||||
}
|
||||
|
||||
NSMutableArray *indexPaths = [NSMutableArray array];
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < insertedCount; i++) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = insertIndex; i < newCount; i++) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
|
||||
}
|
||||
}
|
||||
|
||||
// 关键修复:批量插入前先布局,避免高度计算不准确
|
||||
[self.tableView layoutIfNeeded];
|
||||
|
||||
[self.tableView insertRowsAtIndexPaths:indexPaths
|
||||
withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView beginUpdates];
|
||||
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView endUpdates];
|
||||
}
|
||||
|
||||
[self updateFooterVisibility];
|
||||
@@ -362,6 +541,9 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
- (void)reloadWithMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||
keepOffset:(BOOL)keepOffset
|
||||
scrollToBottom:(BOOL)scrollToBottom {
|
||||
if (self.inverted) {
|
||||
keepOffset = NO;
|
||||
}
|
||||
CGFloat oldContentHeight = self.tableView.contentSize.height;
|
||||
CGFloat oldOffsetY = self.tableView.contentOffset.y;
|
||||
KBAiChatMessage *anchorMessage = nil;
|
||||
@@ -400,8 +582,14 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
[self.messages removeAllObjects];
|
||||
if (messages.count > 0) {
|
||||
for (KBAiChatMessage *message in messages) {
|
||||
[self insertMessageWithTimestamp:message];
|
||||
if (self.inverted) {
|
||||
NSArray<KBAiChatMessage *> *rebuilt = [self invertedMessagesByInsertingTimestamps:messages
|
||||
startingReference:nil];
|
||||
[self.messages addObjectsFromArray:rebuilt];
|
||||
} else {
|
||||
for (KBAiChatMessage *message in messages) {
|
||||
[self insertMessageWithTimestamp:message];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +620,9 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
NSLog(@"[KBChatTableView] ========== reloadWithMessages 结束 ==========");
|
||||
|
||||
if (keepOffset) {
|
||||
if (self.inverted) {
|
||||
keepOffset = NO;
|
||||
}
|
||||
CGFloat offsetY = oldOffsetY;
|
||||
if (anchorMessage) {
|
||||
NSInteger newIndex = [self.messages indexOfObjectIdenticalTo:anchorMessage];
|
||||
@@ -480,6 +671,36 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
|
||||
- (NSArray<KBAiChatMessage *> *)invertedItemsForNewMessageAtBeginning:(KBAiChatMessage *)message {
|
||||
NSMutableArray<KBAiChatMessage *> *result = [NSMutableArray array];
|
||||
NSInteger firstIndex = [self firstNonTimeIndex];
|
||||
KBAiChatMessage *reference = (firstIndex != NSNotFound) ? self.messages[firstIndex] : nil;
|
||||
|
||||
[result addObject:message];
|
||||
if (!reference || [self shouldInsertTimestampBetweenMessage:message andReference:reference]) {
|
||||
KBAiChatMessage *timeMessage = [KBAiChatMessage timeMessageWithTimestamp:message.timestamp];
|
||||
[result addObject:timeMessage];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSArray<KBAiChatMessage *> *)invertedMessagesByInsertingTimestamps:(NSArray<KBAiChatMessage *> *)messages
|
||||
startingReference:(nullable KBAiChatMessage *)reference {
|
||||
NSMutableArray<KBAiChatMessage *> *result = [NSMutableArray array];
|
||||
KBAiChatMessage *ref = reference;
|
||||
for (KBAiChatMessage *msg in messages) {
|
||||
if (ref && [self shouldInsertTimestampBetweenMessage:msg andReference:ref]) {
|
||||
KBAiChatMessage *timeMessage = [KBAiChatMessage timeMessageWithTimestamp:msg.timestamp];
|
||||
[result addObject:timeMessage];
|
||||
} else if (!ref) {
|
||||
// 第一条消息不强制插时间,避免底部多一条时间戳
|
||||
}
|
||||
[result addObject:msg];
|
||||
ref = msg;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSInteger)firstVisibleNonTimeRowExcludingMessage:(KBAiChatMessage *)excludedMessage {
|
||||
NSArray<NSIndexPath *> *visible = self.tableView.indexPathsForVisibleRows;
|
||||
if (visible.count == 0) { return NSNotFound; }
|
||||
@@ -605,6 +826,48 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)appendHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
|
||||
openingMessage:(nullable KBAiChatMessage *)openingMessage {
|
||||
if (messages.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger insertIndex = self.messages.count;
|
||||
if (openingMessage) {
|
||||
NSInteger openingIndex = [self.messages indexOfObjectIdenticalTo:openingMessage];
|
||||
if (openingIndex != NSNotFound) {
|
||||
insertIndex = openingIndex;
|
||||
}
|
||||
}
|
||||
|
||||
NSInteger referenceIndex = [self lastNonTimeIndexBeforeIndex:insertIndex];
|
||||
KBAiChatMessage *reference = (referenceIndex != NSNotFound) ? self.messages[referenceIndex] : nil;
|
||||
|
||||
NSArray<KBAiChatMessage *> *toInsert = nil;
|
||||
if (self.inverted) {
|
||||
toInsert = [self invertedMessagesByInsertingTimestamps:messages startingReference:reference];
|
||||
} else {
|
||||
toInsert = [self messagesByInsertingTimestamps:messages];
|
||||
}
|
||||
|
||||
[UIView performWithoutAnimation:^{
|
||||
NSRange range = NSMakeRange(insertIndex, toInsert.count);
|
||||
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range];
|
||||
[self.messages insertObjects:toInsert atIndexes:set];
|
||||
|
||||
NSMutableArray<NSIndexPath *> *indexPaths = [NSMutableArray array];
|
||||
for (NSInteger i = 0; i < toInsert.count; i++) {
|
||||
[indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex + i inSection:0]];
|
||||
}
|
||||
|
||||
[self.tableView beginUpdates];
|
||||
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
[self.tableView endUpdates];
|
||||
|
||||
[self.tableView layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
|
||||
/// 判断是否需要插入时间戳
|
||||
- (BOOL)shouldInsertTimestampForMessage:(KBAiChatMessage *)message {
|
||||
// 第一条消息总是显示时间
|
||||
@@ -659,19 +922,37 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
}
|
||||
|
||||
- (void)stopPreviousIncompleteAssistantMessageIfNeeded {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *msg = self.messages[i];
|
||||
if (msg.type == KBAiChatMessageTypeAssistant && !msg.isComplete) {
|
||||
msg.isComplete = YES;
|
||||
msg.needsTypewriterEffect = NO;
|
||||
|
||||
NSIndexPath *oldIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *oldCell = [self.tableView cellForRowAtIndexPath:oldIndexPath];
|
||||
if ([oldCell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
[oldCell stopTypewriterEffect];
|
||||
oldCell.messageLabel.text = msg.text;
|
||||
if (self.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *msg = self.messages[i];
|
||||
if (msg.type == KBAiChatMessageTypeAssistant && !msg.isComplete) {
|
||||
msg.isComplete = YES;
|
||||
msg.needsTypewriterEffect = NO;
|
||||
|
||||
NSIndexPath *oldIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *oldCell = [self.tableView cellForRowAtIndexPath:oldIndexPath];
|
||||
if ([oldCell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
[oldCell stopTypewriterEffect];
|
||||
oldCell.messageLabel.text = msg.text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *msg = self.messages[i];
|
||||
if (msg.type == KBAiChatMessageTypeAssistant && !msg.isComplete) {
|
||||
msg.isComplete = YES;
|
||||
msg.needsTypewriterEffect = NO;
|
||||
|
||||
NSIndexPath *oldIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
|
||||
KBChatAssistantMessageCell *oldCell = [self.tableView cellForRowAtIndexPath:oldIndexPath];
|
||||
if ([oldCell isKindOfClass:[KBChatAssistantMessageCell class]]) {
|
||||
[oldCell stopTypewriterEffect];
|
||||
oldCell.messageLabel.text = msg.text;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -700,6 +981,7 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
KBChatUserMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:kUserCellIdentifier
|
||||
forIndexPath:indexPath];
|
||||
[cell configureWithMessage:message];
|
||||
cell.contentView.transform = self.inverted ? CGAffineTransformMakeScale(1, -1) : CGAffineTransformIdentity;
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -713,6 +995,7 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
BOOL isPlaying = [indexPath isEqual:self.playingCellIndexPath];
|
||||
[cell updateVoicePlayingState:isPlaying];
|
||||
|
||||
cell.contentView.transform = self.inverted ? CGAffineTransformMakeScale(1, -1) : CGAffineTransformIdentity;
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -720,6 +1003,7 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
KBChatTimeCell *cell = [tableView dequeueReusableCellWithIdentifier:kTimeCellIdentifier
|
||||
forIndexPath:indexPath];
|
||||
[cell configureWithMessage:message];
|
||||
cell.contentView.transform = self.inverted ? CGAffineTransformMakeScale(1, -1) : CGAffineTransformIdentity;
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
@@ -732,6 +1016,42 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (UICollectionView *)nearestCollectionView {
|
||||
UIView *view = self;
|
||||
while (view) {
|
||||
if ([view isKindOfClass:[UICollectionView class]]) {
|
||||
return (UICollectionView *)view;
|
||||
}
|
||||
view = view.superview;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setNearestCollectionViewScrollEnabled:(BOOL)enabled {
|
||||
UICollectionView *collectionView = [self nearestCollectionView];
|
||||
if (!collectionView) {
|
||||
return;
|
||||
}
|
||||
if (collectionView.scrollEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
collectionView.scrollEnabled = enabled;
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
[self setNearestCollectionViewScrollEnabled:NO];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
|
||||
if (!decelerate) {
|
||||
[self setNearestCollectionViewScrollEnabled:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
[self setNearestCollectionViewScrollEnabled:YES];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
if ([self.delegate respondsToSelector:@selector(chatTableViewDidScroll:scrollView:)]) {
|
||||
[self.delegate chatTableViewDidScroll:self scrollView:scrollView];
|
||||
@@ -743,23 +1063,18 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
|
||||
withVelocity:(CGPoint)velocity
|
||||
targetContentOffset:(inout CGPoint *)targetContentOffset {
|
||||
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat scrollViewHeight = scrollView.bounds.size.height;
|
||||
CGFloat minOffset = -scrollView.contentInset.top;
|
||||
CGFloat maxOffset = contentHeight - scrollViewHeight + scrollView.contentInset.bottom;
|
||||
if (maxOffset < minOffset) {
|
||||
maxOffset = minOffset;
|
||||
}
|
||||
|
||||
// 如果内容不够长,禁用弹簧效果
|
||||
if (contentHeight <= scrollViewHeight) {
|
||||
scrollView.bounces = NO;
|
||||
} else {
|
||||
scrollView.bounces = YES;
|
||||
|
||||
// 在快速滑动到底部时,避免过度弹簧导致抖动
|
||||
if (velocity.y < 0) { // 向上滑动(到底部)
|
||||
CGFloat maxOffset = contentHeight - scrollViewHeight + scrollView.contentInset.bottom;
|
||||
if (targetContentOffset->y > maxOffset) {
|
||||
targetContentOffset->y = maxOffset;
|
||||
}
|
||||
}
|
||||
if (targetContentOffset->y < minOffset) {
|
||||
targetContentOffset->y = minOffset;
|
||||
} else if (targetContentOffset->y > maxOffset) {
|
||||
targetContentOffset->y = maxOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.aiVM fetchChatHistoryWithCompanionId:companionId
|
||||
pageNum:self.currentPage
|
||||
pageSize:20
|
||||
pageSize:10
|
||||
completion:^(KBChatHistoryPageModel *pageModel, NSError *error) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
@@ -304,8 +304,8 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
|
||||
message.isComplete = YES;
|
||||
message.needsTypewriterEffect = NO;
|
||||
// [newMessages addObject:message];
|
||||
[newMessages insertObject:message atIndex:0];
|
||||
[newMessages addObject:message];
|
||||
// [newMessages insertObject:message atIndex:0];
|
||||
}
|
||||
|
||||
// 插入历史消息(确保开场白始终是第一条)
|
||||
@@ -317,12 +317,19 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
strongSelf.messages = newMessages;
|
||||
[strongSelf ensureOpeningMessageAtTop];
|
||||
} else {
|
||||
// 后续页,插入到开场白之后
|
||||
// 后续页,继续加载历史
|
||||
[strongSelf ensureOpeningMessageAtTop];
|
||||
if (newMessages.count > 0) {
|
||||
NSUInteger insertIndex = [strongSelf hasOpeningMessageAtTop] ? 1 : 0;
|
||||
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
|
||||
[strongSelf.messages insertObjects:newMessages atIndexes:indexSet];
|
||||
if (strongSelf.chatView.inverted) {
|
||||
NSInteger openingIndex = [strongSelf openingMessageIndexInMessages];
|
||||
NSUInteger insertIndex = (openingIndex != NSNotFound) ? (NSUInteger)openingIndex : strongSelf.messages.count;
|
||||
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
|
||||
[strongSelf.messages insertObjects:newMessages atIndexes:indexSet];
|
||||
} else {
|
||||
NSUInteger insertIndex = [strongSelf hasOpeningMessageAtTop] ? 1 : 0;
|
||||
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(insertIndex, newMessages.count)];
|
||||
[strongSelf.messages insertObjects:newMessages atIndexes:indexSet];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,10 +342,16 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
keepOffset:NO
|
||||
scrollToBottom:YES];
|
||||
} else {
|
||||
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, prependHistory",
|
||||
(long)loadedPage);
|
||||
KBAiChatMessage *openingMessage = [strongSelf hasOpeningMessageAtTop] ? strongSelf.messages.firstObject : nil;
|
||||
[strongSelf.chatView prependHistoryMessages:newMessages openingMessage:openingMessage];
|
||||
if (strongSelf.chatView.inverted) {
|
||||
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, appendHistory", (long)loadedPage);
|
||||
KBAiChatMessage *openingMessage = [strongSelf openingMessageInMessages];
|
||||
[strongSelf.chatView appendHistoryMessages:newMessages openingMessage:openingMessage];
|
||||
} else {
|
||||
NSLog(@"[KBPersonaChatCell] 刷新 UI - loadedPage: %ld, prependHistory",
|
||||
(long)loadedPage);
|
||||
KBAiChatMessage *openingMessage = [strongSelf hasOpeningMessageAtTop] ? strongSelf.messages.firstObject : nil;
|
||||
[strongSelf.chatView prependHistoryMessages:newMessages openingMessage:openingMessage];
|
||||
}
|
||||
}
|
||||
[strongSelf.chatView endLoadMoreWithHasMoreData:strongSelf.hasMoreHistory];
|
||||
|
||||
@@ -381,6 +394,9 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
if (self.messages.count == 0) {
|
||||
return NO;
|
||||
}
|
||||
if (self.chatView.inverted) {
|
||||
return [self isOpeningMessage:self.messages.lastObject];
|
||||
}
|
||||
return [self isOpeningMessage:self.messages.firstObject];
|
||||
}
|
||||
|
||||
@@ -409,7 +425,35 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
KBAiChatMessage *openingMsg = [KBAiChatMessage assistantMessageWithText:introText];
|
||||
openingMsg.isComplete = YES;
|
||||
openingMsg.needsTypewriterEffect = NO;
|
||||
[self.messages insertObject:openingMsg atIndex:0];
|
||||
if (self.chatView.inverted) {
|
||||
[self.messages addObject:openingMsg];
|
||||
} else {
|
||||
[self.messages insertObject:openingMsg atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable KBAiChatMessage *)openingMessageInMessages {
|
||||
NSInteger index = [self openingMessageIndexInMessages];
|
||||
if (index == NSNotFound) {
|
||||
return nil;
|
||||
}
|
||||
return self.messages[index];
|
||||
}
|
||||
|
||||
- (NSInteger)openingMessageIndexInMessages {
|
||||
NSString *introText = self.persona.introText ?: @"";
|
||||
if (introText.length == 0 || self.messages.count == 0) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
if (self.chatView.inverted) {
|
||||
NSInteger lastIndex = self.messages.count - 1;
|
||||
KBAiChatMessage *msg = self.messages[lastIndex];
|
||||
return [self isOpeningMessage:msg] ? lastIndex : NSNotFound;
|
||||
}
|
||||
|
||||
KBAiChatMessage *first = self.messages.firstObject;
|
||||
return [self isOpeningMessage:first] ? 0 : NSNotFound;
|
||||
}
|
||||
|
||||
#pragma mark - 通知处理
|
||||
@@ -454,7 +498,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
|
||||
[self ensureOpeningMessageAtTop];
|
||||
KBAiChatMessage *message = [KBAiChatMessage userMessageWithText:text];
|
||||
[self.messages addObject:message];
|
||||
if (self.chatView.inverted) {
|
||||
[self.messages insertObject:message atIndex:0];
|
||||
} else {
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
[self.chatView addMessage:message autoScroll:YES];
|
||||
}
|
||||
|
||||
@@ -465,7 +513,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
|
||||
[self ensureOpeningMessageAtTop];
|
||||
KBAiChatMessage *message = [KBAiChatMessage loadingUserMessage];
|
||||
[self.messages addObject:message];
|
||||
if (self.chatView.inverted) {
|
||||
[self.messages insertObject:message atIndex:0];
|
||||
} else {
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
[self.chatView addMessage:message autoScroll:YES];
|
||||
}
|
||||
|
||||
@@ -473,13 +525,25 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
[self.chatView updateLastUserMessage:text];
|
||||
|
||||
// 更新数据源中的消息
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
break;
|
||||
if (self.chatView.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.text = text;
|
||||
message.isLoading = NO;
|
||||
message.isComplete = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -488,11 +552,21 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
[self.chatView markLastUserMessageLoadingComplete];
|
||||
|
||||
// 同步更新数据源
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.isLoading = NO;
|
||||
break;
|
||||
if (self.chatView.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.isLoading = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeUser && message.isLoading) {
|
||||
message.isLoading = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +589,11 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
KBAiChatMessage *message = [KBAiChatMessage assistantMessageWithText:text
|
||||
audioId:audioId];
|
||||
message.needsTypewriterEffect = YES;
|
||||
[self.messages addObject:message];
|
||||
if (self.chatView.inverted) {
|
||||
[self.messages insertObject:message atIndex:0];
|
||||
} else {
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
[self.chatView addMessage:message autoScroll:YES];
|
||||
}
|
||||
|
||||
@@ -527,18 +605,32 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
|
||||
[self ensureOpeningMessageAtTop];
|
||||
KBAiChatMessage *message = [KBAiChatMessage loadingAssistantMessage];
|
||||
[self.messages addObject:message];
|
||||
if (self.chatView.inverted) {
|
||||
[self.messages insertObject:message atIndex:0];
|
||||
} else {
|
||||
[self.messages addObject:message];
|
||||
}
|
||||
[self.chatView addMessage:message autoScroll:YES];
|
||||
}
|
||||
|
||||
/// 移除 loading AI 消息
|
||||
- (void)removeLoadingAssistantMessage {
|
||||
// 从数据源中移除
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
break;
|
||||
if (self.chatView.inverted) {
|
||||
for (NSInteger i = 0; i < self.messages.count; i++) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (NSInteger i = self.messages.count - 1; i >= 0; i--) {
|
||||
KBAiChatMessage *message = self.messages[i];
|
||||
if (message.type == KBAiChatMessageTypeAssistant && message.isLoading) {
|
||||
[self.messages removeObjectAtIndex:i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,7 +648,22 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
scrollView:(UIScrollView *)scrollView {
|
||||
CGFloat offsetY = scrollView.contentOffset.y;
|
||||
|
||||
// 下拉到顶部,加载历史消息
|
||||
if (chatView.inverted) {
|
||||
CGFloat contentHeight = scrollView.contentSize.height;
|
||||
CGFloat scrollViewHeight = scrollView.bounds.size.height;
|
||||
CGFloat maxOffsetY = contentHeight - scrollViewHeight + scrollView.contentInset.bottom;
|
||||
if (maxOffsetY < 0) {
|
||||
maxOffsetY = 0;
|
||||
}
|
||||
if (offsetY >= maxOffsetY - 50 && !self.isLoading && self.canTriggerLoadMore && self.hasMoreHistory) {
|
||||
self.canTriggerLoadMore = NO;
|
||||
[self loadMoreHistory];
|
||||
} else if (offsetY < maxOffsetY - 100) {
|
||||
self.canTriggerLoadMore = YES;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (offsetY <= 50 && !self.isLoading && self.canTriggerLoadMore && self.hasMoreHistory) {
|
||||
self.canTriggerLoadMore = NO;
|
||||
[self loadMoreHistory];
|
||||
@@ -622,6 +729,7 @@ static NSString * const KBChatSessionDidResetNotification = @"KBChatSessionDidRe
|
||||
if (!_chatView) {
|
||||
_chatView = [[KBChatTableView alloc] init];
|
||||
_chatView.backgroundColor = [UIColor clearColor];
|
||||
_chatView.inverted = YES;
|
||||
_chatView.delegate = self;
|
||||
}
|
||||
return _chatView;
|
||||
|
||||
Reference in New Issue
Block a user