AI对话增加缓存
authorliangzhenying <867174124@qq.com>
Mon, 10 Nov 2025 09:07:17 +0000 (17:07 +0800)
committerliangzhenying <867174124@qq.com>
Mon, 10 Nov 2025 09:07:17 +0000 (17:07 +0800)
16 files changed:
ShenYin/ShenYin/Api/HttpClient2.swift
ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Contents.json [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@2x.png [new file with mode: 0644]
ShenYin/ShenYin/Assets.xcassets/home/share_icon_1.imageset/Frame 1987@3x.png [new file with mode: 0644]
ShenYin/ShenYin/Cache/ChatCache.swift [new file with mode: 0644]
ShenYin/ShenYin/Cache/CountryCache.swift [new file with mode: 0644]
ShenYin/ShenYin/Cache/DCFolder.swift
ShenYin/ShenYin/Cache/RecordCache.swift
ShenYin/ShenYin/UI/Home/Chat/ChatCell.swift
ShenYin/ShenYin/UI/Home/Chat/ChatVC.swift
ShenYin/ShenYin/UI/Home/HistoryRecord/VC/HistorySearchVC.swift [new file with mode: 0644]
ShenYin/ShenYin/UI/Home/RecordDetail/RecordDetaiContent.swift
ShenYin/ShenYin/UI/Launch/BleManager.swift
ShenYin/ShenYin/UI/Mine/LanguageVC.swift
ShenYin/ShenYin/UI/Mine/Login/ViewModel/CountryModel.swift [deleted file]
ShenYin/ShenYin/UI/Mine/Login/ViewModel/LoginVM.swift

index ff2820b..68339ee 100644 (file)
@@ -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 (file)
index 0000000..fdf7abb
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..96e96aa
--- /dev/null
@@ -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 (file)
index 0000000..fdf9e9a
--- /dev/null
@@ -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 []
+    }
+}
index be2be8c..376b999 100644 (file)
@@ -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
+    }
 }
index 0403f1a..7cb810c 100644 (file)
@@ -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()
index aad59a1..4c30c65 100644 (file)
@@ -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()
index e036073..2b3d9b5 100644 (file)
@@ -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 (file)
index 0000000..c452bd0
--- /dev/null
@@ -0,0 +1,7 @@
+//
+//  HistorySearchVC.swift
+//  ShenYin
+//
+//  Created by liangshi on 2025/11/10.
+//
+
index 49f4ff7..7ffa28d 100644 (file)
@@ -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
+        }
     }
 }
index 4c50b9b..72b9d71 100644 (file)
@@ -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())
             }
index e304dd3..6afbf03 100644 (file)
@@ -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 (file)
index 88fc5c4..0000000
+++ /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
-    }
-}
index 1e939e5..c093d9a 100644 (file)
@@ -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)
         }
     }
-    
 }