|
@@ -17,7 +17,7 @@ extension TSChatViewController {
|
|
|
inputBarVC.emptyInput()
|
|
|
|
|
|
sendMessages(data)
|
|
|
- messagesCollectionView.scrollToLastItem(animated: true)
|
|
|
+ scrollToBottom()
|
|
|
view.endEditing(true)
|
|
|
}
|
|
|
|
|
@@ -48,37 +48,44 @@ extension TSChatViewController {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ //先插入回答的消息,转圈加载
|
|
|
let message = TSChatMessage(attributedText: kMDAttributedString(text: ""), user: viewModel.kAIUser, messageId: UUID().uuidString, date: Date())
|
|
|
message.sendState = .start
|
|
|
insertMessage(message)
|
|
|
-
|
|
|
- inputBarVC.sendEnabled(enabled: false)
|
|
|
-
|
|
|
NotificationCenter.default.post(name: .kAIAnsweringNotification, object: nil, userInfo: [kIsAIAnswering: true])
|
|
|
+
|
|
|
+// //每次全部输出
|
|
|
+// viewModel.sendChatMessage(message: messageString) {[weak self] string in
|
|
|
+// guard let self = self else { return }
|
|
|
+// debugPrint("viewModel.AiMDString=\(viewModel.AiMDString)")
|
|
|
+// message.kind = .attributedText(kMDAttributedString(text: viewModel.AiMDString))
|
|
|
+// message.sendState = .progress(0.5)
|
|
|
+//
|
|
|
+// if self.scrollToBottomButton.isHidden == true {
|
|
|
+// updataAIChatCellUI()
|
|
|
+// self.messagesCollectionView.scrollToLastItem(animated: false)
|
|
|
+// }
|
|
|
+// }
|
|
|
|
|
|
+ //逐字输出
|
|
|
+ var previousAiMDString = ""
|
|
|
viewModel.sendChatMessage(message: messageString) {[weak self] string in
|
|
|
guard let self = self else { return }
|
|
|
debugPrint("viewModel.AiMDString=\(viewModel.AiMDString)")
|
|
|
- message.kind = .attributedText(kMDAttributedString(text: viewModel.AiMDString))
|
|
|
message.sendState = .progress(0.5)
|
|
|
-
|
|
|
- if self.scrollToBottomButton.isHidden == true {
|
|
|
- updataAIChatCellUI()
|
|
|
- self.messagesCollectionView.scrollToLastItem(animated: false)
|
|
|
- }
|
|
|
-
|
|
|
- } completion: {[weak self] data, error in
|
|
|
+ delayedOutputAnimation(message: message, previousStr: previousAiMDString, newAddStr: string)
|
|
|
+ previousAiMDString = viewModel.AiMDString
|
|
|
+ }
|
|
|
+
|
|
|
+ completion: {[weak self] data, error in
|
|
|
guard let self = self else { return }
|
|
|
- if let netData = data {
|
|
|
+ if let _ = data {
|
|
|
message.sendState = .success("netData")
|
|
|
- //保存这条消息到本地数据库
|
|
|
- //消耗一次 AI 次数
|
|
|
- kPurchaseDefault.useOnceForFree(type: .aichat)
|
|
|
-
|
|
|
+ kPurchaseDefault.useOnceForFree(type: .aichat)//消耗一次 AI 次数
|
|
|
+ message.kind = .attributedText(kMDAttributedString(text: viewModel.AiMDString))
|
|
|
}else {
|
|
|
message.kind = .attributedText(kMDAttributedString(text: kAIErrorString))
|
|
|
message.sendState = .failed(kAIErrorString)
|
|
|
- //保存这条消息到本地数据库
|
|
|
}
|
|
|
updataAIChatCellUI()
|
|
|
|
|
@@ -91,52 +98,93 @@ extension TSChatViewController {
|
|
|
}
|
|
|
|
|
|
if self.scrollToBottomButton.isHidden == true {
|
|
|
- self.messagesCollectionView.scrollToLastItem(animated: false)
|
|
|
+ self.scrollToBottom()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func updataAIChatCellUI(){
|
|
|
-// if self.scrollToBottomButton.isHidden == true {
|
|
|
-// if isLastItemVisible() {
|
|
|
- kExecuteOnMainThread {
|
|
|
- if self.messageList.count >= 2 {
|
|
|
- UIView.performWithoutAnimation {
|
|
|
- self.messagesCollectionView.reloadItems(at: [self.lastIndexPath])
|
|
|
- }
|
|
|
- }else{
|
|
|
- self.messagesCollectionView.reloadData()
|
|
|
+ kExecuteOnMainThread {
|
|
|
+ if self.messageList.count >= 2 {
|
|
|
+ UIView.performWithoutAnimation {
|
|
|
+ self.messagesCollectionView.reloadItems(at: [self.lastIndexPath])
|
|
|
}
|
|
|
+ }else{
|
|
|
+ self.messagesCollectionView.reloadData()
|
|
|
}
|
|
|
-// }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
// 判断是否显示最后一个单元格
|
|
|
func isLastItemVisible() -> Bool {
|
|
|
- guard let indexPath = getIndexPathForLastItem() else { return false }
|
|
|
let visibleIndexPaths = messagesCollectionView.indexPathsForVisibleItems
|
|
|
return visibleIndexPaths.contains(lastIndexPath)
|
|
|
}
|
|
|
|
|
|
+ func scrollToBottom() {
|
|
|
+ self.messagesCollectionView.scrollToLastItem(animated: false)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
- // 获取最后一个单元格的 indexPath
|
|
|
- func getIndexPathForLastItem() -> IndexPath? {
|
|
|
- let section = messagesCollectionView.numberOfSections - 1
|
|
|
- if messagesCollectionView.numberOfItems(inSection: section) > 0 {
|
|
|
- let item = messagesCollectionView.numberOfItems(inSection: section) - 1
|
|
|
- return IndexPath(item: item, section: section)
|
|
|
+extension TSChatViewController{
|
|
|
+
|
|
|
+ func delayedOutputAnimation(message:TSChatMessage,previousStr:String,newAddStr:String, delay: TimeInterval = 0.05) {
|
|
|
+ var showUIString = previousStr
|
|
|
+ let characters = Array(newAddStr)// 将 newText 转换为字符数组
|
|
|
+
|
|
|
+ // 使用递归逐字显示
|
|
|
+ func typeCharacter(at index: Int) {
|
|
|
+ guard index < characters.count else { return } // 递归终止条件
|
|
|
+ showUIString.append(String(characters[index]))
|
|
|
+ // 设置延迟,逐字显示
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
|
+ if self.scrollToBottomButton.isHidden == true {
|
|
|
+ message.kind = .attributedText(kMDAttributedString(text: showUIString))
|
|
|
+ self.updataAIChatCellUI()
|
|
|
+ self.scrollToBottom()
|
|
|
+ }
|
|
|
+ typeCharacter(at: index + 1) // 递归调用
|
|
|
+ }
|
|
|
}
|
|
|
- return nil
|
|
|
+
|
|
|
+ // 开始逐字显示
|
|
|
+ typeCharacter(at: 0)
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+ func typeTextAnimation(label: UILabel, originalText: String, newText: String, delay: TimeInterval = 0.05) {
|
|
|
+ // 确保 label 当前显示的文本是 originalText
|
|
|
+ label.text = originalText
|
|
|
+
|
|
|
+ // 将 newText 转换为字符数组
|
|
|
+ let characters = Array(newText)
|
|
|
|
|
|
+ // 使用递归逐字显示
|
|
|
+ func typeCharacter(at index: Int) {
|
|
|
+ guard index < characters.count else { return } // 递归终止条件
|
|
|
+
|
|
|
+ // 更新 label 的文本
|
|
|
+ let text = String(characters[0...index])
|
|
|
+ label.text = text
|
|
|
+ label.attributedText = kMDAttributedString(text: text)
|
|
|
+
|
|
|
+ // 设置延迟,逐字显示
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
|
+ typeCharacter(at: index + 1) // 递归调用
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始逐字显示
|
|
|
+ typeCharacter(at: originalText.count)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+let kIsAIAnswering = "isAIAnswering"
|
|
|
public extension Notification.Name {
|
|
|
//AI 回答中通知
|
|
|
static let kAIAnsweringNotification = Self.init("kAIAnsweringNotification")
|
|
|
}
|
|
|
-let kIsAIAnswering = "isAIAnswering"
|
|
|
+
|
|
|
|