CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 100040;
+ CURRENT_PROJECT_VERSION = 100041;
DEVELOPMENT_TEAM = G23X75CJ67;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
CODE_SIGN_ENTITLEMENTS = ShenYin/ShenYin.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 100040;
+ CURRENT_PROJECT_VERSION = 100041;
DEVELOPMENT_TEAM = G23X75CJ67;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
App.shared.window = window
AppLaunch.start()
// MapManager.shared.setupSdk()
-// window?.rootViewController = UINavigationController(rootViewController: SportSetVC())
+// window?.rootViewController = UINavigationController(rootViewController: RSChatVC())
}
func sceneDidDisconnect(_ scene: UIScene) {
}
extension CCAiManager{
static func preparePlayBlueTooth(){
+ Logger.debug("准备播放")
PcmSound.sharedInstance().stopAudioUnit()
PcmSound.sharedInstance().cleanup()
PcmSound.sharedInstance().setup(16000, withchannels: 1)
// }
static func stopPlay(){
+ Logger.debug("停止播放")
PcmSound.sharedInstance().stopAudioUnit()
PcmSound.sharedInstance().cleanup()
}
/// 日记管理
class Logger {
- static var isDebug: Bool = true
+ static var isDebug: Bool = false
fileprivate static let shared = Logger()
private var fileHandle:FileHandle?
-// self.selectedIndex = 2
+// self.selectedIndex = 3
}
}
override func setupViews() {
addSubviews([icon,titleLabel,deviceLabel,nameLabel])
+ icon.do { v in
+ if CCDevice.isJoy{
+ v.yc.setCornerRadius(25)
+ }
+ }
titleLabel.do { (v) in
v.textColor = .color(.first)
v.font = .regular(24)
]
],
"inbound_attributes": [ // ASR输入音频配置
- "disable_vad": true, // 开启vad 自动断句 (开启后可以有客户端处理回音消除后持续发送音频由服务器判断是否转写和发送LLM)
+ "disable_vad": false, // 开启vad 自动断句 (开启后可以有客户端处理回音消除后持续发送音频由服务器判断是否转写和发送LLM)
"vad_silence": 400, // 设置vad 静音阈值 (毫秒) 连续静音超过 阈值自动切换状态
"interrupt_options": [
"concatenate_results": true, // 是否在打断后拼接前后的ASR结果
- "thinking_time_only": true // 是否只在思考时间允许打断 思考时间(ASR结束--->第一帧音频合成)
- // "keyword_interrupt": false, // 启用打断词功能 (打断词只有在思考以及之后才能打断)
- // "interrupt_keywords": ["停止", "打断","打断一下", "等一下", "别说了", "打住"] // 配置打断词列表
+ "thinking_time_only": true, // 是否只在思考时间允许打断 思考时间(ASR结束--->第一帧音频合成)
+// "keyword_interrupt": false, // 启用打断词功能 (打断词只有在思考以及之后才能打断)
+// "interrupt_keywords": ["停止", "打断","打断一下", "等一下", "别说了", "打住"] // 配置打断词列表
],
"cfg": [
"format": "pcm", // 音频输入格式
Logger.debug("还未鉴权")
return
}
- Logger.debug("send \(pcmData)")
+// Logger.debug("send \(pcmData)")
socket?.send(data: pcmData)
}
}
}
func webSocketMessageData(_ data: Data){
- Logger.debug("MSVoiceChat receive data ",data)
+// Logger.debug("MSVoiceChat receive data ",data)
ttsCallBack?(data)
}
import UIKit
import AVFAudio
class RSChatVC:BaseVC{
- let toolBar = ChatRecordBar()
+ let toolBar = RSRecordBar()
let tableView = UITableView(frame: CGRectZero, style: .plain)
let tipsLabel = UILabel()
let recordMamager = AiMouseRecordManager()
var dataList:[RSChatCellModel] = []
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ RSManager.shared.start()
+ }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
PcmSound.sharedInstance().delegate = self
- RSManager.shared.start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
CCAiManager.stopPlay()
+ toolBar.endBtnAction()
}
override func setupViews() {
navBar.leftView.isHidden = true
RSManager.shared.chat.eventCallBack = {[weak self] event in
guard let self = self else {return}
if event.event == "started" {
- print("开始已任务,可以开始录音")
- //2、开启录音
recordMamager.start()
}
+ else if event.event == "failed"{
+ toolBar.recordBtn.state = .active
+ }
+ else if event.event == "vad.active"{
+ toolBar.recordBtn.state = .active
+ }
+ else if event.event == "asr.started"{
+ toolBar.recordBtn.state = .asr
+ CCAiManager.stopPlay()
+ }
+ else if event.event == "asr.completed"{
+
+ }
+ else if event.event == "llm.created"{
+ toolBar.recordBtn.state = .respose
+ }
else if event.event == "asr.result",let text = event.payload?.content,let id = event.payload?.dialog_id{
if let model = dataList.last,model.id == id,model.isSend == true {
model.content = text
}
refreshData()
}
- else if event.event == "llm.completed",let text = event.payload?.content,let id = event.payload?.dialog_id{
-
+ else if event.event == "tts.started"{
+ CCAiManager.preparePlayBlueTooth()
}
}
RSManager.shared.chat.ttsCallBack = { data in
func refreshData(){
tableView.reloadData()
if dataList.count > 0 {
- tableView.scrollToRow(at: IndexPath(row: dataList.count - 1, section: 0), at: .none, animated: true)
+ tableView.scrollToRow(at: IndexPath(row: dataList.count - 1, section: 0), at: .none, animated: false)
}
}
}
-extension RSChatVC:ChatRecordBarDelegate{
- func recordBarCancel(_ toolBar: ChatRecordBar) {
+extension RSChatVC:RSRecordBarDelegate{
+ func recordBarCancel(_ toolBar: RSRecordBar) {
recordMamager.stop()
RSManager.shared.chat.cancelSpeedch()
if let item = dataList.last,item.content.isEmpty{
}
}
- func recordBarStart(_ toolBar: ChatRecordBar) {
- CCAiManager.stopPlay()
+ func recordBarStart(_ toolBar: RSRecordBar) {
RSManager.shared.chat.start()
}
- func recordBarStop(_ toolBar: ChatRecordBar) {
+ func recordBarStop(_ toolBar: RSRecordBar) {
recordMamager.stop()
- RSManager.shared.chat.endSpeedch()
- CCAiManager.preparePlayBlueTooth()
+ RSManager.shared.chat.cancelSpeedch()
+ CCAiManager.stopPlay()
}
}
class RSManager{
static let shared = RSManager()
// 8FFDCB2185D5、testflihgt
-// 8FFDCB2185D7、目前
+// 8FFDCB2185D7、testflihgt
// 8FFDCB2185D8、8FFDCB2185D9 还未使用
- let deviceId = "8FFDCB2185D7"
+ let deviceId = "8FFDCB2185D8"
var chat:MSVoiceChat = MSVoiceChat()
func start(){
func startLogin(password:String){
RSAuth.shared.login(deviceId: deviceId, password: password){[weak self] token,error in
if let err = error{
- if case .reponseError(let code,let message) = err,code == 50021{
+ if case .reponseError(let code,_) = err,code == 50021{
//50021是账号或密码错误.可以在这里调用注册,重新注册后,之前的密码会被覆盖,APP更新保存的新密码
self?.registerAction()
}
--- /dev/null
+//
+// RSRecordBar.swift
+// ShenYin
+//
+// Created by liangshi on 2026/1/28.
+//
+
+@objc protocol RSRecordBarDelegate {
+ func recordBarStart(_ toolBar:RSRecordBar)
+ func recordBarCancel(_ toolBar:RSRecordBar)
+ func recordBarStop(_ toolBar:RSRecordBar)
+}
+
+class RSRecordBar:BaseView{
+ let startBtn = CustomButton()
+ let endBtn = CustomButton()
+
+ let recordBtn = RSRecordStateView()
+ weak var delegate:RSRecordBarDelegate?
+
+ override func setupViews() {
+ startBtn.do { v in
+ v.yc.setCornerRadius(16)
+ v.backgroundColor = .color(.cellBg)
+ v.setTitleColor(.color(.first), for: .normal)
+ v.titleLabel?.font = .bold(16)
+ v.setTitle("开始对话".l10n, for: .normal)
+ v.addTarget(self, action: #selector(startBtnAction), for: .touchUpInside)
+ v.isHidden = false
+ }
+ endBtn.do { v in
+ v.yc.setCornerRadius(16)
+ v.backgroundColor = .color(.cellBg)
+ v.setTitleColor(.color(.first), for: .normal)
+ v.titleLabel?.font = .bold(16)
+ v.setTitle("结束对话".l10n, for: .normal)
+ v.addTarget(self, action: #selector(endBtnAction), for: .touchUpInside)
+ v.isHidden = true
+ }
+ recordBtn.do { v in
+ v.isHidden = true
+ }
+
+ addSubviews([startBtn,endBtn,recordBtn])
+
+ startBtn.snp.makeConstraints { make in
+ make.left.right.equalToSuperview().inset(18)
+ make.top.height.equalToSuperview()
+ }
+ endBtn.snp.makeConstraints { make in
+ make.right.equalToSuperview().inset(18)
+ make.width.equalTo(100)
+ make.top.height.equalToSuperview()
+ }
+ recordBtn.snp.makeConstraints { make in
+ make.left.equalToSuperview().inset(18)
+ make.right.equalTo(endBtn.snp.left).offset(-10)
+ make.top.height.equalToSuperview()
+ }
+ NotificationManager.add(self, selector: #selector(deviceChange(_:)), forName: .deviceValidChange)
+ updateState()
+ }
+ @objc func startBtnAction(){
+ if !CCBleManager.shared.isConnetd {
+ return
+ }
+ startBtn.isHidden = true
+ endBtn.isHidden = false
+ recordBtn.isHidden = false
+ recordBtn.state = .active
+ delegate?.recordBarStart(self)
+ }
+ @objc func endBtnAction(){
+ startBtn.isHidden = false
+ endBtn.isHidden = true
+ recordBtn.isHidden = true
+ delegate?.recordBarStop(self)
+ }
+ @objc func deviceChange(_ noti:Notification){
+ updateState()
+ }
+ func updateState(){
+ self.alpha = CCBleManager.shared.isConnetd ? 1:0.3
+ }
+}
+
+
+enum RSRecordState {
+ case active // 正在录音
+ case asr // 识别
+ case respose//响应
+}
+class RSRecordStateView: UIView {
+
+ private var animationBg = UIView()
+ private var animationView = UIImageView()
+
+
+ // 配置属性
+ private let normalColor = UIColor.white
+ private let recordingColor = UIColor.color(.theme)
+ private let titleLabel = UILabel()
+
+ // 状态标识
+ private var isRecording = false
+ private var isCanceling = false
+
+ private var startTime: Date?
+ private let minimumDuration: TimeInterval = 0.5
+
+ // 取消阈值(可根据需求调整)
+ private let cancelThreshold: CGFloat = 30
+
+ var state:RSRecordState = .active{
+ didSet{
+ if state == .active {
+ animationBg.backgroundColor = .color(.theme)
+ animationView.isHidden = false
+ titleLabel.isHidden = true
+ alpha = 1
+ }
+ else{
+ alpha = 0.5
+ animationBg.backgroundColor = .color(.theme)
+ animationView.isHidden = true
+ animationView.isHidden = true
+ titleLabel.isHidden = false
+ titleLabel.text = state == .asr ? "识别中".l10n : "回复中".l10n
+ }
+ }
+ }
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupUI()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setupUI()
+ }
+
+ private func setupUI() {
+
+ titleLabel.do { v in
+ v.textColor = .white
+ v.font = .bold(16)
+ v.textAlignment = .center
+ }
+
+ animationBg.do { v in
+ v.yc.setCornerRadius(16)
+ animationBg.backgroundColor = .color(.bgColor)
+ }
+
+ animationView.kf.setImage(with: Bundle.main.url(forResource: "dialog_record", withExtension: "gif")!)
+ self.updateUI()
+
+ addSubviews([animationBg])
+ animationBg.addSubviews([titleLabel,animationView])
+ titleLabel.snp.makeConstraints { make in
+ make.edges.equalToSuperview()
+ }
+
+ animationBg.snp.makeConstraints { make in
+ make.left.right.equalToSuperview()
+ make.top.height.equalToSuperview()
+ }
+ animationView.snp.makeConstraints { make in
+ make.width.equalTo(218)
+ make.height.equalTo(24)
+ make.center.equalToSuperview()
+ }
+ }
+
+
+ private func updateUI() {
+ if isRecording {
+ animationView.isHidden = false
+ if isCanceling {
+ animationBg.backgroundColor = .hexString("#FF4E4E")
+ titleLabel.isHidden = true
+ }
+ else{
+ titleLabel.isHidden = true
+ animationBg.backgroundColor = .color(.theme)
+ }
+ } else {
+ titleLabel.isHidden = false
+
+ animationBg.backgroundColor = .color(.cellBg)
+ animationView.isHidden = true
+ animationView.isHidden = true
+ }
+ }
+
+}