// // KMAITranslationVC.swift // PDF Reader Pro // // Created by wanjun on 2023/5/22. // import Cocoa class KMAITranslationVC: NSViewController { @IBOutlet weak var aiTranslationLabel: NSTextField! @IBOutlet weak var aiTranslationView: KMDottedLineView! @IBOutlet weak var supportPDFWordFileImageView: NSImageView! @IBOutlet weak var supportPDFWordFileLabel: NSTextField! @IBOutlet weak var supportPDFWordFileSubLabel: NSTextField! @IBOutlet weak var translationLanguageLabel: NSTextField! @IBOutlet weak var automaticBox: KMBox! @IBOutlet weak var automaticLabel: NSTextField! @IBOutlet weak var languageBox: KMBox! @IBOutlet weak var languageLabel: NSTextField! @IBOutlet weak var languageImageView: NSImageView! @IBOutlet weak var selectYourFilesBox: KMBox! @IBOutlet weak var selectYourFilesLabel: NSTextField! @IBOutlet weak var selectYourFilesImageView: NSImageView! @IBOutlet weak var orDropFileHereLabel: NSTextField! @IBOutlet weak var errorView: NSView! @IBOutlet weak var errorLabel: NSTextField! var fromStr: String = "auto" var toStr: String = "en" var fromLanguages: [String] = ["Automatic", "English", "Simplified Chinese", "Traditional Chinese", "Japanese", "Korean", "French", "Spanish", "Italian", "German", "Portuguese", "Russian", "Vietnamese", "Thai", "Arabic", "Greek", "Bulgarian", "Finnish", "Slovene", "Dutch", "Czech", "Swedish", "Polish", "Danish", "Romanian", "Hungarian"] var toLanguages: [String] = ["English", "Simplified Chinese", "Traditional Chinese", "Japanese", "Korean", "French", "Spanish", "Italian", "German", "Portuguese", "Russian", "Vietnamese", "Thai", "Arabic", "Greek", "Bulgarian", "Finnish", "Slovene", "Dutch", "Czech", "Swedish", "Polish", "Danish", "Romanian", "Hungarian"] var popover: NSPopover? var progressController: SKProgressController? var timer: Timer? var timerCounter = 0.0 override func viewDidLoad() { super.viewDidLoad() // Do view setup here. self.initLocalization() self.initializeUI() } override func viewDidAppear() { super.viewDidAppear() self.errorView.isHidden = true self.initLocalization() } // MARK: initialize func initializeUI() -> Void { self.aiTranslationView.canHover = false self.aiTranslationLabel.textColor = NSColor.km_init(hex: "#252629") self.aiTranslationLabel.font = NSFont.SFProTextSemiboldFont(20.0) self.supportPDFWordFileImageView.image = NSImage(named: "ic_function_other") self.supportPDFWordFileLabel.textColor = NSColor.km_init(hex: "#252629") self.supportPDFWordFileLabel.font = NSFont.SFProTextSemiboldFont(20.0) self.supportPDFWordFileSubLabel.textColor = NSColor.km_init(hex: "#94989C") self.supportPDFWordFileSubLabel.font = NSFont.SFProTextRegularFont(14.0) self.translationLanguageLabel.textColor = NSColor.km_init(hex: "#252629") self.translationLanguageLabel.font = NSFont.SFProTextRegularFont(14.0) self.automaticLabel.textColor = NSColor.km_init(hex: "#252629") self.automaticLabel.font = NSFont.SFProTextRegularFont(16.0) self.languageLabel.textColor = NSColor.km_init(hex: "#252629") self.languageLabel.font = NSFont.SFProTextRegularFont(16.0) self.automaticBox.cornerRadius = 4.0 self.automaticBox.borderColor = NSColor.km_init(hex: "#DFE1E5") self.languageBox.cornerRadius = 4.0 self.languageBox.borderColor = NSColor.km_init(hex: "#DFE1E5") self.languageImageView.image = NSImage(named: "ic_transtate") self.automaticBox.downCallback = { [weak self](downEntered: Bool, mouseBox: KMBox, event) -> Void in if downEntered { self?.languageAction(true) } } self.languageBox.downCallback = { [weak self](downEntered: Bool, mouseBox: KMBox, event) -> Void in if downEntered { self?.languageAction(false) } } self.selectYourFilesBox.cornerRadius = 4.0 self.selectYourFilesBox.fillColor = NSColor.km_init(hex: "#1770F4") self.selectYourFilesLabel.textColor = NSColor.km_init(hex: "#FFFFFF") self.selectYourFilesLabel.font = NSFont.SFProTextRegularFont(16.0) self.selectYourFilesImageView.image = NSImage(named: "icon_SelectYourFiles") self.orDropFileHereLabel.textColor = NSColor.km_init(hex: "#616469") self.orDropFileHereLabel.font = NSFont.SFProTextRegularFont(14.0) self.selectYourFilesBox.moveCallback = { [weak self](mouseEntered: Bool, mouseBox: KMBox) -> Void in if mouseEntered { self?.selectYourFilesBox.fillColor = NSColor.km_init(hex: "#3F8FF6") } else { self?.selectYourFilesBox.fillColor = NSColor.km_init(hex: "#1770F4") } } self.selectYourFilesBox.downCallback = { [weak self](downEntered: Bool, mouseBox: KMBox, event) -> Void in if downEntered { if !KMLightMemberManager.manager.isLogin() { KMLoginWindowController.show(window: NSApp.mainWindow!) return } self?._selectFiles() // Task { @MainActor in // if await (KMLightMemberManager.manager.canPayFunction() == false) { // let _ = KMSubscribeWaterMarkWindowController.show(window: self.view.window!, type: .aiTranslate) { isSub, _, isClose in // if (isClose) { // return // } // if (isSub) { // self._selectFiles() // return // } // } // return // } // self._selectFiles() // } } } self.errorView.isHidden = true self.errorView.wantsLayer = true self.errorView.layer?.backgroundColor = NSColor.km_init(hex: "##36383B").cgColor self.errorView.layer?.cornerRadius = 4.0 self.errorLabel.textColor = NSColor.km_init(hex: "#FFFFFF") self.errorLabel.font = NSFont.SFProTextRegularFont(14.0) } private func _selectFiles() { self.errorView.isHidden = true let openPanel = NSOpenPanel() openPanel.allowedFileTypes = ["pdf", "PDF", "docx", "doc"] openPanel.allowsMultipleSelection = false openPanel.beginSheetModal(for: self.view.window!) { result in if result == .OK { for url in openPanel.urls { let isExceedsLimit = self.isPDFPageCountExceedsLimit(filePath: url.path) if (url.pathExtension == "pdf") || url.pathExtension == "PDF" { if !url.path.isPDFValid() { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return } } if self.isFileGreaterThan10MB(atPath: url.path) { self.errorView.isHidden = false self.errorLabel.stringValue = NSLocalizedString("The uploaded file size cannot exceed 10MB", comment: "") } else if isExceedsLimit { self.errorView.isHidden = false self.errorLabel.stringValue = NSLocalizedString("Documents cannot exceed 30 pages", comment: "") } else { DispatchQueue.main.async { self.showProgressWindow() self.progressController?.maxValue = Double(100) } self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.timerTick), userInfo: nil, repeats: true) let infoDictionary = Bundle .main.infoDictionary! let majorVersion = infoDictionary["CFBundleShortVersionString"] let languages = [self.automaticLabel.stringValue, self.languageLabel.stringValue] UserDefaults.standard.set(languages, forKey: "KMAITranslationLanguageArrayKey") UserDefaults.standard.synchronize() self.fromStr = self.languageAbbreviation(self.automaticLabel.stringValue) self.toStr = self.languageAbbreviation(self.languageLabel.stringValue) let alert = NSAlert() alert.messageText = NSLocalizedString("Processing times may be longer for larger documents. Thank you for your patience.", comment: "") alert.addButton(withTitle: NSLocalizedString("Continue", comment: "")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) alert.beginSheetModal(for: self.view.window!) { [weak self] result in if (result == .alertFirstButtonReturn) { KMRequestServerManager.manager.aiTranslationFileUpload(file: url.path, version: majorVersion as! String) { [weak self] success, result in // self.hiddenProgressWindow() if success { let result: NSDictionary = result!.result let fileKey = result["fileKey"] let fileName = result["fileName"] let pageCount = result["pageCount"] if fileKey != nil { self?.fileTranslateHandle(fileKey as! String) } } else { let result: String = result!.message DispatchQueue.main.async { self?.hiddenProgressWindow() self?.errorView.isHidden = false self?.errorLabel.stringValue = result } } } } else if result == .alertSecondButtonReturn { DispatchQueue.main.async { self?.hiddenProgressWindow() } return } } } } } } } func initLocalization() -> Void { self.aiTranslationLabel.stringValue = NSLocalizedString("AI Translation", comment: "") self.supportPDFWordFileLabel.stringValue = NSLocalizedString("Support PDF and Word file", comment: "") self.supportPDFWordFileSubLabel.stringValue = NSLocalizedString("Limit document size to 10M, document page number to 30, 10w characters per month.", comment: "") self.translationLanguageLabel.stringValue = NSLocalizedString("Translation Language:", comment: "") self.selectYourFilesLabel.stringValue = NSLocalizedString("Select your file", comment: "") self.orDropFileHereLabel.stringValue = NSLocalizedString("or drop file here", comment: "") let languageArr = UserDefaults.standard.array(forKey: "KMAITranslationLanguageArrayKey") as? [String] ?? [] if (languageArr.count > 0) { self.automaticLabel.stringValue = languageArr[0] self.languageLabel.stringValue = languageArr[1] } else { self.automaticLabel.stringValue = NSLocalizedString("Automatic", comment: "") self.languageLabel.stringValue = NSLocalizedString("English", comment: "") let languages = [NSLocalizedString("Automatic", comment: ""), NSLocalizedString("English", comment: "")] UserDefaults.standard.set(languages, forKey: "KMAITranslationLanguageArrayKey") UserDefaults.standard.synchronize() } } // MARK: Private Methods func fileTranslateHandle(_ fileKey: String) -> Void { let infoDictionary = Bundle .main.infoDictionary! let majorVersion = infoDictionary["CFBundleShortVersionString"] // DispatchQueue.main.async { // self.showProgressWindow() // } KMRequestServerManager.manager.aiTranslationFileTranslateHandle(fileKey: fileKey, from: self.fromStr, to: self.toStr, version: majorVersion as! String) { [weak self] success, result in if success { let result: NSDictionary = result!.result let fileUrl: String = result["fileUrl"] as! String let downFileUrl: String = result["downFileUrl"] as! String let ossDownUrl: String = result["ossDownUrl"] as! String let fileName: String = result["fileName"] as! String let downFileName: String = result["downFileName"] as! String let from: String = result["from"] as! String let to: String = result["to"] as! String self?.downloadFile(filePath: ossDownUrl, downFileName: downFileName) } else { let result: String = result!.message DispatchQueue.main.async { self?.hiddenProgressWindow() self?.errorView.isHidden = false self?.errorLabel.stringValue = result } } } } func downloadFile(filePath: String, downFileName: String) -> Void { guard let fileURL = URL(string: filePath) else { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Invalid file link", comment: "") alert.runModal() return } let destinationURL = FileManager.default.temporaryDirectory.appendingPathComponent(downFileName) if FileManager.default.fileExists(atPath: destinationURL.path) { do { try FileManager.default.removeItem(at: destinationURL) KMPrint("删除旧文件成功") } catch { KMPrint("删除旧文件失败:\(error)") } } let sessionConfiguration = URLSessionConfiguration.default let session = URLSession(configuration: sessionConfiguration) let downloadTask = session.downloadTask(with: fileURL) { (tempLocalURL, response, error) in if let error = error { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = String(format: "%@:\(error)", NSLocalizedString("Download failed", comment: "")) alert.runModal() return } guard let tempLocalURL = tempLocalURL else { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Invalid temporary directory", comment: "") alert.runModal() return } DispatchQueue.main.async { self.hiddenProgressWindow() } do { try FileManager.default.moveItem(at: tempLocalURL, to: destinationURL) NSDocumentController.shared.openDocument(withContentsOf: destinationURL, display: true) { document, documentWasAlreadyOpen, error in if error != nil { NSApp.presentError(error!) } else { } } } catch { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = String(format: "%@:\(error)", NSLocalizedString("Failed to save file", comment: "")) alert.runModal() } } downloadTask.resume() } func languageAbbreviation(_ language: String) -> String { if language == "Automatic Identification" { return "auto" } else if language == "English" { return "en" } else if language == "Simplified Chinese" { return "zh" } else if language == "Traditional Chinese" { return "cht" } else if language == "Japanese" { return "jp" } else if language == "Korean" { return "kor" } else if language == "French" { return "fra" } else if language == "Spanish" { return "spa" } else if language == "Italian" { return "it" } else if language == "German" { return "de" } else if language == "Portuguese" { return "pt" } else if language == "Russian" { return "ru" } else if language == "Vietnamese" { return "vie" } else if language == "Thai" { return "th" } else if language == "Arabic" { return "ara" } else if language == "Greek" { return "el" } else if language == "Bulgarian" { return "bul" } else if language == "Finnish" { return "fin" } else if language == "Slovene" { return "slo" } else if language == "Dutch" { return "nl" } else if language == "Czech" { return "cs" } else if language == "Swedish" { return "swe" } else if language == "Polish" { return "pl" } else if language == "Danish" { return "dan" } else if language == "Romanian" { return "rom" } else if language == "Hungarian" { return "hu" } return "auto" } func showProgressWindow() { let progress = SKProgressController() progress.window?.backgroundColor = NSColor.km_init(hex: "#36383B") progress.window?.contentView?.wantsLayer = true progress.window?.contentView?.layer?.backgroundColor = NSColor.km_init(hex: "#36383B").cgColor progress.progressField.textColor = NSColor.white progress.message = NSLocalizedString("Translating...", comment: "") progress.closeBlock = { [weak self] in self?.hiddenProgressWindow() } self.progressController = progress self.view.window?.beginSheet(progress.window!) } func hiddenProgressWindow() { DispatchQueue.main.async { self.progressController?.doubleValue = 100.0 } self.stopTimer() if (self.progressController != nil) { self.view.window?.endSheet((self.progressController?.window)!) self.progressController = nil KMRequestServer.requestServer.task?.cancel() } } func isFileGreaterThan10MB(atPath filePath: String) -> Bool { let fileManager = FileManager.default do { let fileAttributes = try fileManager.attributesOfItem(atPath: filePath) if let fileSize = fileAttributes[.size] as? UInt64 { let megabyteSize = fileSize / (1024 * 1024) return megabyteSize >= 10 } } catch { KMPrint("Error: \(error)") } return false } func isPDFPageCountExceedsLimit(filePath: String) -> Bool { let url = URL(fileURLWithPath: filePath) guard let document = PDFDocument(url: url) else { return false } let pageCount = document.pageCount return pageCount > 30 } @objc func timerTick() { timerCounter += 1.0 self.progressController?.increment(by: 1.0) if timerCounter >= 95 { stopTimer() } } func stopTimer() { timer?.invalidate() timer = nil } // MARK: Action Methods func languageAction(_ isFromLanguage: Bool) -> Void { if (self.popover != nil && self.popover!.isShown) { self.popover?.close() self.popover = nil return } var languages = self.fromLanguages if !isFromLanguage { languages = self.toLanguages } let vc: KMAILanguagePopVC = KMAILanguagePopVC().initWithPopViewDataArr(languages) let createFilePopover: NSPopover = NSPopover.init() self.popover = createFilePopover createFilePopover.contentViewController = vc createFilePopover.animates = true createFilePopover.behavior = .semitransient createFilePopover.setValue(true, forKey: "shouldHideAnchor") if isFromLanguage { vc.selectString = self.automaticLabel.stringValue } else { vc.selectString = self.languageLabel.stringValue } vc.downCallback = { [unowned self] (language: String) -> Void in createFilePopover.close() if isFromLanguage { self.automaticLabel.stringValue = language self.fromStr = self.languageAbbreviation(language) } else { self.languageLabel.stringValue = language self.toStr = self.languageAbbreviation(language) } } if isFromLanguage { createFilePopover.show(relativeTo: CGRect(x: automaticBox.bounds.origin.x, y: 10, width: automaticBox.bounds.size.width, height: automaticBox.bounds.size.height), of: automaticBox, preferredEdge: .maxY) vc.customBoxWidthLayoutConstraint.constant = automaticBox.frame.width } else { createFilePopover.show(relativeTo: CGRect(x: languageBox.bounds.origin.x, y: 10, width: languageBox.bounds.size.width, height: languageBox.bounds.size.height), of: languageBox, preferredEdge: .maxY) vc.customBoxWidthLayoutConstraint.constant = languageBox.frame.width } } }