From: liangzhenying <867174124@qq.com> Date: Mon, 10 Nov 2025 09:07:17 +0000 (+0800) Subject: AI对话增加缓存 X-Git-Tag: 1.0.2~75 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=1a124dd7404767351d337629ca2acf4ac2423810;p=mirrors%2FShenYin.git AI对话增加缓存 --- diff --git a/ShenYin/ShenYin/Api/HttpClient2.swift b/ShenYin/ShenYin/Api/HttpClient2.swift index ff2820b..68339ee 100644 --- a/ShenYin/ShenYin/Api/HttpClient2.swift +++ b/ShenYin/ShenYin/Api/HttpClient2.swift @@ -200,13 +200,13 @@ extension CommomApi{ case .uploadVoiceFile(_): return "/res/voice-file/upload" case .getProductList: - return "/dev/product/list?name=\(BleManager.shared.connectPeripheral?.device.name ?? "")" + return "/dev/product/list?name=\(BleManager.shared.connectPeripheral?.device.name ?? "")&languageCode=\(DCConfig.appLanguage.lanCode)" case .productBook(let productId): - return "/res/prod-book-file?productId=\(productId)" + return "/res/prod-book-file?productId=\(productId)&languageCode=\(DCConfig.appLanguage.lanCode)" case .qaCategory: - return "/uc/qa-category/list" + return "/uc/qa-category/list?&languageCode=\(DCConfig.appLanguage.lanCode)" case .qaList(let categoryId,let page): - return "/uc/qa/list/\(page)?categoryId=\(categoryId)" + return "/uc/qa/list/\(page)?categoryId=\(categoryId)&languageCode=\(DCConfig.appLanguage.lanCode)" case .commitFeedback(_): return "/uc/feedback" @@ -328,6 +328,7 @@ extension CommomApi{ break case let .productInfo(name): params["prodName"] = name + params["languageCode"] = DCConfig.appLanguage.lanCode break case let .bindDevice(para): params += para diff --git a/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Contents.json b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Contents.json new file mode 100644 index 0000000..fdf7abb --- /dev/null +++ b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 1987@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 1987@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@2x.png b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@2x.png new file mode 100644 index 0000000..0ebbee3 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@2x.png differ diff --git a/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@3x.png b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@3x.png new file mode 100644 index 0000000..95b6be7 Binary files /dev/null and b/ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@3x.png differ diff --git a/ShenYin/ShenYin/Cache/ChatCache.swift b/ShenYin/ShenYin/Cache/ChatCache.swift new file mode 100644 index 0000000..96e96aa --- /dev/null +++ b/ShenYin/ShenYin/Cache/ChatCache.swift @@ -0,0 +1,61 @@ +// +// ChatCache.swift +// ShenYin +// +// Created by liangshi on 2025/11/10. +// +import HandyJSON + +/// Ai回应模型 +public class ChatCellModel:NSObject,NSCoding { + public var type: Int = 0//1发,0收 + public var content: String = "" + public var id:Int = 0 + + required public override init() { } + + public func encode(with aCoder: NSCoder) { + aCoder.encode("\(type)", forKey: "type") + aCoder.encode(content, forKey: "content") + aCoder.encode("\(id)", forKey: "id") + } + + required public init?(coder aDecoder: NSCoder) { + type = Int(aDecoder.decodeObject(forKey: "type") as! String) ?? 0 + content = aDecoder.decodeObject(forKey: "content") as? String ?? "" + id = Int(aDecoder.decodeObject(forKey: "id") as! String) ?? 0 + } + +} + +class ChatCache { + static let shared = ChatCache() + private let userDefaults = UserDefaults.standard + + func saveList(_ list: [ChatCellModel]) { + var fullList = get() + fullList.append(contentsOf: list) + if fullList.count > 1000{ + fullList = Array(fullList.dropFirst(fullList.count - 1000)) + } + + let encoder = NSKeyedArchiver.archivedData(withRootObject: fullList) + let filePath = DCFolder.chatCache + do { + try encoder.write(to: URL(fileURLWithPath: filePath), options:.atomic) + } catch { + Logger.debug("记录保存失败",filePath,error) + } + } + + func get() -> [ChatCellModel] { + let data = YLToos.readFile(from: DCFolder.chatCache) + if data == nil{ + return [] + } + if let deviceList = NSKeyedUnarchiver.unarchiveObject(with: data!) as? [ChatCellModel] { + return deviceList + } + return [] + } +} diff --git a/ShenYin/ShenYin/Cache/CountryCache.swift b/ShenYin/ShenYin/Cache/CountryCache.swift new file mode 100644 index 0000000..fdf9e9a --- /dev/null +++ b/ShenYin/ShenYin/Cache/CountryCache.swift @@ -0,0 +1,76 @@ +// +// CountryCache.swift +// ShenYin +// +// Created by liangshi on 2025/11/10. +// + +import Foundation +import HandyJSON +public class CountryModelRoot: ResponseBaseModel { + public var data = [CountryModel]() +} +public class CountryModel: NSObject,HandyJSON,NSCoding { + public var id = "" + public var name = "" + public var language = "" + public var money = "" + public var phonePrefix = "" + public var langCode = "" + + var isChinese:Bool{ + return name == "中国" + } + + static func chinese()->CountryModel{ + let model = CountryModel() + model.name = "中国" + model.phonePrefix = "86" + model.id = "1968848875208851458" + model.money = "ï¿¥" + model.phonePrefix = "+86" + model.langCode = "zh-CN" + + return model + } + required public override init() { } + + public func encode(with aCoder: NSCoder) { + aCoder.encode(id, forKey: "id") + aCoder.encode(name, forKey: "name") + aCoder.encode(language, forKey: "language") + aCoder.encode(money, forKey: "money") + aCoder.encode(phonePrefix, forKey: "phonePrefix") + aCoder.encode(langCode, forKey: "langCode") + } + + required public init?(coder aDecoder: NSCoder) { + id = aDecoder.decodeObject(forKey: "id") as? String ?? "" + name = aDecoder.decodeObject(forKey: "name") as? String ?? "" + language = aDecoder.decodeObject(forKey: "language") as? String ?? "" + money = aDecoder.decodeObject(forKey: "money") as? String ?? "" + phonePrefix = aDecoder.decodeObject(forKey: "phonePrefix") as? String ?? "" + langCode = aDecoder.decodeObject(forKey: "langCode") as? String ?? "" + } +} + + +class CountryCache { + static let shared = CountryCache() + private let userDefaults = UserDefaults.standard + private let deviceListKey = "CountryCache" + + func saveList(_ list: [CountryModel]) { + let encoder = NSKeyedArchiver.archivedData(withRootObject: list) + userDefaults.set(encoder, forKey: deviceListKey) + userDefaults.synchronize() + } + + func get() -> [CountryModel] { + if let data = userDefaults.data(forKey: deviceListKey), + let deviceList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [CountryModel] { + return deviceList + } + return [] + } +} diff --git a/ShenYin/ShenYin/Cache/DCFolder.swift b/ShenYin/ShenYin/Cache/DCFolder.swift index be2be8c..376b999 100644 --- a/ShenYin/ShenYin/Cache/DCFolder.swift +++ b/ShenYin/ShenYin/Cache/DCFolder.swift @@ -50,7 +50,6 @@ class DCFolder { } } static func recordFileResave(srcPath:String)->String?{ - let desPath = DCFolder.record + (srcPath as NSString).lastPathComponent let fileManager = FileManager.default do { @@ -62,4 +61,12 @@ class DCFolder { return nil } } + + static var chatCache:String{ + let path = DCFolder.user + "/chat.txt" + if !FileManager.default.fileExists(atPath: path) { + FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) + } + return path + } } diff --git a/ShenYin/ShenYin/Cache/RecordCache.swift b/ShenYin/ShenYin/Cache/RecordCache.swift index 0403f1a..7cb810c 100644 --- a/ShenYin/ShenYin/Cache/RecordCache.swift +++ b/ShenYin/ShenYin/Cache/RecordCache.swift @@ -8,7 +8,7 @@ class RecordCache { static let shared = RecordCache() private let userDefaults = UserDefaults.standard - private let deviceListKey = "deviceListKey" + private let deviceListKey = "RecordCache" func add(_ device: RecordDetailModel) { var deviceList = getList() diff --git a/ShenYin/ShenYin/UI/Home/Chat/ChatCell.swift b/ShenYin/ShenYin/UI/Home/Chat/ChatCell.swift index aad59a1..4c30c65 100644 --- a/ShenYin/ShenYin/UI/Home/Chat/ChatCell.swift +++ b/ShenYin/ShenYin/UI/Home/Chat/ChatCell.swift @@ -4,12 +4,7 @@ // // Created by liangshi on 2025/8/27. // -/// Ai回应模型 -public class ChatCellModel { - public var type: Int = 0//1发,0收 - public var content: String = "" - public var id:Int = 0 -} + class ChatCell:BaseTableViewCell{ let bgView = LoadingView() diff --git a/ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift b/ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift index e036073..2b3d9b5 100644 --- a/ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift +++ b/ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift @@ -76,11 +76,24 @@ class ChatVC:BaseVC{ PcmSound.sharedInstance().delegate = self JoyGemChatManager.shared().addObserver(self, forKeyPath: "isActive",options: [.new, .old], context: nil); -// let model = ChatCellModel() -// model.type = 0 -// model.content = "我是你的新朋友,有什么我可以帮助到你的?" -// model.id = 0 -// dataList.append(model) + +// // 创建一个自定义的串行队列 +// let customQueue = DispatchQueue(label: "com.example.customQueue") +// customQueue.async { +// // 这里的代码在子线程中执行 +// print("This is running on a background thread. Thread: \(Thread.current)") +// // 模拟一些耗时操作 +// Thread.sleep(forTimeInterval: 2) +// DispatchQueue.main.async { +// // 回到主线程更新UI等操作 +// print("Back on the main thread. Thread: \(Thread.current)") +// } +// } + YLprogressHUD.showLoading() + let list = ChatCache.shared.get() + dataList.append(contentsOf: list) + refreshData() + YLprogressHUD.hidden() loadRoleList() } @@ -89,6 +102,12 @@ class ChatVC:BaseVC{ ///结束对话,但并不会终止正在播放的录音 JoyGemChatManager.shared().stopChat() + + ChatCache.shared.saveList(dataList) + } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + refreshData() } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if (keyPath == "isActive") { @@ -192,8 +211,10 @@ extension ChatVC : JoyGemChatManagerDelegate { func chatManager(_ manager: JoyGemChatManager, audioData data: Data?, index: UInt, sampleRate: UInt, finish isFinish: Bool) { if (!isTtsPlaying) { isTtsPlaying = true + Logger.debug("开始播放") JoyGemChatManager.shared().playAudioStart() } + Logger.debug("收到音频数据", data?.count ?? 0) PcmSound.sharedInstance().feed(data) } diff --git a/ShenYin/ShenYin/UI/Home/HistoryRecord/VC/HistorySearchVC.swift b/ShenYin/ShenYin/UI/Home/HistoryRecord/VC/HistorySearchVC.swift new file mode 100644 index 0000000..c452bd0 --- /dev/null +++ b/ShenYin/ShenYin/UI/Home/HistoryRecord/VC/HistorySearchVC.swift @@ -0,0 +1,7 @@ +// +// HistorySearchVC.swift +// ShenYin +// +// Created by liangshi on 2025/11/10. +// + diff --git a/ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetaiContent.swift b/ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetaiContent.swift index 49f4ff7..7ffa28d 100644 --- a/ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetaiContent.swift +++ b/ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetaiContent.swift @@ -7,7 +7,8 @@ class RecordDetaiContent: BaseView { let titleLabel = UILabel() - let btn = UIButton(type: .custom) + let shareIcon = UIImageView() + let copyIcon = UIImageView() let textView = UITextView() var title:String! = ""{ didSet{ @@ -21,15 +22,21 @@ class RecordDetaiContent: BaseView { } override func setupViews() { - addSubviews([titleLabel,textView,btn]) + addSubviews([titleLabel,textView,shareIcon,copyIcon]) titleLabel.do { v in v.textColor = .rgb(0x0092E1) v.font = .regular(16) } - btn.do { v in - v.setImage(UIImage(named: "record_detail_copy"), for: .normal) - v.addTarget(self, action: #selector(copyAction), for: .touchUpInside) + shareIcon.do { v in +// v.backgroundColor = .red + v.image = UIImage(named: "share_icon_1") + v.yc.addTap(target: self, action: #selector(shareAction)) + } + copyIcon.do { v in +// v.backgroundColor = .red + v.image = UIImage(named: "record_detail_copy") + v.yc.addTap(target: self, action: #selector(copyAction)) } textView.do { v in v.text = "" @@ -46,11 +53,16 @@ class RecordDetaiContent: BaseView { make.top.equalToSuperview().inset(15) make.left.equalToSuperview().inset(18) } - btn.snp.makeConstraints { make in + copyIcon.snp.makeConstraints { make in make.size.equalTo(26) make.right.equalToSuperview().inset(18) make.centerY.equalTo(titleLabel) } + shareIcon.snp.makeConstraints { make in + make.size.equalTo(26) + make.right.equalTo(copyIcon.snp.left).offset(-6) + make.centerY.equalTo(titleLabel) + } textView.snp.makeConstraints { make in make.top.equalToSuperview().inset(35) make.left.right.equalToSuperview() @@ -59,6 +71,18 @@ class RecordDetaiContent: BaseView { } @objc func copyAction(){ UIPasteboard.general.string = textView.text -// YLprogressHUD.showAutoHiden("已拷贝") + YLprogressHUD.showAutoHiden("已复制") + } + @objc func shareAction(){ + if let text = textView.text{ + let vc = UIActivityViewController(activityItems: [text], applicationActivities: nil) + // 对于iPad,设置弹出源 + // vc.popoverPresentationController?.sourceView = self.view + // // 可以排除不需要的分享选项,例如排除打印和复制到剪贴板的选项 + // vc.excludedActivityTypes = [.print, .copyToPasteboard] + vc.modalPresentationStyle = .overFullScreen + App.shared.topVC().present(vc, animated: true) + return + } } } diff --git a/ShenYin/ShenYin/UI/Launch/BleManager.swift b/ShenYin/ShenYin/UI/Launch/BleManager.swift index 4c50b9b..72b9d71 100644 --- a/ShenYin/ShenYin/UI/Launch/BleManager.swift +++ b/ShenYin/ShenYin/UI/Launch/BleManager.swift @@ -261,7 +261,8 @@ extension BleManager : JoyGemAIManagerDelegate { } switch action { case .cancel: - YLprogressHUD.showAutoHiden("请求关闭录音功能/通知关闭了对话") + Logger.debug("请求关闭录音功能/通知关闭了对话") +// YLprogressHUD.showAutoHiden("请求关闭录音功能/通知关闭了对话") if let topVC = App.shared.topVC(), let vc = topVC as? FieldRecordingVC { vc.stopRecord() } @@ -269,19 +270,23 @@ extension BleManager : JoyGemAIManagerDelegate { vc.stopRecord() } case .recordLive: - YLprogressHUD.showAutoHiden("请求开启现场录音") + Logger.debug("请求开启现场录音") +// YLprogressHUD.showAutoHiden("请求开启现场录音") if !App.shared.topVC().isKind(of: FieldRecordingVC.self) { App.shared.goToPage(FieldRecordingVC(autoStart: true)) } case .recordCall: - YLprogressHUD.showAutoHiden("请求开启通话录音") + Logger.debug("请求开启通话录音") +// YLprogressHUD.showAutoHiden("请求开启通话录音") if !App.shared.topVC().isKind(of: CallingRecordVC.self) { App.shared.goToPage(CallingRecordVC(autoStart: true)) } case .si: - YLprogressHUD.showAutoHiden( "请求开启同传听译") + Logger.debug( "请求开启同传听译") +// YLprogressHUD.showAutoHiden( "请求开启同传听译") case .chat: - YLprogressHUD.showAutoHiden( "通知开启了AI对话") + Logger.debug("通知开启了AI对话") +// YLprogressHUD.showAutoHiden( "通知开启了AI对话") if !App.shared.topVC().isKind(of: ChatVC.self) { App.shared.goToPage(ChatVC()) } diff --git a/ShenYin/ShenYin/UI/Mine/LanguageVC.swift b/ShenYin/ShenYin/UI/Mine/LanguageVC.swift index e304dd3..6afbf03 100644 --- a/ShenYin/ShenYin/UI/Mine/LanguageVC.swift +++ b/ShenYin/ShenYin/UI/Mine/LanguageVC.swift @@ -5,21 +5,69 @@ // Created by liangshi on 2025/8/6. // - import UIKit - -class LanguageVC:BaseVC{ +class LanguageVC: BaseVC{ + let tableView = UITableView(frame: .zero, style: .plain) + var currentType = CountryModel() + var didSelected:((_ model:CountryModel)->Void)? override func viewDidLoad() { super.viewDidLoad() - - + // Do any additional setup after loading the view. } override func setupViews() { self.navBar.title = "语言设置" + tableView.do { v in + v.backgroundColor = .rgb(0xF7F7F7) + v.delegate = self + v.dataSource = self + v.rowHeight = 52 + v.separatorStyle = .none + v.register(SelectCell.self, forCellReuseIdentifier: SelectCell.reuseID) + } + view.addSubviews([tableView]) + } override func setupLayout() { - + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(kNavBtm) + } + } + override func setupData() { + LoginVM.fetchCountryList {[weak self] result in + guard let self = self else{return} + self.tableView.reloadData() + } + } +} + +extension LanguageVC:UITableViewDelegate,UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return LoginVM.shared.countryList.count } + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = SelectCell.cell(withTableView: tableView, indexPath: indexPath) + cell.country = LoginVM.shared.countryList[indexPath.row] + let rect = CGRect(x: 0, y: 0, width: kScreenWid - 36, height: 52) + if indexPath.row == 0 { + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.topLeft,.topRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else if indexPath.row == LoginVM.shared.countryList.count - 1{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.bottomLeft,.bottomRight], cornerRadii: CGSize(width: 8, height: 8)) + } + else{ + cell.bgView.yc.setCorners(withRoundedRect: rect, roundingCorners: [.allCorners], cornerRadii: CGSize(width: 0, height: 0)) + } + cell.selectedIcon.isHidden = cell.country.id != currentType.id + return cell + } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let data = LoginVM.shared.countryList[indexPath.row] + currentType = data + tableView.reloadData() + didSelected?(data) + navigationController?.popViewController(animated: true) + } } diff --git a/ShenYin/ShenYin/UI/Mine/Login/ViewModel/CountryModel.swift b/ShenYin/ShenYin/UI/Mine/Login/ViewModel/CountryModel.swift deleted file mode 100644 index 88fc5c4..0000000 --- a/ShenYin/ShenYin/UI/Mine/Login/ViewModel/CountryModel.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// CountryModel.swift -// ShenYin -// -// Created by liangshi on 2025/9/27. -// - -import Foundation -import HandyJSON -public class CountryModelRoot: ResponseBaseModel { - public var data = [CountryModel]() -} -public class CountryModel: HandyJSON { - public var id = "" - public var name = "" - public var language = "" - public var money = "" - public var phonePrefix = "" - - required public init() { } - - var isChinese:Bool{ - return name == "中国" - } - - static func chinese()->CountryModel{ - let model = CountryModel() - model.name = "中国" - model.phonePrefix = "86" - model.id = "1968848875208851458" - - return model - } -} diff --git a/ShenYin/ShenYin/UI/Mine/Login/ViewModel/LoginVM.swift b/ShenYin/ShenYin/UI/Mine/Login/ViewModel/LoginVM.swift index 1e939e5..c093d9a 100644 --- a/ShenYin/ShenYin/UI/Mine/Login/ViewModel/LoginVM.swift +++ b/ShenYin/ShenYin/UI/Mine/Login/ViewModel/LoginVM.swift @@ -34,6 +34,11 @@ class LoginModel:ResponseBaseModel { class LoginVM:BaseVM{ static let shared = LoginVM() var countryList = [CountryModel]() + + override init() { + countryList = CountryCache.shared.get() + + } static public func login(param:[String:Any], completion:@escaping (_ result:LoginModel) ->Void) { let api = CommomApi.login(param: param) @@ -85,9 +90,9 @@ class LoginVM:BaseVM{ HttpClient.request(CommomApi.countryList) { (model:CountryModelRoot, res) in if model.isSuccess{ LoginVM.shared.countryList = model.data + CountryCache.shared.saveList(model.data) } completion(model) } } - }