处理AI回复问题
This commit is contained in:
@@ -288,6 +288,13 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
|||||||
|
|
||||||
- (void)emitChunk:(NSString *)rawText {
|
- (void)emitChunk:(NSString *)rawText {
|
||||||
if (rawText.length == 0) return;
|
if (rawText.length == 0) return;
|
||||||
|
// 调试:在任何处理之前打印后端“原始文本分片”,便于对照排查
|
||||||
|
if (self.loggingEnabled) {
|
||||||
|
NSLog(@"[KBStream] RAW chunk#%ld len=%lu text=\"%@\"",
|
||||||
|
(long)(self.emittedChunkCount + 1),
|
||||||
|
(unsigned long)rawText.length,
|
||||||
|
KBPrintableSnippet(rawText, 160));
|
||||||
|
}
|
||||||
NSString *text = rawText;
|
NSString *text = rawText;
|
||||||
// 0) 规范化换行与段起始:去掉位于片段开头的 \r/\n;将 "\n\t"、"\r\n\t"、"\r\t" 归一为 "\t"
|
// 0) 规范化换行与段起始:去掉位于片段开头的 \r/\n;将 "\n\t"、"\r\n\t"、"\r\t" 归一为 "\t"
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"\r\n\t" withString:@"\t"];
|
text = [text stringByReplacingOccurrencesOfString:@"\r\n\t" withString:@"\t"];
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
del.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
|
del.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
|
||||||
del.layer.cornerRadius = 14; del.layer.masksToBounds = YES;
|
del.layer.cornerRadius = 14; del.layer.masksToBounds = YES;
|
||||||
del.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold];
|
del.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold];
|
||||||
[del setTitle:KBLocalized(@"Delete") forState:UIControlStateNormal];
|
[del setTitle:KBLocalized(@"common_back") forState:UIControlStateNormal];
|
||||||
[del setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
[del setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||||
[del addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside];
|
[del addTarget:self action:@selector(onTapClose) forControlEvents:UIControlEventTouchUpInside];
|
||||||
_closeButton = del;
|
_closeButton = del;
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||||
// 已创建的标签集合(顺序即显示顺序)
|
// 已创建的标签集合(顺序即显示顺序)
|
||||||
@property (nonatomic, strong) NSMutableArray<UILabel *> *labels;
|
@property (nonatomic, strong) NSMutableArray<UILabel *> *labels;
|
||||||
|
// 每一段对应的“原始文本”(不含前缀编号),与 labels 一一对应
|
||||||
|
@property (nonatomic, strong) NSMutableArray<NSString *> *segmentTexts;
|
||||||
// 文本缓冲:保存尚未遇到分隔符的尾部文本
|
// 文本缓冲:保存尚未遇到分隔符的尾部文本
|
||||||
@property (nonatomic, copy) NSString *buffer;
|
@property (nonatomic, copy) NSString *buffer;
|
||||||
|
|
||||||
@@ -40,14 +42,12 @@
|
|||||||
- (void)commonInit {
|
- (void)commonInit {
|
||||||
_delimiter = @"\t";
|
_delimiter = @"\t";
|
||||||
_labelFont = [UIFont systemFontOfSize:16.0];
|
_labelFont = [UIFont systemFontOfSize:16.0];
|
||||||
if (@available(iOS 13.0, *)) {
|
// 统一使用主题色 #02BEAC 作为段落文字颜色
|
||||||
_labelTextColor = [UIColor labelColor];
|
_labelTextColor = [UIColor colorWithRed:2.0/255.0 green:190.0/255.0 blue:172.0/255.0 alpha:1.0];
|
||||||
} else {
|
|
||||||
_labelTextColor = [UIColor blackColor];
|
|
||||||
}
|
|
||||||
_contentHorizontalPadding = 12.0;
|
_contentHorizontalPadding = 12.0;
|
||||||
_interItemSpacing = 5.0; // 标签之间的垂直间距 5pt
|
_interItemSpacing = 5.0; // 标签之间的垂直间距 5pt
|
||||||
_labels = [NSMutableArray array];
|
_labels = [NSMutableArray array];
|
||||||
|
_segmentTexts = [NSMutableArray array];
|
||||||
_buffer = @"";
|
_buffer = @"";
|
||||||
_shouldTrimSegments = YES;
|
_shouldTrimSegments = YES;
|
||||||
|
|
||||||
@@ -95,7 +95,9 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
if (self.delimiter.length == 0) {
|
if (self.delimiter.length == 0) {
|
||||||
[self ensureCurrentLabelExists];
|
[self ensureCurrentLabelExists];
|
||||||
self.buffer = [self.buffer stringByAppendingString:text];
|
self.buffer = [self.buffer stringByAppendingString:text];
|
||||||
self.labels.lastObject.text = self.buffer;
|
// 更新当前段落的展示文本(带编号前缀)
|
||||||
|
NSUInteger idx = (self.labels.count > 0) ? (self.labels.count - 1) : 0;
|
||||||
|
[self setContent:self.buffer forSegmentAtIndex:idx];
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
[self scrollToBottomIfNeeded];
|
[self scrollToBottomIfNeeded];
|
||||||
return;
|
return;
|
||||||
@@ -109,23 +111,31 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
|
|
||||||
// 1) 先把第一段拼接到当前缓冲并实时显示
|
// 1) 先把第一段拼接到当前缓冲并实时显示
|
||||||
self.buffer = [self.buffer stringByAppendingString:parts.firstObject ?: @""];
|
self.buffer = [self.buffer stringByAppendingString:parts.firstObject ?: @""];
|
||||||
self.labels.lastObject.text = self.buffer; // 不裁剪,保留实时输入
|
// 不裁剪,保留实时输入;更新当前段落展示(附带编号前缀)
|
||||||
|
NSUInteger currentIndex = (self.labels.count > 0) ? (self.labels.count - 1) : 0;
|
||||||
|
[self setContent:self.buffer forSegmentAtIndex:currentIndex];
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
|
|
||||||
// 2) 处理每一个分隔符:完成当前段(仅裁剪“末尾空白”)并新建空标签,然后填入下一段的内容
|
// 2) 处理每一个分隔符:完成当前段(仅裁剪“末尾空白”)并新建空标签,然后填入下一段的内容
|
||||||
for (NSUInteger i = 1; i < parts.count; i++) {
|
for (NSUInteger i = 1; i < parts.count; i++) {
|
||||||
// a) 完成当前段:对外观进行最终裁剪(若开启)
|
// a) 完成当前段:对外观进行最终裁剪(若开启)
|
||||||
UILabel *current = self.labels.lastObject;
|
NSString *currentText = (self.segmentTexts.count > currentIndex)
|
||||||
if (self.shouldTrimSegments) { current.text = KBTrimRight(current.text ?: @""); }
|
? (self.segmentTexts[currentIndex] ?: @"")
|
||||||
|
: (self.buffer ?: @"");
|
||||||
|
if (self.shouldTrimSegments) {
|
||||||
|
currentText = KBTrimRight(currentText ?: @"");
|
||||||
|
}
|
||||||
|
[self setContent:currentText forSegmentAtIndex:currentIndex];
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
|
|
||||||
// b) 新建一个空标签,代表下一个段(即刻创建,哪怕下一段当前为空)
|
// b) 新建一个空标签,代表下一个段(即刻创建,哪怕下一段当前为空)
|
||||||
[self createEmptyLabelAsNewSegment];
|
[self createEmptyLabelAsNewSegment];
|
||||||
|
currentIndex = (self.labels.count > 0) ? (self.labels.count - 1) : 0;
|
||||||
|
|
||||||
// c) 将该分隔符之后的这段文本作为新段的初始内容(仍旧实时显示,不裁剪)
|
// c) 将该分隔符之后的这段文本作为新段的初始内容(仍旧实时显示,不裁剪)
|
||||||
NSString *piece = parts[i];
|
NSString *piece = parts[i] ?: @"";
|
||||||
self.buffer = piece ?: @"";
|
self.buffer = piece;
|
||||||
self.labels.lastObject.text = self.buffer;
|
[self setContent:self.buffer forSegmentAtIndex:currentIndex];
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +147,7 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
[lbl removeFromSuperview];
|
[lbl removeFromSuperview];
|
||||||
}
|
}
|
||||||
[self.labels removeAllObjects];
|
[self.labels removeAllObjects];
|
||||||
|
[self.segmentTexts removeAllObjects];
|
||||||
self.buffer = @"";
|
self.buffer = @"";
|
||||||
self.scrollView.contentSize = CGSizeMake(self.bounds.size.width, 0);
|
self.scrollView.contentSize = CGSizeMake(self.bounds.size.width, 0);
|
||||||
}
|
}
|
||||||
@@ -151,7 +162,9 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
label.font = self.labelFont;
|
label.font = self.labelFont;
|
||||||
label.textColor = self.labelTextColor;
|
label.textColor = self.labelTextColor;
|
||||||
label.userInteractionEnabled = YES; // 允许点击
|
label.userInteractionEnabled = YES; // 允许点击
|
||||||
label.text = text;
|
NSUInteger idx = self.labels.count;
|
||||||
|
NSString *content = text ?: @"";
|
||||||
|
label.text = [self displayTextForSegmentIndex:idx content:content];
|
||||||
|
|
||||||
// 添加点击手势
|
// 添加点击手势
|
||||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleLabelTap:)];
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleLabelTap:)];
|
||||||
@@ -159,6 +172,13 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
|
|
||||||
[self.scrollView addSubview:label];
|
[self.scrollView addSubview:label];
|
||||||
[self.labels addObject:label];
|
[self.labels addObject:label];
|
||||||
|
// 维护原始文本数组,与 labels 一一对应
|
||||||
|
if (self.segmentTexts.count <= idx) {
|
||||||
|
while (self.segmentTexts.count < idx) { [self.segmentTexts addObject:@""]; }
|
||||||
|
[self.segmentTexts addObject:content];
|
||||||
|
} else {
|
||||||
|
self.segmentTexts[idx] = content;
|
||||||
|
}
|
||||||
|
|
||||||
// 依据当前宽度立即布局
|
// 依据当前宽度立即布局
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
@@ -182,12 +202,21 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
label.font = self.labelFont;
|
label.font = self.labelFont;
|
||||||
label.textColor = self.labelTextColor;
|
label.textColor = self.labelTextColor;
|
||||||
label.userInteractionEnabled = YES;
|
label.userInteractionEnabled = YES;
|
||||||
label.text = @"";
|
NSUInteger idx = self.labels.count;
|
||||||
|
// 初始为空内容,仅展示编号前缀(例如“1: ”)
|
||||||
|
label.text = [self displayTextForSegmentIndex:idx content:@""];
|
||||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleLabelTap:)];
|
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleLabelTap:)];
|
||||||
[label addGestureRecognizer:tap];
|
[label addGestureRecognizer:tap];
|
||||||
|
|
||||||
[self.scrollView addSubview:label];
|
[self.scrollView addSubview:label];
|
||||||
[self.labels addObject:label];
|
[self.labels addObject:label];
|
||||||
|
// 保证 segmentTexts 与 labels 对齐
|
||||||
|
if (self.segmentTexts.count <= idx) {
|
||||||
|
while (self.segmentTexts.count < idx) { [self.segmentTexts addObject:@""]; }
|
||||||
|
[self.segmentTexts addObject:@""];
|
||||||
|
} else {
|
||||||
|
self.segmentTexts[idx] = @"";
|
||||||
|
}
|
||||||
self.buffer = @"";
|
self.buffer = @"";
|
||||||
|
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
@@ -201,13 +230,16 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UILabel *current = self.labels.lastObject;
|
if (self.labels.count == 0) { return; }
|
||||||
if (!current) { return; }
|
NSUInteger idx = self.labels.count - 1;
|
||||||
|
NSString *content = (self.segmentTexts.count > idx)
|
||||||
|
? (self.segmentTexts[idx] ?: @"")
|
||||||
|
: (self.buffer ?: @"");
|
||||||
if (self.shouldTrimSegments) {
|
if (self.shouldTrimSegments) {
|
||||||
NSString *trimmed = KBTrimRight(current.text ?: @"");
|
content = KBTrimRight(content ?: @"");
|
||||||
current.text = trimmed;
|
|
||||||
self.buffer = trimmed;
|
|
||||||
}
|
}
|
||||||
|
[self setContent:content forSegmentAtIndex:idx];
|
||||||
|
self.buffer = content;
|
||||||
// 无论是否裁剪,都重新布局并滚动到底部,避免最后一段显示不全
|
// 无论是否裁剪,都重新布局并滚动到底部,避免最后一段显示不全
|
||||||
[self layoutLabelsForCurrentWidth];
|
[self layoutLabelsForCurrentWidth];
|
||||||
[self scrollToBottomIfNeeded];
|
[self scrollToBottomIfNeeded];
|
||||||
@@ -271,9 +303,12 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
UILabel *label = (UILabel *)tap.view;
|
UILabel *label = (UILabel *)tap.view;
|
||||||
if (![label isKindOfClass:[UILabel class]]) { return; }
|
if (![label isKindOfClass:[UILabel class]]) { return; }
|
||||||
NSInteger index = [self.labels indexOfObject:label];
|
NSInteger index = [self.labels indexOfObject:label];
|
||||||
NSString *text = label.text ?: @"";
|
// 对外与插入宿主输入框时使用“原始内容”(不含编号前缀)
|
||||||
|
NSString *rawText = (index != NSNotFound && index < (NSInteger)self.segmentTexts.count)
|
||||||
|
? (self.segmentTexts[(NSUInteger)index] ?: @"")
|
||||||
|
: (label.text ?: @"");
|
||||||
if (index != NSNotFound && self.onLabelTap) {
|
if (index != NSNotFound && self.onLabelTap) {
|
||||||
self.onLabelTap(index, text);
|
self.onLabelTap(index, rawText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将文本发送到当前宿主应用的输入框(搜索框/TextView 等)
|
// 将文本发送到当前宿主应用的输入框(搜索框/TextView 等)
|
||||||
@@ -281,10 +316,46 @@ static inline NSString *KBTrimRight(NSString *s) {
|
|||||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
UIInputViewController *ivc = KBFindInputViewController(self);
|
||||||
if (ivc) {
|
if (ivc) {
|
||||||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||||||
if (text.length > 0 && [proxy conformsToProtocol:@protocol(UITextDocumentProxy)]) {
|
if (rawText.length > 0 && [proxy conformsToProtocol:@protocol(UITextDocumentProxy)]) {
|
||||||
[proxy insertText:text];
|
[proxy insertText:rawText];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Segment Helpers
|
||||||
|
|
||||||
|
// 根据段索引与原始内容生成展示文本(带 1 起始的编号前缀)
|
||||||
|
- (NSString *)displayTextForSegmentIndex:(NSUInteger)index content:(NSString *)content {
|
||||||
|
NSInteger displayIndex = (NSInteger)index + 1;
|
||||||
|
// 统一把内部的换行折叠为单个空格,避免同一段里出现“断行”的视觉效果
|
||||||
|
NSString *body = content ?: @"";
|
||||||
|
body = [body stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"];
|
||||||
|
// 将连续换行压缩为单个换行
|
||||||
|
while ([body containsString:@"\n\n"]) {
|
||||||
|
body = [body stringByReplacingOccurrencesOfString:@"\n\n" withString:@"\n"];
|
||||||
|
}
|
||||||
|
// 最终把所有换行替换成空格
|
||||||
|
body = [body stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
|
||||||
|
|
||||||
|
if (body.length > 0) {
|
||||||
|
return [NSString stringWithFormat:@"%ld: %@", (long)displayIndex, body];
|
||||||
|
} else {
|
||||||
|
return [NSString stringWithFormat:@"%ld: ", (long)displayIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统一更新某一段的“原始内容”和对应 label 的展示文本
|
||||||
|
- (void)setContent:(NSString *)content forSegmentAtIndex:(NSUInteger)idx {
|
||||||
|
if (idx >= self.labels.count) { return; }
|
||||||
|
NSString *body = content ?: @"";
|
||||||
|
if (self.segmentTexts.count <= idx) {
|
||||||
|
while (self.segmentTexts.count < idx) { [self.segmentTexts addObject:@""]; }
|
||||||
|
[self.segmentTexts addObject:body];
|
||||||
|
} else {
|
||||||
|
self.segmentTexts[idx] = body;
|
||||||
|
}
|
||||||
|
UILabel *label = self.labels[idx];
|
||||||
|
label.text = [self displayTextForSegmentIndex:idx content:body];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user