diff --git a/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj b/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj index e62acad..f699ad2 100644 --- a/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj +++ b/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj @@ -49,6 +49,10 @@ CB1F014F29E9B6BC009CF942 /* CodeHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F014E29E9B6BC009CF942 /* CodeHighlighter.swift */; }; CB1F015129E9BC8C009CF942 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F015029E9BC8C009CF942 /* Tokenizer.swift */; }; CB1F015429EAD9E9009CF942 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = CB1F015329EAD9E9009CF942 /* Introspect */; }; + CB1F015B29EAFBF5009CF942 /* MessageText+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F015729EAFBF5009CF942 /* MessageText+CoreDataClass.swift */; }; + CB1F015C29EAFBF5009CF942 /* MessageText+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F015829EAFBF5009CF942 /* MessageText+CoreDataProperties.swift */; }; + CB1F015F29EB9D05009CF942 /* Message+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F015D29EB9D05009CF942 /* Message+CoreDataClass.swift */; }; + CB1F016029EB9D05009CF942 /* Message+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F015E29EB9D05009CF942 /* Message+CoreDataProperties.swift */; }; CB1F1DD029DDBA0B008CFD0B /* AIPromptPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1F1DCF29DDBA0B008CFD0B /* AIPromptPopView.swift */; }; CB2449F829D721F3006EE829 /* SystemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2449F729D721F3006EE829 /* SystemManager.swift */; }; CB2449FA29D7FE38006EE829 /* ServerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2449F929D7FE38006EE829 /* ServerManager.swift */; }; @@ -66,8 +70,6 @@ CB2F972829CEFB65004EBD96 /* ChatRoomToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2F972729CEFB65004EBD96 /* ChatRoomToolBar.swift */; }; CB53A3BE29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */; }; CB53A3BF29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */; }; - CBC4B12329B8D28D00650296 /* Message+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC4B11D29B8D28D00650296 /* Message+CoreDataClass.swift */; }; - CBC4B12429B8D28D00650296 /* Message+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC4B11E29B8D28D00650296 /* Message+CoreDataProperties.swift */; }; CBD5AB6429E6DE9A007B6625 /* ProjectSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6329E6DE9A007B6625 /* ProjectSettingManager.swift */; }; CBD5AB6629E6EFE3007B6625 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6529E6EFE3007B6625 /* MarkdownView.swift */; }; CBD5AB6929E707A1007B6625 /* MarkdownTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6829E707A1007B6625 /* MarkdownTheme.swift */; }; @@ -115,6 +117,10 @@ CB1F014C29E9A4CC009CF942 /* MessageTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTextModel.swift; sourceTree = ""; }; CB1F014E29E9B6BC009CF942 /* CodeHighlighter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeHighlighter.swift; sourceTree = ""; }; CB1F015029E9BC8C009CF942 /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; + CB1F015729EAFBF5009CF942 /* MessageText+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageText+CoreDataClass.swift"; sourceTree = ""; }; + CB1F015829EAFBF5009CF942 /* MessageText+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageText+CoreDataProperties.swift"; sourceTree = ""; }; + CB1F015D29EB9D05009CF942 /* Message+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataClass.swift"; sourceTree = ""; }; + CB1F015E29EB9D05009CF942 /* Message+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataProperties.swift"; sourceTree = ""; }; CB1F1DCF29DDBA0B008CFD0B /* AIPromptPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIPromptPopView.swift; sourceTree = ""; }; CB228EA129CD4949006B3559 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; CB2449F729D721F3006EE829 /* SystemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManager.swift; sourceTree = ""; }; @@ -132,8 +138,6 @@ CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataClass.swift"; sourceTree = ""; }; CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataProperties.swift"; sourceTree = ""; }; CBC4B0FE29B8BF9600650296 /* OSXChatGPT.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = OSXChatGPT.xcdatamodel; sourceTree = ""; }; - CBC4B11D29B8D28D00650296 /* Message+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataClass.swift"; sourceTree = ""; }; - CBC4B11E29B8D28D00650296 /* Message+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataProperties.swift"; sourceTree = ""; }; CBD5AB6329E6DE9A007B6625 /* ProjectSettingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSettingManager.swift; sourceTree = ""; }; CBD5AB6529E6EFE3007B6625 /* MarkdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownView.swift; sourceTree = ""; }; CBD5AB6829E707A1007B6625 /* MarkdownTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTheme.swift; sourceTree = ""; }; @@ -330,12 +334,14 @@ CBC4B11429B8CB1B00650296 /* Models */ = { isa = PBXGroup; children = ( + CB1F015D29EB9D05009CF942 /* Message+CoreDataClass.swift */, + CB1F015E29EB9D05009CF942 /* Message+CoreDataProperties.swift */, + CB1F015729EAFBF5009CF942 /* MessageText+CoreDataClass.swift */, + CB1F015829EAFBF5009CF942 /* MessageText+CoreDataProperties.swift */, CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */, CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */, CB27656729D1E65100897E0E /* Conversation+CoreDataClass.swift */, CB27656829D1E65100897E0E /* Conversation+CoreDataProperties.swift */, - CBC4B11D29B8D28D00650296 /* Message+CoreDataClass.swift */, - CBC4B11E29B8D28D00650296 /* Message+CoreDataProperties.swift */, 182B43A329BF730300F06778 /* NSColor.swift */, ); path = Models; @@ -445,6 +451,7 @@ buildActionMask = 2147483647; files = ( CB1F012F29E999EF009CF942 /* Segment.swift in Sources */, + CB1F015C29EAFBF5009CF942 /* MessageText+CoreDataProperties.swift in Sources */, CB1F015129E9BC8C009CF942 /* Tokenizer.swift in Sources */, CB2449FA29D7FE38006EE829 /* ServerManager.swift in Sources */, CB1F014829E99B5E009CF942 /* Int+IsOdd.swift in Sources */, @@ -456,6 +463,7 @@ CB27656629D1DA9800897E0E /* AIPromptView.swift in Sources */, CB1F014929E99B5E009CF942 /* Sequence+Occurrences.swift in Sources */, 182B436B29BC5CBA00F06778 /* AppDelegate.swift in Sources */, + CB1F015B29EAFBF5009CF942 /* MessageText+CoreDataClass.swift in Sources */, 182B437329BC5D1B00F06778 /* CoreDataManager.swift in Sources */, 188FB46729C1FA9700E3C18F /* EidtSessionRemarkView.swift in Sources */, CB27656A29D1E65100897E0E /* Conversation+CoreDataProperties.swift in Sources */, @@ -463,6 +471,7 @@ CB1F014629E99B5E009CF942 /* String+IsNumber.swift in Sources */, 182B436629BC5C8700F06778 /* View.swift in Sources */, CB1F012929E995BA009CF942 /* MarkdownTextBuilder.swift in Sources */, + CB1F015F29EB9D05009CF942 /* Message+CoreDataClass.swift in Sources */, 182B437B29BC5FBE00F06778 /* EnterAPIView.swift in Sources */, CB0F5A5F29D059C4005B71D2 /* TextOutputFormat.swift in Sources */, 182B437929BC5D6200F06778 /* OSXChatGPTApp.swift in Sources */, @@ -470,6 +479,7 @@ CB28A52229C07BE500F0286A /* KeyboardMonitor.swift in Sources */, CB1F014A29E99B5E009CF942 /* Sequence+AnyOf.swift in Sources */, 182B437429BC5D1B00F06778 /* ViewModel.swift in Sources */, + CB1F016029EB9D05009CF942 /* Message+CoreDataProperties.swift in Sources */, CB1F014329E99B5E009CF942 /* Substring+HasSuffix.swift in Sources */, CB53A3BE29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift in Sources */, 182B436729BC5C8700F06778 /* ChatRoomView.swift in Sources */, @@ -478,7 +488,6 @@ CB27656929D1E65100897E0E /* Conversation+CoreDataClass.swift in Sources */, CB1F014729E99B5E009CF942 /* String+HTMLEntities.swift in Sources */, CB0F5A6029D059C4005B71D2 /* SplashCodeSyntaxHighlighter.swift in Sources */, - CBC4B12429B8D28D00650296 /* Message+CoreDataProperties.swift in Sources */, CB1F014D29E9A4CC009CF942 /* MessageTextModel.swift in Sources */, 182B436529BC5C8700F06778 /* SessionsView.swift in Sources */, CB1F014429E99B5E009CF942 /* String+PrefixChecking.swift in Sources */, @@ -496,7 +505,6 @@ CB2F972229CED6AE004EBD96 /* ChatRoomInputView.swift in Sources */, CB1F014529E99B5E009CF942 /* String+Removing.swift in Sources */, CB2F972829CEFB65004EBD96 /* ChatRoomToolBar.swift in Sources */, - CBC4B12329B8D28D00650296 /* Message+CoreDataClass.swift in Sources */, CB27657329D30F1400897E0E /* AIPromptViewMdoel.swift in Sources */, CB1F1DD029DDBA0B008CFD0B /* AIPromptPopView.swift in Sources */, CB1F013129E99A18009CF942 /* SyntaxRule.swift in Sources */, diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift b/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift index 00e7397..90b02e4 100644 --- a/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift +++ b/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift @@ -12,6 +12,7 @@ struct ChatGPTResponse { var request: ChatGPTRequest var state: State var text: String + var stream: String//流 enum State { case replyStart case replying @@ -287,39 +288,32 @@ extension ChatGPTManager { Task { do { let stream = try await httpClient.postStream(chatRequest: request) - let res = ChatGPTResponse(request: request ,state: .replyStart, text: "") + let res = ChatGPTResponse(request: request ,state: .replyStart, text: "", stream: "") await tempMessagePool.reset() if self.chatGPTSpeaking == false { return } - DispatchQueue.main.async { - complete?(res) - } + complete?(res) var newMsg: String = "" for try await line in stream { await tempMessagePool.append(line: line) let newMessage = await tempMessagePool.message - print("回复:\(newMessage)") + print("回复1:\(line)") + print("回复2:\(newMessage)") newMsg += newMessage - let res = ChatGPTResponse(request: request ,state: .replying, text: newMessage) - DispatchQueue.main.async { - complete?(res) - } + let res = ChatGPTResponse(request: request ,state: .replying, text: newMessage, stream: line) + complete?(res) } - let re = ChatGPTResponse(request: request ,state: .replyFinish, text: newMsg) + let re = ChatGPTResponse(request: request ,state: .replyFinish, text: newMsg, stream: newMsg) await tempMessagePool.reset() - DispatchQueue.main.async { - self.chatGPTSpeaking = false - complete?(re) - } + self.chatGPTSpeaking = false + complete?(re) }catch let err { await tempMessagePool.reset() if let error = err as? HTTPError { - let res = ChatGPTResponse(request: request ,state: .replyFial, text: error.message) - DispatchQueue.main.async { - self.chatGPTSpeaking = false - complete?(res) - } + let res = ChatGPTResponse(request: request ,state: .replyFial, text: error.message, stream: "") + self.chatGPTSpeaking = false + complete?(res) } } diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift b/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift index df2833a..cb2b91f 100644 --- a/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift +++ b/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift @@ -159,9 +159,6 @@ extension ViewModel { fetchMoreMessage(sesstionId: sesstionId) } func fetchMoreMessage(sesstionId: String) { - - - let request: NSFetchRequest = Message.fetchRequest() request.predicate = NSPredicate(format: "sesstionId == %@", sesstionId) let timestampSortDescriptor = NSSortDescriptor(key: "createdDate", ascending: false) @@ -174,6 +171,22 @@ extension ViewModel { if results.count == 0 { return } +// results.forEach { message in +// if let tempArray = message.textModel.array as? [MessageText] { +// tempArray.forEach { item in +// message.removeFromTextModel(item) +// } +// CoreDataManager.shared.delete(objects: tempArray) +// } +// if message.textModel.array.count == 0 { +// let arr = self.generateContent(message.text ?? "") +// arr.forEach { item in +// message.addToTextModel(item) +// } +// } +// } +// CoreDataManager.shared.saveData() + if messages.count == 0 { if let last = results.last { if last.role == ChatGPTManager.shared.gptRoleString { @@ -279,111 +292,195 @@ extension ViewModel { if ChatGPTManager.shared.answerType.valueBool { self.showStopAnswerBtn = true } + var stream: String = "" ChatGPTManager.shared.askChatGPTStream(messages: messages, prompt: prompt) { rsp in if rsp.request.answerType == .stream { isFeedback = true //流式请求 if rsp.state == .replyStart { - self.removeGptThinkMessage() - newMsg = Message(context: CoreDataManager.shared.container.viewContext) - newMsg?.sesstionId = sesstionId - newMsg?.role = ChatGPTManager.shared.gptRoleString - newMsg?.id = UUID() - newMsg?.createdDate = Date() - if self.currentConversation?.sesstionId == sesstionId { - self.messages.append(newMsg!) - } - CoreDataManager.shared.saveData() - self.updateConversation(sesstionId: sesstionId, message:newMsg) - if self.currentConversation?.sesstionId == sesstionId { - self.scrollID = newMsg!.id! - self.changeMsgText = ""//更新滚动 - } - }else if rsp.state == .replying { - self.scrollID = newMsg!.id! - newMsg?.text = rsp.text - if self.currentConversation?.sesstionId == sesstionId { - self.messages[self.messages.count - 1] = newMsg!//更新UI - self.changeMsgText = rsp.text//更新滚动 - } - }else if rsp.state == .replyFial { - self.removeGptThinkMessage() - if newMsg == nil { + stream = "" + DispatchQueue.main.async { + self.removeGptThinkMessage() newMsg = Message(context: CoreDataManager.shared.container.viewContext) newMsg?.sesstionId = sesstionId newMsg?.role = ChatGPTManager.shared.gptRoleString newMsg?.id = UUID() - newMsg?.msgType = .fialMsg newMsg?.createdDate = Date() + newMsg?.msgTextType = .none +// let textModel = MessageText(context: CoreDataManager.shared.container.viewContext) +// textModel.id = UUID() +// textModel.textType = .text +// newMsg?.addToTextModel(textModel) + + if self.currentConversation?.sesstionId == sesstionId { + self.messages.append(newMsg!) + } + CoreDataManager.shared.saveData() + self.updateConversation(sesstionId: sesstionId, message:newMsg) + if self.currentConversation?.sesstionId == sesstionId { + self.scrollID = newMsg!.id! + self.changeMsgText = ""//更新滚动 + } } + }else if rsp.state == .replying { + if newMsg == nil { + return + } + self.scrollID = newMsg!.id! newMsg?.text = rsp.text - //失败 - CoreDataManager.shared.saveData() - newMsg?.text = rsp.text - if self.currentConversation?.sesstionId == sesstionId { - if self.messages.count > 0 { +// stream += rsp.stream +// if stream.contains("```") {//有代码块 +// let arr = stream.components(separatedBy: "```") +// let first = arr.first ?? "" +// let second = arr.last ?? "" +// let textModel = newMsg?.textModel.array.last as? MessageText +// textModel?.text = first +// if newMsg!.msgTextType == .text || newMsg!.msgTextType == .none { +// //文本变代码块 +// textModel?.textType = .text +// textModel?.isFull = true +// textModel?.text = textModel?.text?.trimmingCharacters(in: .whitespacesAndNewlines) +// +// stream = second +// newMsg?.msgTextType = .code +// DispatchQueue.main.async { +// let codeModel = MessageText(context: CoreDataManager.shared.container.viewContext) +// codeModel.text = second +// codeModel.id = UUID() +// codeModel.textType = .code +// newMsg?.msgTextType = .code +// newMsg?.addToTextModel(codeModel) +//// CoreDataManager.shared.saveData() +// } +// }else if newMsg!.msgTextType == .code { +// //代码块变文本 +// textModel?.textType = .code +// textModel?.isFull = true +// textModel?.text = textModel?.text?.trimmingCharacters(in: .whitespacesAndNewlines) +// +// stream = second +// newMsg?.msgTextType = .text +// DispatchQueue.main.async { +// let codeModel = MessageText(context: CoreDataManager.shared.container.viewContext) +// codeModel.text = second +// codeModel.id = UUID() +// codeModel.textType = .text +// newMsg?.msgTextType = .text +// newMsg?.addToTextModel(codeModel) +//// CoreDataManager.shared.saveData() +// } +// } +// +// +// }else {//没有新代码块,则添加到最后一个 +// let textModel = newMsg?.textModel.array.last as? MessageText +// textModel?.text = stream +// } + + DispatchQueue.main.async { + if self.currentConversation?.sesstionId == sesstionId { self.messages[self.messages.count - 1] = newMsg!//更新UI + self.changeMsgText = rsp.text//更新滚动 } - self.changeMsgText = rsp.text//更新滚动 } - self.showStopAnswerBtn = false - updateBlock() + }else if rsp.state == .replyFial { + DispatchQueue.main.async { + self.removeGptThinkMessage() + if newMsg == nil { + newMsg = Message(context: CoreDataManager.shared.container.viewContext) + newMsg?.sesstionId = sesstionId + newMsg?.role = ChatGPTManager.shared.gptRoleString + newMsg?.id = UUID() + newMsg?.msgType = .fialMsg + newMsg?.createdDate = Date() + } + newMsg?.text = rsp.text + //失败 + CoreDataManager.shared.saveData() + newMsg?.text = rsp.text + if self.currentConversation?.sesstionId == sesstionId { + if self.messages.count > 0 { + self.messages[self.messages.count - 1] = newMsg!//更新UI + } + self.changeMsgText = rsp.text//更新滚动 + } + self.showStopAnswerBtn = false + updateBlock() + } }else if rsp.state == .replyFinish { - self.updateConversation(sesstionId: sesstionId, message:newMsg) - CoreDataManager.shared.saveData() - if self.currentConversation?.sesstionId == sesstionId { - self.changeMsgText = rsp.text//更新滚动 + DispatchQueue.main.async { +// let arr = self.generateContent(rsp.text) +// if let taskListTasks = newMsg?.textModel.array as? [MessageText] { +// taskListTasks.forEach({ item in +// newMsg?.removeFromTextModel(item) +// }) +// CoreDataManager.shared.delete(objects: taskListTasks) +// } +// arr.forEach { item in +// newMsg?.addToTextModel(item) +// } + self.updateConversation(sesstionId: sesstionId, message:newMsg) + CoreDataManager.shared.saveData() + if self.currentConversation?.sesstionId == sesstionId { + self.changeMsgText = rsp.text//更新滚动 + } + self.showStopAnswerBtn = false + updateBlock() } - self.showStopAnswerBtn = false - updateBlock() } }else { isFeedback = true //完整请求 if rsp.state == .replyStart { - self.removeGptThinkMessage() - newMsg = Message(context: CoreDataManager.shared.container.viewContext) - newMsg?.sesstionId = sesstionId - newMsg?.role = ChatGPTManager.shared.gptRoleString - newMsg?.id = UUID() - newMsg?.createdDate = Date() - CoreDataManager.shared.saveData() - self.updateConversation(sesstionId: sesstionId, message:newMsg) - if self.currentConversation?.sesstionId == sesstionId { - self.scrollID = newMsg!.id! - self.messages.append(newMsg!) - self.changeMsgText = rsp.text//更新滚动 - } - }else if rsp.state == .replyFial { - //失败 - if newMsg == nil { + DispatchQueue.main.async { + self.removeGptThinkMessage() newMsg = Message(context: CoreDataManager.shared.container.viewContext) newMsg?.sesstionId = sesstionId newMsg?.role = ChatGPTManager.shared.gptRoleString newMsg?.id = UUID() - newMsg?.msgType = .fialMsg newMsg?.createdDate = Date() - } - newMsg?.text = rsp.text - CoreDataManager.shared.saveData() - if self.currentConversation?.sesstionId == sesstionId { - self.scrollID = newMsg!.id! + CoreDataManager.shared.saveData() self.updateConversation(sesstionId: sesstionId, message:newMsg) - self.messages[self.messages.count - 1] = newMsg!//更新UI - self.changeMsgText = rsp.text//更新滚动 + if self.currentConversation?.sesstionId == sesstionId { + self.scrollID = newMsg!.id! + self.messages.append(newMsg!) + self.changeMsgText = rsp.text//更新滚动 + } + } + }else if rsp.state == .replyFial { + DispatchQueue.main.async { + //失败 + if newMsg == nil { + newMsg = Message(context: CoreDataManager.shared.container.viewContext) + newMsg?.sesstionId = sesstionId + newMsg?.role = ChatGPTManager.shared.gptRoleString + newMsg?.id = UUID() + newMsg?.msgType = .fialMsg + newMsg?.createdDate = Date() + } + newMsg?.text = rsp.text + CoreDataManager.shared.saveData() + if self.currentConversation?.sesstionId == sesstionId { + self.scrollID = newMsg!.id! + self.updateConversation(sesstionId: sesstionId, message:newMsg) + self.messages[self.messages.count - 1] = newMsg!//更新UI + self.changeMsgText = rsp.text//更新滚动 + } + updateBlock() } - updateBlock() }else if rsp.state == .replyFinish { - //成功 - newMsg?.text = rsp.text - CoreDataManager.shared.saveData() - if self.currentConversation?.sesstionId == sesstionId { - self.scrollID = newMsg!.id! - self.updateConversation(sesstionId: sesstionId, message:newMsg) - self.messages[self.messages.count - 1] = newMsg!//更新UI - self.changeMsgText = rsp.text//更新滚动 + DispatchQueue.main.async { + //成功 + newMsg?.text = rsp.text + CoreDataManager.shared.saveData() + if self.currentConversation?.sesstionId == sesstionId { + self.scrollID = newMsg!.id! + self.updateConversation(sesstionId: sesstionId, message:newMsg) + self.messages[self.messages.count - 1] = newMsg!//更新UI + self.changeMsgText = rsp.text//更新滚动 + } + updateBlock() } - updateBlock() } } @@ -424,6 +521,82 @@ extension ViewModel { return Int64(Date().timeIntervalSince1970) } } + + private func generateContent(_ content:String) -> [MessageText] { + var array: [MessageText] = [] + if !content.contains("```") { + let codeModel = MessageText(context: CoreDataManager.shared.container.viewContext) + codeModel.text = content + codeModel.id = UUID() + codeModel.textType = .text + array.append(codeModel) + }else { + let components = content.components(separatedBy: "```") + var idx: Int = 0// 0: code, 1: text + for (index, str) in components.enumerated() { + if index == 0 { + if (content.hasPrefix("```")) { + idx = 0 + }else { + idx = 1 + } + if idx == 0 { + let model = self.createMessageCodeModel(text: str) + array.append(model) + + }else { + let content = str.trimmingCharacters(in: .whitespacesAndNewlines) + let codeModel = MessageText(context: CoreDataManager.shared.container.viewContext) + codeModel.text = content + codeModel.id = UUID() + codeModel.textType = .text + array.append(codeModel) + } + if idx == 0 { + idx = 1 + }else { + idx = 0 + } + + }else { + if idx == 0 { + let model = self.createMessageCodeModel(text: str) + array.append(model) + }else { + let content = str.trimmingCharacters(in: .whitespacesAndNewlines) + let codeModel = MessageText(context: CoreDataManager.shared.container.viewContext) + codeModel.text = content + codeModel.id = UUID() + codeModel.textType = .text + array.append(codeModel) + } + if idx == 0 { + idx = 1 + }else { + idx = 0 + } + } + + } + } + return array + } + + private func createMessageCodeModel(text: String) -> MessageText { + let lines = text.components(separatedBy: "\n") + let secondLine = lines[0] + var content = String(text) + let startIndex = text.index(text.startIndex, offsetBy: 0) + let endIndex = text.index(text.startIndex, offsetBy: secondLine.count) + let range = startIndex.. - init(_ content: String, theme: MarkdownTheme) { - self.content = content + init(_ textModel: [MessageText], theme: MarkdownTheme) { + self.textModel = textModel self.theme = theme highlighter = CodeHighlighter(format: MarkdownAttTextBuilder(theme: theme)) + self.textModel.forEach { model in + if model.textType == .code { + model.attString = highlighter.highlight(model.text ?? "") + } + } } + var body: some View { VStack(alignment: .leading, spacing: 15) { - ForEach(self.generateContent(), id: \.self) { element in - if element.type == .code { -// ScrollView(.horizontal) { -// } + ForEach(textModel, id: \.id) { model in + if model.textType == .code { HStack(spacing: 15) { - Text(element.attText) + Text(model.attString) .font(theme.font) .fixedSize(horizontal: false, vertical: true) .padding() .lineSpacing(1.5) .minimumScaleFactor(1) - + Spacer() }.allowsHitTesting(false) .background(theme.backgroundColor) .cornerRadius(10) - }else { - Text(element.text) + Text(model.text ?? "") .font(theme.font) .lineSpacing(1.5) .foregroundColor(theme.plainTextColor) } } } +// VStack(alignment: .leading, spacing: 15) { +// ForEach(textModel, id: \.id) { element in +// if element.type == .code { +//// ScrollView(.horizontal) {//开启水平滚动,会导致列表无法滚动 +//// } +// HStack(spacing: 15) { +// Text(element.attText) +// .font(theme.font) +// .fixedSize(horizontal: false, vertical: true) +// .padding() +// .lineSpacing(1.5) +// .minimumScaleFactor(1) +// +// Spacer() +// }.allowsHitTesting(false) +// .background(theme.backgroundColor) +// .cornerRadius(10) +// +// }else { +// Text(element.text) +// .font(theme.font) +// .lineSpacing(1.5) +// .foregroundColor(theme.plainTextColor) +// } +// } +// } } - private func generateContent() -> [MessageTextModel] { - var array: [MessageTextModel] = [] - if !content.contains("```") { - let model = MessageTextModel(type: .text, text: content) - array.append(model) - }else { - let components = content.components(separatedBy: "```") - var idx: Int = 0// 0: code, 1: text - for (index, str) in components.enumerated() { - if index == 0 { - if (content.hasPrefix("```")) { - idx = 0 - }else { - idx = 1 - } - if idx == 0 { - var model = createMessageCodeModel(text: String(str)) - model.attText = highlighter.highlight(model.text) - array.append(model) - }else { - let content = str.trimmingCharacters(in: .whitespacesAndNewlines) - let model = MessageTextModel(type: .text, text: content) - array.append(model) - } - if idx == 0 { - idx = 1 - }else { - idx = 0 - } - - }else { - if idx == 0 { - var model = createMessageCodeModel(text: String(str)) - model.attText = highlighter.highlight(model.text) - array.append(model) - }else { - let content = str.trimmingCharacters(in: .whitespacesAndNewlines) - let model = MessageTextModel(type: .text, text: content) - array.append(model) - } - if idx == 0 { - idx = 1 - }else { - idx = 0 - } - } - - } - } - return array - } - - private func createMessageCodeModel(text: String) -> MessageTextModel { - let lines = text.components(separatedBy: "\n") - let secondLine = lines[0] - var content = String(text) - let startIndex = text.index(text.startIndex, offsetBy: 0) - let endIndex = text.index(text.startIndex, offsetBy: secondLine.count) - let range = startIndex.. [MessageTextModel] { +// var array: [MessageTextModel] = [] +// if !content.contains("```") { +// let model = MessageTextModel(type: .text, text: content) +// array.append(model) +// }else { +// let components = content.components(separatedBy: "```") +// var idx: Int = 0// 0: code, 1: text +// for (index, str) in components.enumerated() { +// if index == 0 { +// if (content.hasPrefix("```")) { +// idx = 0 +// }else { +// idx = 1 +// } +// if idx == 0 { +// var model = createMessageCodeModel(text: String(str)) +// model.attText = highlighter.highlight(model.text) +// array.append(model) +// }else { +// let content = str.trimmingCharacters(in: .whitespacesAndNewlines) +// let model = MessageTextModel(type: .text, text: content) +// array.append(model) +// } +// if idx == 0 { +// idx = 1 +// }else { +// idx = 0 +// } +// +// }else { +// if idx == 0 { +// var model = createMessageCodeModel(text: String(str)) +// model.attText = highlighter.highlight(model.text) +// array.append(model) +// }else { +// let content = str.trimmingCharacters(in: .whitespacesAndNewlines) +// let model = MessageTextModel(type: .text, text: content) +// array.append(model) +// } +// if idx == 0 { +// idx = 1 +// }else { +// idx = 0 +// } +// } +// +// } +// } +// return array +// } +// +// private func createMessageCodeModel(text: String) -> MessageTextModel { +// let lines = text.components(separatedBy: "\n") +// let secondLine = lines[0] +// var content = String(text) +// let startIndex = text.index(text.startIndex, offsetBy: 0) +// let endIndex = text.index(text.startIndex, offsetBy: secondLine.count) +// let range = startIndex..(entityName: "Message") } - @NSManaged public var sesstionId: String + @NSManaged public var createdDate: Date? @NSManaged public var id: UUID? - @NSManaged public var type: Int16 - @NSManaged public var text: String? @NSManaged public var role: String? - @NSManaged public var createdDate: Date? - + @NSManaged public var sesstionId: String? + @NSManaged public var text: String? + @NSManaged public var type: Int16 + @NSManaged public var textType: Int16 + @NSManaged public var textModel: NSOrderedSet + +} + +// MARK: Generated accessors for textModel +extension Message { + + @objc(insertObject:inTextModelAtIndex:) + @NSManaged public func insertIntoTextModel(_ value: MessageText, at idx: Int) + + @objc(removeObjectFromTextModelAtIndex:) + @NSManaged public func removeFromTextModel(at idx: Int) + + @objc(insertTextModel:atIndexes:) + @NSManaged public func insertIntoTextModel(_ values: [MessageText], at indexes: NSIndexSet) + + @objc(removeTextModelAtIndexes:) + @NSManaged public func removeFromTextModel(at indexes: NSIndexSet) + + @objc(replaceObjectInTextModelAtIndex:withObject:) + @NSManaged public func replaceTextModel(at idx: Int, with value: MessageText) + + @objc(replaceTextModelAtIndexes:withTextModel:) + @NSManaged public func replaceTextModel(at indexes: NSIndexSet, with values: [MessageText]) + + @objc(addTextModelObject:) + @NSManaged public func addToTextModel(_ value: MessageText) + + @objc(removeTextModelObject:) + @NSManaged public func removeFromTextModel(_ value: MessageText) + + @objc(addTextModel:) + @NSManaged public func addToTextModel(_ values: NSOrderedSet) + + @objc(removeTextModel:) + @NSManaged public func removeFromTextModel(_ values: NSOrderedSet) + } extension Message : Identifiable { - + } diff --git a/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataClass.swift b/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataClass.swift new file mode 100644 index 0000000..b6b7774 --- /dev/null +++ b/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataClass.swift @@ -0,0 +1,23 @@ +// +// MessageText+CoreDataClass.swift +// OSXChatGPT +// +// Created by CoderChan on 2023/4/15. +// +// + +import Foundation +import CoreData + + +public class MessageText: NSManagedObject { + var attString: AttributedString = "" + var textType: MessageTextType { + get { + return MessageTextType(rawValue: type) ?? .none + } + set { + type = newValue.rawValue + } + } +} diff --git a/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataProperties.swift b/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataProperties.swift new file mode 100644 index 0000000..98ece62 --- /dev/null +++ b/OSXChatGPT/OSXChatGPT/Models/MessageText+CoreDataProperties.swift @@ -0,0 +1,28 @@ +// +// MessageText+CoreDataProperties.swift +// OSXChatGPT +// +// Created by CoderChan on 2023/4/15. +// +// + +import Foundation +import CoreData + + +extension MessageText { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "MessageText") + } + + @NSManaged public var id: UUID? + @NSManaged public var text: String? + @NSManaged public var type: Int16 + @NSManaged public var isFull: Bool + +} + +extension MessageText : Identifiable { + +} diff --git a/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents b/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents index 4fdf32a..4dc940a 100644 --- a/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents +++ b/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents @@ -14,6 +14,14 @@ + + + + + + + + diff --git a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift index 67c189e..102bed7 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift @@ -41,7 +41,7 @@ struct ChatRoomView: View { ForEach(viewModel.messages, id: \.id) { message in ChatRoomCellView(message: message).environmentObject(viewModel) .id(message.id) // 添加唯一标识符 - .padding(EdgeInsets(top: 10, leading: 10, bottom: 5, trailing: 10)) + .padding(EdgeInsets(top: 10, leading: 0, bottom: 5, trailing: 0)) .onAppear { if viewModel.messages.count > 1 { if viewModel.messages[1] == message && self.isOnAppear { @@ -51,6 +51,8 @@ struct ChatRoomView: View { } } } + + .padding(.bottom, 10) } .removeBackground() .onChange(of: viewModel.changeMsgText) { _ in @@ -191,7 +193,7 @@ struct ChatRoomCellView: View { .cornerRadius(6) } else { -// MarkdownView(message.text ?? "", theme: markdownTheme)//自定义 +// MarkdownView(message.textModel.array as? [MessageText] ?? [], theme: markdownTheme)//自定义 MarkdownContentView { Markdown(message.text ?? "") .padding(12)