处理流逝返回
处理粘贴
This commit is contained in:
@@ -9,6 +9,13 @@
|
|||||||
@interface KBStreamOverlayView ()
|
@interface KBStreamOverlayView ()
|
||||||
@property (nonatomic, strong) KBStreamTextView *textViewInternal;
|
@property (nonatomic, strong) KBStreamTextView *textViewInternal;
|
||||||
@property (nonatomic, strong) UIButton *closeButton;
|
@property (nonatomic, strong) UIButton *closeButton;
|
||||||
|
|
||||||
|
// 新增:流式打字机用的缓冲 & 定时器
|
||||||
|
@property (nonatomic, strong) NSMutableString *pendingText;
|
||||||
|
@property (nonatomic, strong) NSTimer *streamTimer;
|
||||||
|
@property (nonatomic, assign) NSInteger charsPerTick; // 每次“跳”几个字符
|
||||||
|
// 新增:标记 SSE 已经收到 done
|
||||||
|
@property (nonatomic, assign) BOOL streamDidReceiveDone;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation KBStreamOverlayView
|
@implementation KBStreamOverlayView
|
||||||
@@ -34,6 +41,10 @@
|
|||||||
make.height.mas_equalTo(28);
|
make.height.mas_equalTo(28);
|
||||||
make.width.mas_greaterThanOrEqualTo(56);
|
make.width.mas_greaterThanOrEqualTo(56);
|
||||||
}];
|
}];
|
||||||
|
_pendingText = [NSMutableString string];
|
||||||
|
_charsPerTick = 2; // 每次输出 1~2 个字符,可以自己调
|
||||||
|
_streamDidReceiveDone = NO;
|
||||||
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -67,13 +78,73 @@
|
|||||||
|
|
||||||
- (void)appendChunk:(NSString *)text {
|
- (void)appendChunk:(NSString *)text {
|
||||||
if (text.length == 0) return;
|
if (text.length == 0) return;
|
||||||
[self.textViewInternal appendStreamText:text];
|
if (![NSThread isMainThread]) {
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[weakSelf appendChunk:text];
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.pendingText appendString:text];
|
||||||
|
[self startStreamTimerIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)finish {
|
- (void)startStreamTimerIfNeeded {
|
||||||
[self.textViewInternal finishStreaming];
|
if (self.streamTimer) return;
|
||||||
|
self.streamTimer = [NSTimer scheduledTimerWithTimeInterval:0.02
|
||||||
|
target:self
|
||||||
|
selector:@selector(handleStreamTick)
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)stopStreamTimer {
|
||||||
|
[self.streamTimer invalidate];
|
||||||
|
self.streamTimer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handleStreamTick {
|
||||||
|
if (self.pendingText.length == 0) {
|
||||||
|
// 如果已经收到 done 并且没有待播内容了,这里再真正 finish
|
||||||
|
if (self.streamDidReceiveDone) {
|
||||||
|
[self.textViewInternal finishStreaming];
|
||||||
|
}
|
||||||
|
[self stopStreamTimer];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSInteger len = MIN(self.charsPerTick, self.pendingText.length);
|
||||||
|
NSString *slice = [self.pendingText substringToIndex:len];
|
||||||
|
[self.pendingText deleteCharactersInRange:NSMakeRange(0, len)];
|
||||||
|
|
||||||
|
[self.textViewInternal appendStreamText:slice];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (void)finish {
|
||||||
|
if (![NSThread isMainThread]) {
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[weakSelf finish];
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只标记“流已结束”
|
||||||
|
self.streamDidReceiveDone = YES;
|
||||||
|
|
||||||
|
// 如果此时已经没有待播内容了,可以立即结束
|
||||||
|
if (self.pendingText.length == 0) {
|
||||||
|
[self stopStreamTimer];
|
||||||
|
[self.textViewInternal finishStreaming];
|
||||||
|
}
|
||||||
|
// 否则等 handleStreamTick 把 pendingText 慢慢播完,
|
||||||
|
// 它看到 pendingText == 0 且 streamDidReceiveDone == YES 时会自动调用 finishStreaming
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- (KBStreamTextView *)textView { return self.textViewInternal; }
|
- (KBStreamTextView *)textView { return self.textViewInternal; }
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -290,9 +290,10 @@
|
|||||||
}
|
}
|
||||||
NSInteger resolvedCharacterId = (characterId > 0) ? characterId : 75;
|
NSInteger resolvedCharacterId = (characterId > 0) ? characterId : 75;
|
||||||
NSString *message = seedTitle.length > 0 ? seedTitle : @"aliqua non cupidatat";
|
NSString *message = seedTitle.length > 0 ? seedTitle : @"aliqua non cupidatat";
|
||||||
|
// message = [NSString stringWithFormat:@"%@%d",message,arc4random() % 10000];
|
||||||
NSDictionary *payload = @{
|
NSDictionary *payload = @{
|
||||||
@"characterId": @(resolvedCharacterId),
|
@"characterId": @(resolvedCharacterId),
|
||||||
@"message": @"dolore ea cillum"
|
@"message": message
|
||||||
};
|
};
|
||||||
NSLog(@"[KBFunction] request payload: %@", payload);
|
NSLog(@"[KBFunction] request payload: %@", payload);
|
||||||
NSError *bodyError = nil;
|
NSError *bodyError = nil;
|
||||||
@@ -399,13 +400,15 @@
|
|||||||
- (NSString *)kb_normalizedLLMChunkString:(id)dataValue {
|
- (NSString *)kb_normalizedLLMChunkString:(id)dataValue {
|
||||||
if (![dataValue isKindOfClass:[NSString class]]) { return @""; }
|
if (![dataValue isKindOfClass:[NSString class]]) { return @""; }
|
||||||
NSString *text = (NSString *)dataValue;
|
NSString *text = (NSString *)dataValue;
|
||||||
|
|
||||||
|
// 1. 处理上一个包遗留的 <SPLIT> 前缀(比如 "<SP" + "LIT>")
|
||||||
if (self.eventSourceSplitPrefix.length > 0) {
|
if (self.eventSourceSplitPrefix.length > 0) {
|
||||||
text = [self.eventSourceSplitPrefix stringByAppendingString:text ?: @""];
|
text = [self.eventSourceSplitPrefix stringByAppendingString:text ?: @""];
|
||||||
self.eventSourceSplitPrefix = nil;
|
self.eventSourceSplitPrefix = nil;
|
||||||
}
|
}
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"\r\n\t" withString:@"\t"];
|
if (text.length == 0) { return @""; }
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"\n\t" withString:@"\t"];
|
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"\r\t" withString:@"\t"];
|
// 2. 去掉开头多余换行(避免一开始就空一大块)
|
||||||
while (text.length > 0) {
|
while (text.length > 0) {
|
||||||
unichar c0 = [text characterAtIndex:0];
|
unichar c0 = [text characterAtIndex:0];
|
||||||
if (c0 == '\n' || c0 == '\r') {
|
if (c0 == '\n' || c0 == '\r') {
|
||||||
@@ -414,17 +417,24 @@
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"/t" withString:@"\t"];
|
if (text.length == 0) { return @""; }
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"<SPLIT>" withString:@"\t"];
|
|
||||||
|
// 3. 处理结尾可能是不完整的 "<SPLIT" 之类,先截掉,放到下一个包里拼
|
||||||
NSString *suffix = [self kb_pendingSplitSuffixForString:text];
|
NSString *suffix = [self kb_pendingSplitSuffixForString:text];
|
||||||
if (suffix.length > 0) {
|
if (suffix.length > 0) {
|
||||||
self.eventSourceSplitPrefix = suffix;
|
self.eventSourceSplitPrefix = suffix;
|
||||||
text = [text substringToIndex:text.length - suffix.length];
|
text = [text substringToIndex:text.length - suffix.length];
|
||||||
}
|
}
|
||||||
text = [text stringByReplacingOccurrencesOfString:@"\t " withString:@"\t"];
|
if (text.length == 0) { return @""; }
|
||||||
|
|
||||||
|
// 4. 处理完整的 <SPLIT>,变成段落分隔符 \t
|
||||||
|
text = [text stringByReplacingOccurrencesOfString:@"<SPLIT>" withString:@"\t"];
|
||||||
|
|
||||||
|
// 不再做其它替换,不合并 /t、不改行,只把真正内容原样丢给 UI
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (NSString *)kb_formattedSearchResultString:(id)dataValue {
|
- (NSString *)kb_formattedSearchResultString:(id)dataValue {
|
||||||
// data 不是数组就直接返回空串
|
// data 不是数组就直接返回空串
|
||||||
if (![dataValue isKindOfClass:[NSArray class]]) { return @""; }
|
if (![dataValue isKindOfClass:[NSArray class]]) { return @""; }
|
||||||
@@ -570,12 +580,18 @@
|
|||||||
// });
|
// });
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
|
BOOL hasPasteText = ![self.pasteView.pasBtn.currentTitle isEqualToString:KBLocalized(@" Paste Ta's Words")];
|
||||||
|
// BOOL hasPasteText = (self.pasteView.pasBtn.imageView.image == nil);
|
||||||
|
if (!hasPasteText) {
|
||||||
|
[KBHUD showInfo:KBLocalized(@"Please copy the text first")];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSString *copyTitle = self.pasteView.pasBtn.currentTitle;
|
||||||
// 3) 已登录:开始业务逻辑(展示加载并拉取流式内容)
|
// 3) 已登录:开始业务逻辑(展示加载并拉取流式内容)
|
||||||
[self.tagListView setLoading:YES atIndex:index];
|
[self.tagListView setLoading:YES atIndex:index];
|
||||||
self.loadingTagIndex = @(index);
|
self.loadingTagIndex = @(index);
|
||||||
self.loadingTagTitle = title ?: @"";
|
self.loadingTagTitle = title ?: @"";
|
||||||
[self kb_startNetworkStreamingWithSeed:self.loadingTagTitle];
|
[self kb_startNetworkStreamingWithSeed:copyTitle];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,11 +668,11 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1)把内容真正「粘贴」到当前输入框
|
// 1)把内容真正「粘贴」到当前输入框
|
||||||
UIInputViewController *ivc = KBFindInputViewController(self);
|
// UIInputViewController *ivc = KBFindInputViewController(self);
|
||||||
if (ivc) {
|
// if (ivc) {
|
||||||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
// id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||||||
[proxy insertText:text];
|
// [proxy insertText:text];
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 2)顺便把最新的剪贴板内容展示在左侧粘贴区按钮上,便于用户确认
|
// 2)顺便把最新的剪贴板内容展示在左侧粘贴区按钮上,便于用户确认
|
||||||
[self kb_updatePasteButtonWithDisplayText:text];
|
[self kb_updatePasteButtonWithDisplayText:text];
|
||||||
|
|||||||
@@ -262,3 +262,4 @@
|
|||||||
"Change The Nickname" = "Change Nickname";
|
"Change The Nickname" = "Change Nickname";
|
||||||
"Please Enter The Modified Nickname" = "Please enter the new nickname";
|
"Please Enter The Modified Nickname" = "Please enter the new nickname";
|
||||||
"Save" = "Save";
|
"Save" = "Save";
|
||||||
|
"Please copy the text first" = "Please copy the text first";
|
||||||
|
|||||||
@@ -259,3 +259,4 @@
|
|||||||
"Change The Nickname" = "修改名称";
|
"Change The Nickname" = "修改名称";
|
||||||
"Please Enter The Modified Nickname" = "请输入修改后的昵称";
|
"Please Enter The Modified Nickname" = "请输入修改后的昵称";
|
||||||
"Save" = "保存";
|
"Save" = "保存";
|
||||||
|
"Please copy the text first" = "请先复制文本";
|
||||||
|
|||||||
Reference in New Issue
Block a user