This commit is contained in:
2025-11-12 15:31:22 +08:00
parent 1dbe04cdf9
commit c317afc0fe
3 changed files with 53 additions and 44 deletions

View File

@@ -14,6 +14,7 @@
@property (nonatomic, assign) NSInteger decodedPrefixBytes; // sseTextBuffer SSE
@property (nonatomic, assign) NSInteger deliveredCharCount; // SSE
@property (nonatomic, assign) BOOL hasEmitted; // 1 \t
@property (nonatomic, assign) BOOL lastChunkEndedWithTab; // "\t" \t
@property (nonatomic, strong) NSMutableArray<NSString *> *pendingQueue; //
@property (nonatomic, strong) NSTimer *flushTimer; //
@property (nonatomic, strong, nullable) NSError *finishError; //
@@ -57,7 +58,7 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
_buffer = [NSMutableData data];
_sseTextBuffer = [NSMutableString string];
_pendingQueue = [NSMutableArray array];
_flushInterval = 0.10;
_flushInterval = 0.1;
_splitLargeDeltasOnWhitespace = YES;
}
return self;
@@ -90,6 +91,7 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
self.decodedPrefixBytes = 0;
self.deliveredCharCount = 0;
self.hasEmitted = NO;
self.lastChunkEndedWithTab = NO;
[self.pendingQueue removeAllObjects];
[self.flushTimer invalidate]; self.flushTimer = nil;
self.finishError = nil;
@@ -108,6 +110,7 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
self.decodedPrefixBytes = 0;
self.deliveredCharCount = 0;
self.hasEmitted = NO;
self.lastChunkEndedWithTab = NO;
[self.pendingQueue removeAllObjects];
[self.flushTimer invalidate]; self.flushTimer = nil;
self.finishError = nil;
@@ -251,26 +254,37 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
- (void)emitChunk:(NSString *)rawText {
if (rawText.length == 0) return;
NSString *text = rawText;
// 1) /t -> \t
if (self.treatSlashTAsTab) {
text = [text stringByReplacingOccurrencesOfString:@"/t" withString:@"\t"];
}
// 2) "\t"
if (!self.hasEmitted && self.trimLeadingTabOnce) {
// \t
NSUInteger i = 0;
while (i < text.length) {
unichar c = [text characterAtIndex:i];
if (c == ' ' || c == '\r' || c == '\n') { i++; continue; }
break;
}
if (i < text.length && [text characterAtIndex:i] == '\t') {
NSMutableString *m = [text mutableCopy];
[m deleteCharactersInRange:NSMakeRange(i, 1)];
text = m;
if (text.length > 0 && [text characterAtIndex:0] == '\t') {
NSUInteger start = 1;
if (start < text.length && [text characterAtIndex:start] == ' ') start++;
text = [text substringFromIndex:start];
}
}
if (text.length == 0) return;
// 3) \t -> \t
if (text.length > 0) {
// \t
if (self.lastChunkEndedWithTab) {
NSUInteger j = 0;
while (j < text.length && [text characterAtIndex:j] == ' ') { j++; }
if (j > 0) {
text = [text substringFromIndex:1]; //
}
}
// \t \t
text = [text stringByReplacingOccurrencesOfString:@"\t " withString:@"\t"];
}
if (text.length == 0) { self.lastChunkEndedWithTab = NO; return; }
if (self.onChunk) dispatch_async(dispatch_get_main_queue(), ^{ self.onChunk(text); });
self.hasEmitted = YES;
// \t
unichar lastc = [text characterAtIndex:text.length - 1];
self.lastChunkEndedWithTab = (lastc == '\t');
}
#pragma mark - Queue/Flush

View File

@@ -312,7 +312,7 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/
- (void)kb_startNetworkStreamingWithSeed:(NSString *)seedTitle fallbackToMock:(BOOL)fallback {
[self kb_stopNetworkStreaming];
if (![[KBFullAccessManager shared] hasFullAccess]) { if (fallback) [self kb_startMockStreamingWithSeed:seedTitle]; return; }
if (![[KBFullAccessManager shared] hasFullAccess]) { if (fallback) /*[self kb_startMockStreamingWithSeed:seedTitle]*/; return; }
NSURL *url = [NSURL URLWithString:kKBStreamDemoURL];
if (!url) { if (fallback) [self kb_startMockStreamingWithSeed:seedTitle]; return; }
@@ -323,8 +323,10 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/
// /t->\t \tfetcher
fetcher.disableCompression = YES;
fetcher.acceptEventStream = NO; // SSE
fetcher.treatSlashTAsTab = NO;
fetcher.trimLeadingTabOnce = NO;
// \t \t Fetcher UI
fetcher.treatSlashTAsTab = YES;
fetcher.trimLeadingTabOnce = YES;
fetcher.flushInterval = 0.05; //
fetcher.onChunk = ^(NSString *chunk) {
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
[self kb_appendChunkToStreamView:chunk];
@@ -351,27 +353,11 @@ static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/
#pragma mark - Helpers
/// KBStreamTextView
/// - "/t" "\t"
/// - "\t"
/// -
/// - KBStreamFetcher /t->\t \t
/// - UI
- (void)kb_appendChunkToStreamView:(NSString *)chunk {
if (chunk.length == 0 || !self.streamTextView) return;
NSString *text = [chunk stringByReplacingOccurrencesOfString:@"/t" withString:@"\t"];
if (!self.streamHasOutput) {
NSUInteger i = 0; //
while (i < text.length) {
unichar c = [text characterAtIndex:i];
if (c == ' ' || c == '\r' || c == '\n') { i++; continue; }
break;
}
if (i < text.length && [text characterAtIndex:i] == '\t') {
NSMutableString *m = [text mutableCopy];
[m deleteCharactersInRange:NSMakeRange(i, 1)];
text = m;
}
}
if (text.length == 0) return;
[self.streamTextView appendStreamText:text];
[self.streamTextView appendStreamText:chunk];
self.streamHasOutput = YES;
}

View File

@@ -62,6 +62,20 @@
[self addSubview:_scrollView];
}
#pragma mark - Helpers
// /
static inline NSString *KBTrimRight(NSString *s) {
if (s.length == 0) return s;
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSInteger end = (NSInteger)s.length - 1;
while (end >= 0 && [set characterIsMember:[s characterAtIndex:(NSUInteger)end]]) {
end--;
}
if (end < 0) return @"";
return [s substringToIndex:(NSUInteger)end + 1];
}
#pragma mark - Public API
// label label
@@ -98,14 +112,11 @@
self.labels.lastObject.text = self.buffer; //
[self layoutLabelsForCurrentWidth];
// 2)
// 2)
for (NSUInteger i = 1; i < parts.count; i++) {
// a)
UILabel *current = self.labels.lastObject;
if (self.shouldTrimSegments) {
NSString *trimmed = [current.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
current.text = trimmed;
}
if (self.shouldTrimSegments) { current.text = KBTrimRight(current.text ?: @""); }
[self layoutLabelsForCurrentWidth];
// b)
@@ -133,9 +144,7 @@
#pragma mark - Layout Helpers
- (void)addLabelForText:(NSString *)text {
if (self.shouldTrimSegments) {
text = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
if (self.shouldTrimSegments) { text = KBTrimRight(text ?: @""); }
// UILabel
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.numberOfLines = 0;
@@ -195,7 +204,7 @@
UILabel *current = self.labels.lastObject;
if (!current) { return; }
if (self.shouldTrimSegments) {
NSString *trimmed = [current.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *trimmed = KBTrimRight(current.text ?: @"");
current.text = trimmed;
self.buffer = trimmed;
}