处理聊天数据一致性问题

This commit is contained in:
mh 2025-12-01 18:22:33 +08:00
parent bd8703656f
commit 9405f4e42c
4 changed files with 51 additions and 13 deletions

View File

@ -1,5 +1,4 @@
//
// PhoneCallViewModel.swift
// // PhoneCallViewModel.swift
//
// Created by Leon on 2025/8/27.

View File

@ -263,6 +263,9 @@ extension SessionController: UITableViewDataSource {
let cell = tableView.dequeueReusableCell(withIdentifier: StreamChatBubbleCell.reuseIdentifier, for: indexPath) as! StreamChatBubbleCell
let message = streamMessages[indexPath.row]
cell.configure(message: message)
cell.onLongPress = { [weak self] in
self?.copyStreamMessage(at: indexPath.row)
}
return cell
}
let model = self.util.cellModels[indexPath.row]
@ -338,6 +341,15 @@ extension SessionController: UITableViewDelegate {
header.ignoredScrollViewContentInsetTop = tableView.contentInset.top
}
}
private func copyStreamMessage(at row: Int) {
guard isStreamChatMode,
streamMessages.indices.contains(row) else { return }
let text = streamMessages[row].text
guard text.isEmpty == false else { return }
UIPasteboard.general.string = text
Hud.toast(str: "复制成功")
}
}

View File

@ -84,6 +84,7 @@ class SessionController: CLBaseViewController {
private var typingCharactersPerSecond: CGFloat = 20
private var lastDisplayLinkTimestamp: CFTimeInterval = 0
private var shouldFinishTypingAfterTarget = false
private var rawStreamingBuffer: String = ""
convenience init(accountID: String) {
self.init()
@ -588,6 +589,7 @@ extension SessionController {
private struct StreamChunkResult {
let text: String?
let finished: Bool
let isRawString: Bool
}
private func processStreamChatPayload(_ payload: Any?, event: String?) {
@ -595,7 +597,7 @@ extension SessionController {
let result = parseStreamChunk(payload)
if let text = result.text, text.isEmpty == false {
appendOrUpdateIncomingChunk(text)
appendOrUpdateIncomingChunk(text, isRawString: result.isRawString)
}
if result.finished || (event?.lowercased().contains("done") ?? false) {
@ -603,7 +605,7 @@ extension SessionController {
}
}
private func appendOrUpdateIncomingChunk(_ chunk: String) {
private func appendOrUpdateIncomingChunk(_ chunk: String, isRawString: Bool) {
DispatchQueue.main.async { [weak self] in
guard let self = self, chunk.isEmpty == false else { return }
@ -612,7 +614,13 @@ extension SessionController {
if let currentId = self.currentStreamingMessageId,
let index = self.streamMessages.firstIndex(where: { $0.id == currentId }) {
if isRawString {
self.rawStreamingBuffer += chunk
self.typingTargetText = self.rawStreamingBuffer
} else {
self.typingTargetText = self.mergeText(current: self.typingTargetText, incoming: chunk)
self.rawStreamingBuffer = self.typingTargetText
}
messageIndex = index
} else {
let message = StreamChatMessageModel(id: UUID().uuidString,
@ -621,7 +629,13 @@ extension SessionController {
isStreaming: true)
self.streamMessages.append(message)
self.currentStreamingMessageId = message.id
if isRawString {
self.rawStreamingBuffer = chunk
self.typingTargetText = self.rawStreamingBuffer
} else {
self.rawStreamingBuffer = chunk
self.typingTargetText = chunk
}
self.typingDisplayedLength = 0
self.shouldFinishTypingAfterTarget = false
messageIndex = self.streamMessages.count - 1
@ -689,17 +703,17 @@ extension SessionController {
if let dict = payload as? [String: Any] {
let finished = (dict["finished"] as? Bool) ?? (dict["done"] as? Bool) ?? false
if let text = extractText(from: dict) {
return StreamChunkResult(text: text, finished: finished)
return StreamChunkResult(text: text, finished: finished, isRawString: false)
}
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
let jsonString = String(data: data, encoding: .utf8) {
return StreamChunkResult(text: jsonString, finished: finished)
return StreamChunkResult(text: jsonString, finished: finished, isRawString: false)
}
return StreamChunkResult(text: nil, finished: finished)
return StreamChunkResult(text: nil, finished: finished, isRawString: false)
} else if let str = payload as? String {
let normalized = str.trimmingCharacters(in: .whitespacesAndNewlines)
if normalized.isEmpty {
return StreamChunkResult(text: nil, finished: false)
return StreamChunkResult(text: nil, finished: false, isRawString: true)
}
if let data = normalized.data(using: .utf8),
@ -708,13 +722,13 @@ extension SessionController {
}
if normalized.lowercased() == "[done]" {
return StreamChunkResult(text: nil, finished: true)
return StreamChunkResult(text: nil, finished: true, isRawString: true)
}
let containsDoneFlag = normalized.lowercased().contains("__end__") || normalized.lowercased().contains("<end>")
return StreamChunkResult(text: str, finished: containsDoneFlag)
return StreamChunkResult(text: str, finished: containsDoneFlag, isRawString: true)
}
return StreamChunkResult(text: nil, finished: false)
return StreamChunkResult(text: nil, finished: false, isRawString: false)
}
func resetStreamingStateIfNeeded() {
@ -831,6 +845,7 @@ extension SessionController {
typingDisplayedLength = 0
shouldFinishTypingAfterTarget = false
currentStreamingMessageId = nil
rawStreamingBuffer = ""
}
/// SSE

View File

@ -6,6 +6,12 @@ final class StreamChatBubbleCell: UITableViewCell {
private let bubbleView = UIView()
private let messageLabel = UILabel()
var onLongPress: (() -> Void)?
private lazy var longPressGesture: UILongPressGestureRecognizer = {
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
gesture.minimumPressDuration = 0.4
return gesture
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
@ -32,6 +38,7 @@ final class StreamChatBubbleCell: UITableViewCell {
bubbleView.layer.masksToBounds = true
bubbleView.setContentHuggingPriority(.defaultLow, for: .horizontal)
bubbleView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
contentView.addGestureRecognizer(longPressGesture)
bubbleView.addSubview(messageLabel)
messageLabel.numberOfLines = 0
@ -80,4 +87,9 @@ final class StreamChatBubbleCell: UITableViewCell {
layoutIfNeeded()
}
}
@objc private func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
guard gesture.state == .began else { return }
onLongPress?()
}
}