// // AINewConfigWindowController.swift // PDF Reader Pro Edition // // Created by Niehaoyu on 2024/4/16. // import Cocoa protocol AIConfigWindowDelegate: AnyObject { func ai_InputViewDidChooseCurFile(aiConfigWindow: AINewConfigWindowController) } @objcMembers class AINewConfigWindowController: NSWindowController, NSWindowDelegate, AIInfoInputViewDelegate { static var currentWindowController: AINewConfigWindowController! @IBOutlet weak var contendBox: NSBox! var aiHeaderView: AIHeaderView! var aiChatView: AIChatView! var aiTypeItemView: AITypeItemChooseView! var aiInfoInputView: AIInfoInputView! var inputStringHeight: CGFloat = 40 var didSetOriginFrame: Bool = false var eventLabel: String = "AITools_Start" weak var aiDelegate: AIConfigWindowDelegate? var chooseCurFileHandle: ((_ windowVC: AINewConfigWindowController) -> Void)? deinit { DistributedNotificationCenter.default.removeObserver(self) } @objc static func currentWC() -> AINewConfigWindowController { if currentWindowController != nil { return currentWindowController } else { let configWC: AINewConfigWindowController = AINewConfigWindowController.init(windowNibName: "AINewConfigWindowController") currentWindowController = configWC; return currentWindowController } } override func showWindow(_ sender: Any?) { super.showWindow(sender) self.window?.delegate = self self.aiInfoInputView.setUpTranslateUI() self.aiChatView.reloadData() self.aiTypeItemView.refreshUI() self.refreshViewUI() } override func windowDidLoad() { super.windowDidLoad() // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. self.window?.title = NSLocalizedString("AI Tools", comment: "") self.loadAIHeaderView() self.loadAIChatView() self.loadAIInputView() self.loadAITypeItemView() self.refreshViewUI() self.refreshViewColor() DistributedNotificationCenter.default().addObserver(self, selector: #selector(themeChange), name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil) } func refreshViewColor() { if KMAppearance.isDarkMode() { self.contendBox.fillColor = NSColor(red: 33/255, green: 33/255, blue: 33/255, alpha: 1) } else { self.contendBox.fillColor = NSColor.white } self.aiHeaderView.refreshViewColor() self.aiInfoInputView.refreshUI() self.aiChatView.reloadData() self.aiTypeItemView.refreshViewColor() } func loadAIHeaderView() { self.aiHeaderView = AIHeaderView.createFromNib() self.aiHeaderView.frame = NSMakeRect(0, NSHeight(self.contendBox.frame)-58, NSWidth(self.contendBox.frame), 28) self.aiHeaderView.autoresizingMask = [.minXMargin, .maxXMargin, .width, .minYMargin] self.contendBox.addSubview(self.aiHeaderView) } func loadAIChatView() { self.aiChatView = AIChatView.createFromNib() self.aiChatView.wantsLayer = true self.aiChatView.layer?.backgroundColor = NSColor.clear.cgColor self.aiChatView.frame = NSMakeRect(0, 300, NSWidth(self.contendBox.frame), NSHeight(self.contendBox.frame)-400) self.aiChatView.autoresizingMask = [.width, .height] self.contendBox.addSubview(self.aiChatView) self.aiChatView.chooseConfigHandle = {[weak self] view, configType in DispatchQueue.main.async { self?.chooseAIFunctionWithType(configType) } } self.aiChatView.cancelAIHandle = {[weak self] view, chatInfoModel in DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.05) { chatInfoModel.chatInfoState = .stateCancel self?.aiChatView.reloadData() } } self.aiChatView.continueAITranslateHandle = {[weak self] view, chatInfoModel in DispatchQueue.main.async { chatInfoModel.chatInfoState = .stateLoading self?.aiChatView.reloadData() self?.continueAiTranslate(chatInfoModel) } } self.aiChatView.redoHandle = {[weak self] view, chatInfoModel in DispatchQueue.main.async { let newChatModel = AIChatInfoModel.init() newChatModel.aiConfigType = chatInfoModel.aiConfigType newChatModel.infoType = chatInfoModel.infoType newChatModel.filePath = chatInfoModel.filePath newChatModel.chatInfoState = .stateLoading newChatModel.uploadContent = chatInfoModel.uploadContent newChatModel.translateFromLanguage = chatInfoModel.translateFromLanguage newChatModel.translateToLanguage = chatInfoModel.translateToLanguage AIChatInfoManager.defaultManager.modelsArrM.append(newChatModel) self?.aiChatView.reloadData() if newChatModel.aiConfigType == .summarize { self?.aiSummarizeWithModel(newChatModel) } else if newChatModel.aiConfigType == .reWriting { self?.aiReWritingWithModel(newChatModel) } else if newChatModel.aiConfigType == .proofreading { self?.aiProofreadingWithModel(newChatModel) } else if newChatModel.aiConfigType == .translate { self?.startAiTranslateWithModel(newChatModel) } } } } func loadAIInputView() { self.aiInfoInputView = AIInfoInputView.createFromNib() self.aiInfoInputView.frame = NSMakeRect((NSWidth(self.contendBox.frame)-248)/2.0, 20, 248, 86) self.aiInfoInputView.autoresizingMask = [.minXMargin, .maxXMargin, .width, .maxYMargin] self.contendBox.addSubview(self.aiInfoInputView) self.aiInfoInputView.aiConfigType = .none self.aiInfoInputView.aiDelegate = self self.aiInfoInputView.reloadData() self.aiInfoInputView.startAIHandle = {[unowned self] view in if view.aiConfigType == .summarize { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .summarize chatModel.infoType = .chatFileUpload chatModel.filePath = self.aiInfoInputView.filePath AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiSummarize() } else if view.aiConfigType == .reWriting { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .reWriting chatModel.infoType = .chatStringUpload chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiReWriting() } else if view.aiConfigType == .proofreading { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .proofreading chatModel.infoType = .chatStringUpload chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiProofreading() } else if view.aiConfigType == .translate { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .translate if self.aiInfoInputView.filePath.isEmpty == false { //文件 chatModel.filePath = self.aiInfoInputView.filePath chatModel.infoType = .chatFileUpload } else { //文字 chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string chatModel.infoType = .chatStringUpload } chatModel.translateFromLanguage = self.aiInfoInputView.fromLanguage chatModel.translateToLanguage = self.aiInfoInputView.toLanguage AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.startAiTranslate() } self.refreshViewUI() self.aiTypeItemView.reloadData() } self.aiInfoInputView.inputFrameUpdateHandle = {[weak self] view, stringSize in if view.aiConfigType == .summarize { self?.inputStringHeight = max(stringSize.height, 40) } else if view.aiConfigType == .reWriting { self?.inputStringHeight = max(stringSize.height, 40) } else if view.aiConfigType == .proofreading { self?.inputStringHeight = max(stringSize.height, 40) } else if view.aiConfigType == .translate { if view.filePath.isEmpty { self?.inputStringHeight = max(stringSize.height, 40) } else { self?.inputStringHeight = 40 } } self?.refreshViewUI() } self.aiInfoInputView.refreshUI() } func loadAITypeItemView() { self.aiTypeItemView = AITypeItemChooseView.createFromNib() self.aiTypeItemView.wantsLayer = true self.aiTypeItemView.layer?.backgroundColor = NSColor.clear.cgColor self.aiTypeItemView.frame = NSMakeRect((NSWidth(self.contendBox.frame)-246)/2, NSMaxY(self.aiInfoInputView.frame)+13, 246, 52) self.aiTypeItemView.autoresizingMask = [.minXMargin, .maxXMargin, .width, .maxYMargin] self.contendBox.addSubview(self.aiTypeItemView) self.aiTypeItemView.chooseTypeHandle = {[weak self] itemView, aiConfigtype in self?.chooseAIFunctionWithType(aiConfigtype) } self.aiTypeItemView.clearHandle = {[weak self] itemView in if AIChatInfoManager.defaultManager.modelsArrM.count < 1 { return } let alert = NSAlert() alert.alertStyle = .critical alert.informativeText = NSLocalizedString("All the AI content will be removed. Are you sure you want to clear the session?", comment: "") alert.messageText = NSLocalizedString("Clear session", comment: "") alert.addButton(withTitle: NSLocalizedString("Clear", comment: "")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) let response = alert.runModal() if response.rawValue == 1000 { DispatchQueue.main.async { AIChatInfoManager.defaultManager.clearData() self?.aiChatView.reloadData() } } } } func chooseAIFunctionWithType(_ aiConfigType: AIConfigType) -> Void { if KMMemberInfo.shared.aiSubscription() == false { return } if KMMemberInfo.shared.aiPoint == 0 && (AIInfoManager.default().aiInfo.totalToken - AIInfoManager.default().aiInfo.usedTimes) < 1 { let alert = NSAlert() alert.alertStyle = .critical alert.informativeText = NSLocalizedString("No Value Present", comment: "") alert.addButton(withTitle: NSLocalizedString("OK", comment: "")) alert.runModal() return } if self.eventLabel.isEmpty { self.eventLabel = "AITools_Start" } if aiConfigType == .summarize { FMTrackEventManager.defaultManager.trackOnceEvent(event: "AITools", withProperties: [self.eventLabel:"AISum"]) } else if aiConfigType == .reWriting { FMTrackEventManager.defaultManager.trackOnceEvent(event: "AITools", withProperties: [self.eventLabel:"AIRewrite"]) } else if aiConfigType == .proofreading { FMTrackEventManager.defaultManager.trackOnceEvent(event: "AITools", withProperties: [self.eventLabel:"AIProofread"]) } else if aiConfigType == .translate { FMTrackEventManager.defaultManager.trackOnceEvent(event: "AITools", withProperties: [self.eventLabel:"AITranslate"]) } self.aiInfoInputView.aiConfigType = aiConfigType self.aiInfoInputView.reloadData() self.aiInfoInputView.aiFunctionTypeChanged() if self.aiInfoInputView.fileEmptyTextView.string.isEmpty == false { self.aiInfoInputView.updateCountLabelInfo() self.aiInfoInputView.refreshStringSize() } self.refreshViewUI() } func setCurrentPDFSelection(_ string: String) -> Void { self.aiInfoInputView.fileEmptyTextView.string = string } func refreshViewUI() { self.aiHeaderView.frame = NSMakeRect(0, NSHeight(self.contendBox.frame)-58, NSWidth(self.contendBox.frame), 28) if self.aiInfoInputView.aiConfigType == .none { self.aiInfoInputView.frame = NSMakeRect(13, 17, 240, 86) } else if self.aiInfoInputView.aiConfigType == .summarize { self.aiInfoInputView.frame = NSMakeRect(13, 17, 240, min(96+self.inputStringHeight, 500)) } else if self.aiInfoInputView.aiConfigType == .reWriting { self.aiInfoInputView.frame = NSMakeRect(13, 17, 240, min(72+self.inputStringHeight, 500)) } else if self.aiInfoInputView.aiConfigType == .proofreading { self.aiInfoInputView.frame = NSMakeRect(13, 17, 240, min(72+self.inputStringHeight, 500)) } else if self.aiInfoInputView.aiConfigType == .translate { self.aiInfoInputView.frame = NSMakeRect(13, 17, 240, min(125+self.inputStringHeight, 500)) } self.aiTypeItemView.frame = NSMakeRect(13, NSMaxY(self.aiInfoInputView.frame)+8, 240, self.aiTypeItemView.viewHeight) self.aiChatView.frame = NSMakeRect(0, NSMaxY(self.aiTypeItemView.frame)+8, NSWidth(self.contendBox.frame), NSHeight(self.contendBox.frame) - NSMaxY(self.aiTypeItemView.frame) - 16 - 58) } //MARK: AI-Action func aiSummarize() -> Void { if FileManager.default.fileExists(atPath: self.aiInfoInputView.filePath) { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .summarize chatModel.infoType = .chatStringResult chatModel.filePath = self.aiInfoInputView.filePath chatModel.chatInfoState = .stateLoading AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiSummarizeWithModel(chatModel) } } func aiSummarizeWithModel(_ chatModel: AIChatInfoModel) -> Void { if FileManager.default.fileExists(atPath: chatModel.filePath) { AIChatInfoManager.defaultManager.isAILoading = true //MARK: TestData // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { // DispatchQueue.main.async { // AIChatInfoManager.defaultManager.isAILoading = false // chatModel.chatResult = "AI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果\nAI Summary 返回的结果" // chatModel.chatInfoState = .stateSuccess // self.aiChatView.reloadData() // self.aiTypeItemView.reloadData() // self.aiInfoInputView.reloadData() // // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4) { // DispatchQueue.main.async { // chatModel.chatInfoState = .stateFailed // chatModel.chatResult = "Unknown error" // // self.aiChatView.reloadData() // self.aiTypeItemView.reloadData() // self.aiInfoInputView.reloadData() // } // } // } // } //MARK: 正式数据 KMAIRequestServerManager.defaultManager.aiAction(content: chatModel.filePath, state: .extractSummaryFile) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = wrapper.content chatModel.chatResult = resultStr chatModel.chatInfoState = wrapper.success ? .stateSuccess : .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } } else { DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = NSLocalizedString("File Not Exist", comment: "") chatModel.chatResult = resultStr chatModel.chatInfoState = .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } } func aiReWriting() -> Void { //Loading(28+155)->Finish(实际大小) let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .reWriting chatModel.infoType = .chatStringResult chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string chatModel.chatInfoState = .stateLoading AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiReWritingWithModel(chatModel) } func aiReWritingWithModel(_ chatModel: AIChatInfoModel) -> Void { AIChatInfoManager.defaultManager.isAILoading = true //MARK: TestData // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { // DispatchQueue.main.async { // AIChatInfoManager.defaultManager.isAILoading = false // chatModel.chatInfoState = .stateSuccess // chatModel.chatResult = "AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果AI Rewrite返回的结果" // self.aiChatView.reloadData() // self.aiTypeItemView.reloadData() // self.aiInfoInputView.reloadData() // } // } //MARK: 正式数据 KMAIRequestServerManager.defaultManager.aiAction(content: chatModel.uploadContent, state: .rewrite) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = wrapper.content chatModel.chatResult = resultStr chatModel.chatInfoState = wrapper.success ? .stateSuccess : .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } } func aiProofreading() -> Void { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .proofreading chatModel.infoType = .chatStringResult chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string chatModel.chatInfoState = .stateLoading AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.aiProofreadingWithModel(chatModel) } func aiProofreadingWithModel(_ chatModel: AIChatInfoModel) -> Void { AIChatInfoManager.defaultManager.isAILoading = true //MARK: TestData // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { // DispatchQueue.main.async { // chatModel.chatInfoState = .stateSuccess // AIChatInfoManager.defaultManager.isAILoading = false // chatModel.chatResult = "AI Proofreading 返回的结果" // self.aiChatView.reloadData() // self.aiTypeItemView.reloadData() // self.aiInfoInputView.reloadData() // // } // } //MARK: 正式数据 KMAIRequestServerManager.defaultManager.aiAction(content: chatModel.uploadContent, state: .correctTypos) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = wrapper.content chatModel.chatResult = resultStr chatModel.chatInfoState = wrapper.success ? .stateSuccess : .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } } func startAiTranslate() -> Void { let chatModel = AIChatInfoModel.init() chatModel.aiConfigType = .translate if self.aiInfoInputView.filePath.isEmpty { //文字 chatModel.infoType = .chatTranslateResult chatModel.uploadContent = self.aiInfoInputView.fileEmptyTextView.string chatModel.filePath = "" } else { //文件 chatModel.infoType = .chatTranslateResult chatModel.filePath = self.aiInfoInputView.filePath chatModel.uploadContent = "" } chatModel.translateFromLanguage = self.aiInfoInputView.fromLanguage chatModel.translateToLanguage = self.aiInfoInputView.toLanguage chatModel.chatInfoState = .stateLoading AIChatInfoManager.defaultManager.modelsArrM.append(chatModel) self.aiChatView.reloadData() self.startAiTranslateWithModel(chatModel) } func startAiTranslateWithModel(_ chatModel: AIChatInfoModel) -> Void { //MARK: TestData // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { // DispatchQueue.main.async { // chatModel.chatInfoState = .stateInfoConfirm // chatModel.chatResult = "当前翻译需要的内容:" // self.aiChatView.reloadData() // // } // } //MARK: Data if chatModel.filePath.isEmpty == true { //文字 AIChatInfoManager.defaultManager.isAILoading = true KMAIRequestServerManager.defaultManager.aiAction(content: chatModel.uploadContent, state: .textTranslate, from: chatModel.translateFromLanguage, to: chatModel.translateToLanguage) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = wrapper.content chatModel.chatResult = resultStr chatModel.chatInfoState = wrapper.success ? .stateSuccess : .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } }; } else { //文件 AIChatInfoManager.defaultManager.isAILoading = true KMAIRequestServerManager.defaultManager.aiAction(content: chatModel.filePath, state: .fileTranslate, from: chatModel.translateFromLanguage, to: chatModel.translateToLanguage) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false var success = wrapper.success let resultStr = wrapper.content if success == false && resultStr == "501" { success = true } if success { let infoDict: NSDictionary = wrapper.result var credit: Int = 0 if infoDict["credit"] != nil { credit = (infoDict["credit"] ?? "0") as! Int } var charCount: Int = 0 if infoDict["charCount"] != nil { charCount = (infoDict["charCount"] ?? "0") as! Int if credit == 0 { if charCount < 10000 { credit = 2 } else { credit = charCount/5000 + 1 } } } chatModel.creditsValid = true if resultStr == "501" { chatModel.creditsValid = false } chatModel.costCredits = credit chatModel.totalChart = charCount chatModel.chatInfoState = .stateInfoConfirm chatModel.chatResult = resultStr self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } else { chatModel.chatResult = resultStr chatModel.chatInfoState = .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } }; } } func continueAiTranslate(_ chatModel: AIChatInfoModel) -> Void { //MARK: TestData // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { // DispatchQueue.main.async { // chatModel.chatInfoState = .stateSuccess // chatModel.chatResult = "/Users/kdanmobile/Desktop/Quick Start Guide v1.3.0.pdf" // self.aiChatView.reloadData() // self.aiTypeItemView.reloadData() // self.aiInfoInputView.reloadData() // // } // } KMAIRequestServerManager.defaultManager.aiTranslationFileTranslateHandle(fileKey: chatModel.chatResult, from: chatModel.translateFromLanguage, to: chatModel.translateToLanguage) { wrapper in DispatchQueue.main.async { AIChatInfoManager.defaultManager.isAILoading = false let resultStr = wrapper.content chatModel.chatResult = resultStr chatModel.chatInfoState = wrapper.success ? .stateSuccess : .stateFailed self.aiChatView.reloadData() self.aiTypeItemView.reloadData() self.aiInfoInputView.reloadData() } } } //MARK: AIInfoInputViewDelegate func ai_InputViewDidChooseCurFile(aiInputView: AIInfoInputView) { guard let callBack = self.chooseCurFileHandle else { return } callBack(self) } //MARK: NSWindowDelegate func windowDidBecomeMain(_ notification: Notification) { self.aiInfoInputView.reloadData() } func windowWillClose(_ notification: Notification) { AINewConfigWindowController.currentWindowController = nil } @objc func themeChange() { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { self.refreshViewColor() } } }