From 3bdd1d2a04daf53b4397be97bd6046cfdcbe381a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E8=BF=9E=E8=BE=B0?= Date: Thu, 30 Mar 2023 00:52:19 +0800 Subject: [PATCH] add prompt --- .../OSXChatGPT.xcodeproj/project.pbxproj | 20 +++-- .../DataProvider/AIPromptDataManager.swift | 38 +++++++++ .../DataProvider/AIPromptViewMdoel.swift | 82 +++++++++++++++++- .../DataProvider/ChatGPTManager.swift | 10 ++- .../OSXChatGPT/DataProvider/ViewModel.swift | 28 +++++-- OSXChatGPT/OSXChatGPT/Models/NSColor.swift | 27 ++++++ .../Models/Prompt+CoreDataClass.swift | 15 +++- .../Models/Prompt+CoreDataProperties.swift | 10 ++- .../OSXChatGPT.xcdatamodel/contents | 2 + .../WindowView/AIPromptInputView.swift | 66 ++++++++------- .../OSXChatGPT/WindowView/AIPromptView.swift | 81 +++++++++++------- .../WindowView/ChatRoomInputView.swift | 2 +- .../WindowView/ChatRoomToolBar.swift | 4 +- .../OSXChatGPT/WindowView/ChatRoomView.swift | 83 +++---------------- .../OSXChatGPT/WindowView/SessionsView.swift | 53 +++++++----- 15 files changed, 342 insertions(+), 179 deletions(-) create mode 100644 OSXChatGPT/OSXChatGPT/DataProvider/AIPromptDataManager.swift diff --git a/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj b/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj index 13a9fc6..82aa0c6 100644 --- a/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj +++ b/OSXChatGPT/OSXChatGPT.xcodeproj/project.pbxproj @@ -33,8 +33,6 @@ CB27656629D1DA9800897E0E /* AIPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27656529D1DA9800897E0E /* AIPromptView.swift */; }; CB27656929D1E65100897E0E /* Conversation+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27656729D1E65100897E0E /* Conversation+CoreDataClass.swift */; }; CB27656A29D1E65100897E0E /* Conversation+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27656829D1E65100897E0E /* Conversation+CoreDataProperties.swift */; }; - CB27656D29D1E6A100897E0E /* Prompt+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27656B29D1E6A100897E0E /* Prompt+CoreDataClass.swift */; }; - CB27656E29D1E6A100897E0E /* Prompt+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27656C29D1E6A100897E0E /* Prompt+CoreDataProperties.swift */; }; CB27657329D30F1400897E0E /* AIPromptViewMdoel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27657229D30F1400897E0E /* AIPromptViewMdoel.swift */; }; CB27657529D33D7A00897E0E /* AIPromptInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB27657429D33D7A00897E0E /* AIPromptInputView.swift */; }; CB28A52229C07BE500F0286A /* KeyboardMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB28A52129C07BE500F0286A /* KeyboardMonitor.swift */; }; @@ -43,6 +41,9 @@ CB2F972029CE1ADC004EBD96 /* OSXChatGPT.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CBC4B0FD29B8BF9600650296 /* OSXChatGPT.xcdatamodeld */; }; CB2F972229CED6AE004EBD96 /* ChatRoomInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2F972129CED6AE004EBD96 /* ChatRoomInputView.swift */; }; CB2F972829CEFB65004EBD96 /* ChatRoomToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2F972729CEFB65004EBD96 /* ChatRoomToolBar.swift */; }; + CB53A3BB29D4795300A5B8FC /* AIPromptDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB53A3BA29D4795300A5B8FC /* AIPromptDataManager.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 */; }; /* End PBXBuildFile section */ @@ -74,14 +75,15 @@ CB27656529D1DA9800897E0E /* AIPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIPromptView.swift; sourceTree = ""; }; CB27656729D1E65100897E0E /* Conversation+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataClass.swift"; sourceTree = ""; }; CB27656829D1E65100897E0E /* Conversation+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataProperties.swift"; sourceTree = ""; }; - CB27656B29D1E6A100897E0E /* Prompt+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataClass.swift"; sourceTree = ""; }; - CB27656C29D1E6A100897E0E /* Prompt+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataProperties.swift"; sourceTree = ""; }; CB27657229D30F1400897E0E /* AIPromptViewMdoel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIPromptViewMdoel.swift; sourceTree = ""; }; CB27657429D33D7A00897E0E /* AIPromptInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIPromptInputView.swift; sourceTree = ""; }; CB28A52129C07BE500F0286A /* KeyboardMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardMonitor.swift; sourceTree = ""; }; CB28A52729C1569900F0286A /* ThinkingAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThinkingAnimationView.swift; sourceTree = ""; }; CB2F972129CED6AE004EBD96 /* ChatRoomInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomInputView.swift; sourceTree = ""; }; CB2F972729CEFB65004EBD96 /* ChatRoomToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomToolBar.swift; sourceTree = ""; }; + CB53A3BA29D4795300A5B8FC /* AIPromptDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIPromptDataManager.swift; sourceTree = ""; }; + 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 = ""; }; @@ -155,6 +157,7 @@ 182B437029BC5D1B00F06778 /* ViewModel.swift */, CB28A52129C07BE500F0286A /* KeyboardMonitor.swift */, CB27657229D30F1400897E0E /* AIPromptViewMdoel.swift */, + CB53A3BA29D4795300A5B8FC /* AIPromptDataManager.swift */, ); path = DataProvider; sourceTree = ""; @@ -214,8 +217,8 @@ CBC4B11429B8CB1B00650296 /* Models */ = { isa = PBXGroup; children = ( - CB27656B29D1E6A100897E0E /* Prompt+CoreDataClass.swift */, - CB27656C29D1E6A100897E0E /* Prompt+CoreDataProperties.swift */, + CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */, + CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */, CB27656729D1E65100897E0E /* Conversation+CoreDataClass.swift */, CB27656829D1E65100897E0E /* Conversation+CoreDataProperties.swift */, CBC4B11D29B8D28D00650296 /* Message+CoreDataClass.swift */, @@ -320,15 +323,16 @@ 182B437929BC5D6200F06778 /* OSXChatGPTApp.swift in Sources */, CB28A52229C07BE500F0286A /* KeyboardMonitor.swift in Sources */, 182B437429BC5D1B00F06778 /* ViewModel.swift in Sources */, + CB53A3BE29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift in Sources */, 182B436729BC5C8700F06778 /* ChatRoomView.swift in Sources */, CB27656929D1E65100897E0E /* Conversation+CoreDataClass.swift in Sources */, - CB27656E29D1E6A100897E0E /* Prompt+CoreDataProperties.swift in Sources */, CB0F5A6029D059C4005B71D2 /* SplashCodeSyntaxHighlighter.swift in Sources */, CBC4B12429B8D28D00650296 /* Message+CoreDataProperties.swift in Sources */, 182B436529BC5C8700F06778 /* SessionsView.swift in Sources */, CB27657529D33D7A00897E0E /* AIPromptInputView.swift in Sources */, - CB27656D29D1E6A100897E0E /* Prompt+CoreDataClass.swift in Sources */, + CB53A3BB29D4795300A5B8FC /* AIPromptDataManager.swift in Sources */, 182B437229BC5D1B00F06778 /* HTTPClient.swift in Sources */, + CB53A3BF29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift in Sources */, 182B436829BC5C8700F06778 /* MainContentView.swift in Sources */, CB28A52829C1569900F0286A /* ThinkingAnimationView.swift in Sources */, 182B437529BC5D1B00F06778 /* ChatGPTManager.swift in Sources */, diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptDataManager.swift b/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptDataManager.swift new file mode 100644 index 0000000..595cbaa --- /dev/null +++ b/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptDataManager.swift @@ -0,0 +1,38 @@ +// +// AIPromptDataManager.swift +// OSXChatGPT +// +// Created by CoderChan on 2023/3/29. +// + +import Foundation + + +class AIPromptDataManager { + static var shared = AIPromptDataManager() + + var allPrompts: [Prompt] = [] + + + init() { + fetchAllPrompts() + } + + + func addPrompt(prompt: Prompt) { + + } + + func deletePrompt(prompt: Prompt) { + + } + +} + +extension AIPromptDataManager { + private func fetchAllPrompts() { + let completedDateSort = NSSortDescriptor(keyPath: \Prompt.createdDate, ascending: false) + let aa: [Prompt] = CoreDataManager.shared.fetch("Prompt", sorting: [completedDateSort]) + allPrompts = aa + } +} diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptViewMdoel.swift b/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptViewMdoel.swift index 5e7bd37..7616f01 100644 --- a/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptViewMdoel.swift +++ b/OSXChatGPT/OSXChatGPT/DataProvider/AIPromptViewMdoel.swift @@ -8,8 +8,76 @@ import Foundation import SwiftUI +class AIPromptSessionViewMdoel: ObservableObject { + @Published var allPrompts: [Prompt] = [] + @Published var selectedItem :Prompt? + + func fetchAllPrompts(session: Conversation) { + let completedDateSort = NSSortDescriptor(keyPath: \Prompt.serial, ascending: false) + var aa: [Prompt] = CoreDataManager.shared.fetch("Prompt", sorting: [completedDateSort]) + + if (aa.count == 0) { + aa = AIPromptSessionViewMdoel.createDefaultPrompt() + } + + if let prompt = session.prompt { + if aa.contains(where: {$0.id == prompt.id}) { + selectedItem = prompt + } + } + + aa.removeAll(where: {$0.type == 1}) + let prompt = Prompt(context: CoreDataManager.shared.container.viewContext) + prompt.type = 1 + prompt.id = UUID() + prompt.createdDate = Date() + prompt.hexColor = "#999999" + aa.insert(prompt, at: 0) + if selectedItem == nil { + selectedItem = prompt + } + + CoreDataManager.shared.saveData() + allPrompts = aa + } + + func deletePrompt(prompt: Prompt) { + if prompt.type == 1 { + return + } + allPrompts.removeAll(where: {$0.id == prompt.id}) + if prompt.id == selectedItem?.id { + selectedItem = allPrompts.first + } + CoreDataManager.shared.delete(object: prompt) + } + + static func createDefaultPrompt() -> [Prompt] { + var temp: [Prompt] = [] + let prompt1 = Prompt(context: CoreDataManager.shared.container.viewContext) + prompt1.id = UUID() + prompt1.createdDate = Date() + prompt1.title = "翻译" + prompt1.prompt = "翻译我说的任何中文或英文。只返回翻译结果,不解释它" + prompt1.hexColor = NSColor.randomColor().toHexString() + temp.append(prompt1) + + let prompt2 = Prompt(context: CoreDataManager.shared.container.viewContext) + prompt2.id = UUID() + prompt2.createdDate = Date() + prompt2.title = "iOS开发者" + prompt2.prompt = "假设你是一名iOS开发者,使用的是swift语言" + prompt2.hexColor = NSColor.randomColor().toHexString() + temp.append(prompt2) + + + CoreDataManager.shared.saveData() + return temp + } + +} -class AIPromptViewMdoel { +class AIPromptViewMdoel: ObservableObject { let isSession: Bool @Published var allPrompts: [Prompt] = [] @Published var prompts: [Prompt] = [] @@ -31,6 +99,18 @@ class AIPromptViewMdoel { } + func addPrompt(title: String, content: String) { + let prompt = Prompt(context: CoreDataManager.shared.container.viewContext) + prompt.title = title + prompt.prompt = content + prompt.id = UUID() + prompt.createdDate = Date() + CoreDataManager.shared.saveData() + +// AIPromptDataManager.shared.addPrompt(prompt: prompt) + } + + } extension AIPromptViewMdoel { diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift b/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift index 6dd834c..84317b7 100644 --- a/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift +++ b/OSXChatGPT/OSXChatGPT/DataProvider/ChatGPTManager.swift @@ -26,6 +26,7 @@ struct ChatGPTRequest { let messages: [Message] let answerType: ChatGPTAnswerType//是否流式回答 let contextCount: ChatGPTContext + let systemMsg: String?//修饰语 // static let completions = URL(string: "https://api.openai.com/v1/completions")! // static let images = URL(string: "https://api.openai.com/v1/images/generations")! @@ -62,6 +63,10 @@ struct ChatGPTRequest { temp.append(["role": msg.role ?? "user", "content": msg.text ?? ""]) } } + if let system = systemMsg { + let sys = ["role":"system", "content":system] + temp.insert(sys, at: 0) + } return temp } } @@ -270,12 +275,13 @@ extension ChatGPTManager { } - func askChatGPTStream(messages: [Message], complete:((ChatGPTResponse) -> ())?) { + func askChatGPTStream(messages: [Message], prompt: String?, complete:((ChatGPTResponse) -> ())?) { if chatGPTSpeaking == true { return } chatGPTSpeaking = true - let request = ChatGPTRequest(model: model, messages: messages, answerType: answerType, contextCount: askContextCount, apiKey: apiKey) + let request = ChatGPTRequest(model: model, messages: messages, answerType: answerType, contextCount: askContextCount, systemMsg: prompt, apiKey: apiKey) + Task { do { let stream = try await httpClient.postStream(chatRequest: request) diff --git a/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift b/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift index e63cd30..82e2d79 100644 --- a/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift +++ b/OSXChatGPT/OSXChatGPT/DataProvider/ViewModel.swift @@ -38,6 +38,22 @@ import Splash init() { fetchConversations() } + func updateConversation(sesstionId: String, prompt: Prompt) { + var con = fetchConversation(sesstionId: sesstionId) + if con == nil { + con = Conversation(context: CoreDataManager.shared.container.viewContext) + con?.sesstionId = sesstionId + con?.id = UUID() + } + con!.updateData = Date() + con?.prompt = prompt + CoreDataManager.shared.saveData() + if let index = conversations.firstIndex(where: { $0.sesstionId == sesstionId}) { + conversations[index] = con! + } else { + conversations.insert(con!, at: 0) + } + } func updateConversation(sesstionId: String, message: Message?) { var con = fetchConversation(sesstionId: sesstionId) if con == nil { @@ -192,16 +208,16 @@ extension ViewModel { CoreDataManager.shared.delete(objects: msg) } - func resendMessage(sesstionId: String) { + func resendMessage(sesstionId: String, prompt: String?) { if let lastMsg = messages.last { messages.removeAll(where: {$0.id == lastMsg.id }) CoreDataManager.shared.delete(objects: [lastMsg]) } - sendMessage(sesstionId: sesstionId, messages: messages) { + sendMessage(sesstionId: sesstionId, messages: messages, prompt:prompt) { } } - func addNewMessage(sesstionId: String, text: String, role: String, updateBlock: @escaping(() -> ())) { + func addNewMessage(sesstionId: String, text: String, role: String, prompt: String?, updateBlock: @escaping(() -> ())) { if sesstionId.isEmpty { return } @@ -222,7 +238,7 @@ extension ViewModel { print("发送问题:\(text)") var sendMsgs = messages sendMsgs.removeAll(where: {$0.type == 1}) - sendMessage(sesstionId: sesstionId, messages: sendMsgs, updateBlock: updateBlock) + sendMessage(sesstionId: sesstionId, messages: sendMsgs, prompt: prompt, updateBlock: updateBlock) } func cancel() { ChatGPTManager.shared.stopResponse() @@ -239,13 +255,13 @@ extension ViewModel { } } } - private func sendMessage(sesstionId: String, messages: [Message], updateBlock: @escaping(() -> ())) { + private func sendMessage(sesstionId: String, messages: [Message], prompt: String?, updateBlock: @escaping(() -> ())) { var isFeedback = false var newMsg: Message? if ChatGPTManager.shared.answerType.valueBool { self.showStopAnswerBtn = true } - ChatGPTManager.shared.askChatGPTStream(messages: messages) { rsp in + ChatGPTManager.shared.askChatGPTStream(messages: messages, prompt: prompt) { rsp in if rsp.request.answerType == .stream { isFeedback = true //流式请求 diff --git a/OSXChatGPT/OSXChatGPT/Models/NSColor.swift b/OSXChatGPT/OSXChatGPT/Models/NSColor.swift index 1ae4b89..b306e2d 100644 --- a/OSXChatGPT/OSXChatGPT/Models/NSColor.swift +++ b/OSXChatGPT/OSXChatGPT/Models/NSColor.swift @@ -18,10 +18,37 @@ extension NSColor { self.init(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: alpha) } + convenience init(hex: String, alpha: CGFloat = 1) { + var hexString = hex + if hexString.hasPrefix("#") { + hexString.remove(at: hexString.startIndex) + } + let scanner = Scanner(string: hexString) + scanner.scanLocation = 0 + var rgbValue: UInt64 = 0 + scanner.scanHexInt64(&rgbValue) + let red = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0 + let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0 + let blue = CGFloat(rgbValue & 0x0000FF) / 255.0 + self.init(red: red, green: green, blue: blue, alpha: alpha) + } + static func randomColor() -> NSColor { let r = CGFloat(arc4random_uniform(256)) let g = CGFloat(arc4random_uniform(256)) let b = CGFloat(arc4random_uniform(256)) return NSColor(r: r, g: g, b: b) } + + func toHexString() -> String { + + + + let redValue = Int(self.redComponent * 255) + let greenValue = Int(self.greenComponent * 255) + let blueValue = Int(self.blueComponent * 255) + let hexString = String(format: "#%02X%02X%02X", redValue, greenValue, blueValue) + return hexString + } + } diff --git a/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataClass.swift b/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataClass.swift index 8ea23b9..21ff281 100644 --- a/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataClass.swift +++ b/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataClass.swift @@ -2,14 +2,25 @@ // Prompt+CoreDataClass.swift // OSXChatGPT // -// Created by CoderChan on 2023/3/27. +// Created by CoderChan on 2023/3/29. // // import Foundation import CoreData +import SwiftUI public class Prompt: NSManagedObject { - + var color: Color { + if let hex = hexColor { + return NSColor(hex: hex).toColor() + }else { + let hex = NSColor.randomColor().toHexString() + hexColor = hex + CoreDataManager.shared.saveData() + return NSColor(hex: hex).toColor() + } + + } } diff --git a/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataProperties.swift b/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataProperties.swift index 2959675..f7923db 100644 --- a/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataProperties.swift +++ b/OSXChatGPT/OSXChatGPT/Models/Prompt+CoreDataProperties.swift @@ -2,7 +2,7 @@ // Prompt+CoreDataProperties.swift // OSXChatGPT // -// Created by CoderChan on 2023/3/27. +// Created by CoderChan on 2023/3/29. // // @@ -16,12 +16,14 @@ extension Prompt { return NSFetchRequest(entityName: "Prompt") } - @NSManaged public var title: String? - @NSManaged public var prompt: String? @NSManaged public var author: String? - @NSManaged public var id: UUID? @NSManaged public var createdDate: Date? + @NSManaged public var id: UUID? + @NSManaged public var prompt: String? @NSManaged public var serial: Int64 + @NSManaged public var title: String? + @NSManaged public var type: Int16 + @NSManaged public var hexColor: String? @NSManaged public var sesstion: NSSet? } diff --git a/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents b/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents index 566fc28..3c77a2e 100644 --- a/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents +++ b/OSXChatGPT/OSXChatGPT/OSXChatGPT.xcdatamodeld/OSXChatGPT.xcdatamodel/contents @@ -19,10 +19,12 @@ + + \ No newline at end of file diff --git a/OSXChatGPT/OSXChatGPT/WindowView/AIPromptInputView.swift b/OSXChatGPT/OSXChatGPT/WindowView/AIPromptInputView.swift index b5fb091..a0132eb 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/AIPromptInputView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/AIPromptInputView.swift @@ -8,6 +8,7 @@ import SwiftUI struct AIPromptInputView: View { + @StateObject var viewModel = AIPromptViewMdoel(isSession: true) @Binding var isPresented: Bool @State private var title: String = "" @State private var prompt: String = "" @@ -56,7 +57,7 @@ struct AIPromptInputView: View { .padding(.trailing, 20) HStack { - Text("Prompt") + Text("修饰语") .font(.title3) .padding(.top, 5) .padding(.leading, 20) @@ -81,41 +82,41 @@ struct AIPromptInputView: View { }.padding(.leading, 20) .padding(.trailing, 20) - HStack { - Text("作者") - .font(.title3) - .padding(.top, 5) - .padding(.leading, 20) - .padding(.bottom, 0) - .frame(height: 18) - Text("(选填)可分享") - .font(Font.system(size: 11)) - .padding(.top, 5) - .foregroundColor(.gray.opacity(0.6)) - .frame(height: 18) - Spacer() - }.padding(.top, 5) - HStack { - TextField("", text: $author) - .accentColor(nil) - .textFieldStyle(PlainTextFieldStyle()) - .font(Font.system(size: 14)) - .padding(10) - .background(Color.white) - .cornerRadius(8) - .frame(minWidth: 0, maxWidth: .infinity) - }.padding(.leading, 20) - .padding(.trailing, 20) +// HStack { +// Text("作者") +// .font(.title3) +// .padding(.top, 5) +// .padding(.leading, 20) +// .padding(.bottom, 0) +// .frame(height: 18) +// Text("(选填)") +// .font(Font.system(size: 11)) +// .padding(.top, 5) +// .foregroundColor(.gray.opacity(0.6)) +// .frame(height: 18) +// Spacer() +// }.padding(.top, 5) +// HStack { +// TextField("", text: $author) +// .accentColor(nil) +// .textFieldStyle(PlainTextFieldStyle()) +// .font(Font.system(size: 14)) +// .padding(10) +// .background(Color.white) +// .cornerRadius(8) +// .frame(minWidth: 0, maxWidth: .infinity) +// }.padding(.leading, 20) +// .padding(.trailing, 20) } Spacer() VStack { HStack { - Toggle("是否上传至云端", isOn: $isToggleOn) - .padding() - .foregroundColor(.gray) - .font(Font.system(size: 13)) +// Toggle("是否上传至云端", isOn: $isToggleOn) +// .padding() +// .foregroundColor(.gray) +// .font(Font.system(size: 13)) Spacer() Button { @@ -125,10 +126,13 @@ struct AIPromptInputView: View { } Button { + viewModel.addPrompt(title: title, content: prompt) self.isPresented = false } label: { Text("确定") - }.padding(.trailing, 20) + + } + .padding(.trailing, 20) } }.frame(height: 44) diff --git a/OSXChatGPT/OSXChatGPT/WindowView/AIPromptView.swift b/OSXChatGPT/OSXChatGPT/WindowView/AIPromptView.swift index 5f4bf08..70d57ae 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/AIPromptView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/AIPromptView.swift @@ -17,24 +17,16 @@ struct AIPromptView: View { Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) } } -struct ListItem: Identifiable, Hashable { - let id = UUID() - let name: String - var isSelected: Bool = false - var title: String = "11" - var content: String = "444" -} + struct AIPromptPopView: View { + @EnvironmentObject var viewModel: ViewModel + @StateObject var data = AIPromptSessionViewMdoel() @Binding var showInputView: Bool @Binding var showPopover: Bool @State private var isPresented = false - @State var selectedItem :ListItem? - let items: [ListItem] = [ListItem(name: "123", isSelected: false), - ListItem(name: "345", isSelected: false), - ListItem(name: "567", isSelected: true)] + var body: some View { ZStack { -// Spacer() VStack { Spacer() Text("选择提示") @@ -54,19 +46,30 @@ struct AIPromptPopView: View { .padding(10) } } - List(items) { item in - AIPromptPopCellView( - item: item, - isSelected: self.selectedItem == item - ) { - self.selectedItem = item + List(data.allPrompts) { item in + AIPromptPopCellView(item: item, isSelected: data.selectedItem == item) { + data.selectedItem = item + }.contextMenu { + Button(action: { + data.deletePrompt(prompt: item) + }) { + Text("删除") + } } }.frame(width: 560, height: 380) + .onAppear { + if let conversation = viewModel.currentConversation { + data.fetchAllPrompts(session: conversation) + } + } + .onDisappear { + viewModel.updateConversation(sesstionId: viewModel.currentConversation!.sesstionId, prompt: data.selectedItem!) + } } } struct AIPromptPopCellView: View { - let item: ListItem + let item: Prompt let isSelected: Bool let action: () -> Void @@ -88,15 +91,35 @@ struct AIPromptPopCellView: View { .frame(width: 20, height: 20) .padding(5) } - - VStack(alignment: .leading, spacing: 4) { - Text(item.title) - .font(.headline) - .foregroundColor(.primary) - Text(item.content) - .font(.subheadline) - .foregroundColor(.secondary) - }.padding(.leading, 2) + if item.type == 1 { + VStack(alignment: .leading, spacing: 4) { + HStack { + Text("【默认无修饰语】") + .font(Font.system(size: 15)) + .foregroundColor(.white) + .padding(.trailing, 6) + .padding(.bottom, 6) + Text("当前选中的修饰语") + .font(Font.system(size: 14)) + .foregroundColor(.white) + .padding(.bottom, 6) + } + Text("每个会话只能选择一个修饰语, 也可以自定义添加修饰语") + .font(.subheadline) + .foregroundColor(.white.opacity(1)) + }.padding(.leading, 2) + }else { + VStack(alignment: .leading, spacing: 4) { + Text(item.title ?? "") + .font(.headline) + .foregroundColor(.white) + .foregroundColor(.primary) + .padding(.bottom, 6) + Text(item.prompt ?? "") + .font(.subheadline) + .foregroundColor(.white.opacity(1)) + }.padding(.leading, 2) + } Spacer() } @@ -104,7 +127,7 @@ struct AIPromptPopCellView: View { .padding(.vertical, 5) .padding(.horizontal, 10) .background( - AIPromptViewMdoel.randomColor() + item.color ) .cornerRadius(6) .onTapGesture { diff --git a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomInputView.swift b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomInputView.swift index 9e8fec2..072ba26 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomInputView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomInputView.swift @@ -83,7 +83,7 @@ struct ChatRoomInputView: View { return } } - viewModel.addNewMessage(sesstionId: viewModel.currentConversation?.sesstionId ?? "", text: msg, role: "user") { + viewModel.addNewMessage(sesstionId: viewModel.currentConversation?.sesstionId ?? "", text: msg, role: "user", prompt: viewModel.currentConversation?.prompt?.prompt) { } //清空 diff --git a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomToolBar.swift b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomToolBar.swift index b50e2ac..98acbf1 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomToolBar.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomToolBar.swift @@ -29,11 +29,11 @@ struct ChatRoomToolBar: View { } .frame(width: 60) - Button("Prompt") { + Button("修饰语") { showPopover.toggle() } .popover(isPresented: $showPopover) { - AIPromptPopView(showInputView: $showInputView, showPopover: $showPopover) + AIPromptPopView(showInputView: $showInputView, showPopover: $showPopover).environmentObject(viewModel) } Spacer() diff --git a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift index 10a686c..50ed9c9 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/ChatRoomView.swift @@ -37,7 +37,7 @@ struct ChatRoomView: View { ScrollView { ScrollViewReader { scrollView in LazyVStack(alignment: .trailing, spacing: 8) { - ForEach(viewModel.messages) { message in + 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)) @@ -72,7 +72,7 @@ struct ChatRoomView: View { Divider() GeometryReader { toolBarGeometry in Spacer() - ChatRoomToolBar() + ChatRoomToolBar().environmentObject(viewModel) .frame(width: toolBarGeometry.size.width, height: toolBarGeometry.size.height) .background(Color.clear) @@ -113,72 +113,7 @@ struct ChatRoomView: View { } - private func compareText(newText: String, lastText: String) -> String { - var newValue = String(newText) - var lastValue = String(lastText) - //比较两个字符串,找出新输入的那个字符 - newText.forEach { chart in - if let index = newValue.firstIndex(of: chart) { - if let idx = lastValue.firstIndex(of: chart) { - newValue.remove(at: index) - lastValue.remove(at: idx) - } - } - } - return newValue - } - - private func onTextViewChange(_ newValue: String) { - - if (KeyboardMonitor.shared.shiftKeyPressed) { - //按下shift - }else if newMessageText.count < lastMessageText.count { - //删除操作 - }else if newMessageText.count - 1 == lastMessageText.count { - //在中间任意地方按下空格键发送 - let charts = compareText(newText: newMessageText, lastText: lastMessageText) - if charts == "\n" { - if KeyboardMonitor.shared.currentPasteboardText == newMessageText { - //复制进来的,不发送 - }else { - //输入的是空格,则发送 - sendMessage(scrollView: scrollView, text: lastMessageText) - } - } - } - lastMessageText = newMessageText - } - - private func sendMessage(scrollView: ScrollViewProxy?, text: String) { - guard !newMessageText.isEmpty else { return } - let msg = String(newMessageText.dropLast()) - let replaceStr = msg.replacingOccurrences(of: " ", with: "") - if replaceStr.count == 0 { - cleanText() - return - }else if replaceStr.contains("\n") { - let repl = replaceStr.replacingOccurrences(of: "\n", with: "") - if repl.count == 0 { - cleanText() - return - } - } - viewModel.addNewMessage(sesstionId: viewModel.currentConversation?.sesstionId ?? "", text: msg, role: "user") { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - withAnimation { - scrollView?.scrollTo(viewModel.messages.last?.id, anchor: .bottom) - } - } - } - //清空 - cleanText() - } - - private func cleanText() { - newMessageText = "" - conversation?.lastInputText = "" - lastMessageText = "" - } + } @@ -193,6 +128,7 @@ struct ChatRoomCellView: View { VStack { Text(message.text ?? "") .textSelection(.enabled) + .id(message.id) }.padding(12) .background(Color.blue.opacity(0.8)) .foregroundColor(.white) @@ -235,16 +171,23 @@ struct ChatRoomCellView: View { .markdownCodeSyntaxHighlighter(.splash(theme: viewModel.theme)) .background(Color.white.opacity(0.8)) .cornerRadius(6) - }.contextMenu { + }.id(message.id) + .contextMenu { Button(action: { viewModel.deleteMessage(message: message) }) { Text("删除消息") } + Button(action: { + NSPasteboard.general.prepareForNewContents() + NSPasteboard.general.setString(message.text ?? "", forType: .string) + }) { + Text("复制消息") + } } if message.type == 2 && viewModel.messages.last?.id == message.id { Button { - viewModel.resendMessage(sesstionId: message.sesstionId) + viewModel.resendMessage(sesstionId: viewModel.currentConversation?.sesstionId ?? "", prompt: viewModel.currentConversation?.prompt?.prompt) } label: { Image("retry") .resizable() diff --git a/OSXChatGPT/OSXChatGPT/WindowView/SessionsView.swift b/OSXChatGPT/OSXChatGPT/WindowView/SessionsView.swift index 0014616..23d3aeb 100644 --- a/OSXChatGPT/OSXChatGPT/WindowView/SessionsView.swift +++ b/OSXChatGPT/OSXChatGPT/WindowView/SessionsView.swift @@ -37,13 +37,13 @@ struct SessionsView: View { } .buttonStyle(BorderlessButtonStyle()) .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading,0) + .padding(.leading,10) NavigationLink(destination: UserInitializeView().environmentObject(viewModel), isActive: $viewModel.showUserInitialize) { Button(action: { // 点击右边按钮的操作 viewModel.currentConversation = nil//先取消会话 - viewModel.showAIPrompt = false +// viewModel.showAIPrompt = false viewModel.showUserInitialize = true KeyboardMonitor.shared.stopKeyMonitor() KeyboardMonitor.shared.stopMonitorPasteboard() @@ -55,27 +55,27 @@ struct SessionsView: View { .cornerRadius(5) } .buttonStyle(PlainButtonStyle()) - .padding(.trailing, 0) + .padding(.trailing, 20) }.buttonStyle(PlainButtonStyle()) - NavigationLink(destination: AIPromptView(sesstionId: nil), isActive: $viewModel.showAIPrompt) { - Button(action: { - // 点击右边按钮的操作 - viewModel.currentConversation = nil//先取消会话 - viewModel.showUserInitialize = false - viewModel.showAIPrompt = true - KeyboardMonitor.shared.stopKeyMonitor() - KeyboardMonitor.shared.stopMonitorPasteboard() - }) { - Image(systemName: "swift") - .padding(10) - .foregroundColor(.white) - .background(Color.gray) - .cornerRadius(5) - } - .buttonStyle(PlainButtonStyle()) - .padding(.trailing, 10) - }.buttonStyle(PlainButtonStyle()) +// NavigationLink(destination: AIPromptView(sesstionId: nil), isActive: $viewModel.showAIPrompt) { +// Button(action: { +// // 点击右边按钮的操作 +// viewModel.currentConversation = nil//先取消会话 +// viewModel.showUserInitialize = false +// viewModel.showAIPrompt = true +// KeyboardMonitor.shared.stopKeyMonitor() +// KeyboardMonitor.shared.stopMonitorPasteboard() +// }) { +// Image(systemName: "swift") +// .padding(10) +// .foregroundColor(.white) +// .background(Color.gray) +// .cornerRadius(5) +// } +// .buttonStyle(PlainButtonStyle()) +// .padding(.trailing, 10) +// }.buttonStyle(PlainButtonStyle()) }) @@ -125,8 +125,15 @@ struct ChatRowView: View { .frame(width: 40, height: 40) .padding(.leading, 5) VStack(alignment: .leading) { - Text(chat.remark ?? chat.lastMessage?.text ?? "New Chat") - .font(.headline) + if let prompt = chat.prompt?.title { + Text("[修饰]\(prompt)") + .font(.headline) + .foregroundColor(NSColor(r: 224, g: 87, b: 114).toColor()) + }else { + Text(chat.prompt?.title ?? chat.remark ?? chat.lastMessage?.text ?? "New Chat") + .font(.headline) + } + Spacer() if chat.updateData != nil { let dateTime = dateFormatter(chat.updateData!)