diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 1765a18..7ee8df5 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -6,7 +6,8 @@
"Bash(xcodebuild:*)",
"Bash(plutil:*)",
"Bash(find:*)",
- "Bash(ls:*)"
+ "Bash(ls:*)",
+ "Bash(wc:*)"
]
}
}
diff --git a/CustomKeyboard/PrivacyInfo.xcprivacy b/CustomKeyboard/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000..decaaca
--- /dev/null
+++ b/CustomKeyboard/PrivacyInfo.xcprivacy
@@ -0,0 +1,44 @@
+
+
+
+
+ NSPrivacyTracking
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyCollectedDataTypes
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeOtherUserContent
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyAccessedAPITypes
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryUserDefaults
+ NSPrivacyAccessedAPITypeReasons
+
+ CA92.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPITypeReasons
+
+ C617.1
+
+
+
+
+
diff --git a/Shared/KBAPI.h b/Shared/KBAPI.h
index 27615db..957913d 100644
--- a/Shared/KBAPI.h
+++ b/Shared/KBAPI.h
@@ -28,6 +28,7 @@
#define API_LOGOUT @"/user/logout" // 退出登录
+#define API_USER_CANCEL_ACCOUNT @"/user/cancelAccount" // 注销账户
#define API_UPDATA_INFO @"/user/updateInfo" // 更新用户
diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings
index 7a0f9f1..bc127b3 100644
--- a/Shared/Localization/en.lproj/Localizable.strings
+++ b/Shared/Localization/en.lproj/Localizable.strings
@@ -172,6 +172,8 @@
"Delete" = "Delete";
"Points\nMall" = "Points\nMall";
"Log Out" = "Log Out";
+"Cancel Account" = "Cancel Account";
+"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "After cancellation, your account will be deactivated and local login data will be cleared. Continue?";
"Ranking List" = "Ranking List";
"Persona circle" = "Persona circle";
"Clear" = "Clear";
diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings
index 1f46e91..aaab542 100644
--- a/Shared/Localization/zh-Hans.lproj/Localizable.strings
+++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings
@@ -172,6 +172,8 @@
"Delete" = "删除";
"Points\nMall" = "积分\n商城";
"Log Out" = "退出";
+"Cancel Account" = "注销账户";
+"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "注销后账号将被停用,并清除本地登录数据,是否继续?";
"Ranking List" = "排行榜";
"Persona circle" = "圈子";
"Clear" = "立刻清空";
diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj
index e4ca847..1e0d681 100644
--- a/keyBoard.xcodeproj/project.pbxproj
+++ b/keyBoard.xcodeproj/project.pbxproj
@@ -233,6 +233,8 @@
04E0B2022F300002002CA5A0 /* KBVoiceRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */; };
04E161832F10E6470022C23B /* normal_hei_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161812F10E6470022C23B /* normal_hei_them.zip */; };
04E161842F10E6470022C23B /* normal_them.zip in Resources */ = {isa = PBXBuildFile; fileRef = 04E161822F10E6470022C23B /* normal_them.zip */; };
+ 04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */; };
+ 04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */; };
04F4C0AA2F32274000E8F08C /* KBPayMainVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0A92F32274000E8F08C /* KBPayMainVC.m */; };
04F4C0AD2F32288600E8F08C /* KBPaySvipVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AC2F32288600E8F08C /* KBPaySvipVC.m */; };
04F4C0B02F322EF200E8F08C /* PagingViewTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04F4C0AF2F322EF200E8F08C /* PagingViewTableHeaderView.m */; };
@@ -719,6 +721,8 @@
04E0B2012F300002002CA5A0 /* KBVoiceRecordManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBVoiceRecordManager.m; sourceTree = ""; };
04E161812F10E6470022C23B /* normal_hei_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_hei_them.zip; sourceTree = ""; };
04E161822F10E6470022C23B /* normal_them.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = normal_them.zip; sourceTree = ""; };
+ 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
+ 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
04F4C0A82F32274000E8F08C /* KBPayMainVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPayMainVC.h; sourceTree = ""; };
04F4C0A92F32274000E8F08C /* KBPayMainVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPayMainVC.m; sourceTree = ""; };
04F4C0AB2F32288600E8F08C /* KBPaySvipVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPaySvipVC.h; sourceTree = ""; };
@@ -1568,6 +1572,7 @@
04C6EAB92EAF86530089C901 /* keyBoard */ = {
isa = PBXGroup;
children = (
+ 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */,
04FC95F52EB33B52007BD342 /* keyBoard.entitlements */,
04FC95BF2EB1E3B1007BD342 /* Class */,
04C6EAE32EAF942E0089C901 /* VC */,
@@ -1588,6 +1593,7 @@
04C6EAD72EAF870B0089C901 /* CustomKeyboard */ = {
isa = PBXGroup;
children = (
+ 04E2277E2F516ED3001A8F14 /* PrivacyInfo.xcprivacy */,
0419C9632F2C7630002E86D3 /* VM */,
041007D02ECE010100D203BB /* Resource */,
0477BD942EBAFF4E0055D639 /* Utils */,
@@ -2257,6 +2263,7 @@
0460866B2F18D75500757C95 /* ai_test.m4a in Resources */,
041007D42ECE012500D203BB /* 002.zip in Resources */,
041007D22ECE012000D203BB /* KBSkinIconMap.strings in Resources */,
+ 04E2277F2F516ED3001A8F14 /* PrivacyInfo.xcprivacy in Resources */,
A1B2C3ED2F20000000000001 /* kb_words.txt in Resources */,
A1B2C3F12F20000000000002 /* kb_keyboard_layout_config.json in Resources */,
0498BDF52EEC50EE006CC1D5 /* emoji_categories.json in Resources */,
@@ -2272,6 +2279,7 @@
04286A0F2ECDA71B00CE730C /* 001.zip in Resources */,
04E038D82F20BFFB002CA5A0 /* websocket-api.md in Resources */,
0479200B2ED87CEE004E8522 /* permiss_video.mp4 in Resources */,
+ 04E2277D2F516EBD001A8F14 /* PrivacyInfo.xcprivacy in Resources */,
04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */,
04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */,
047920072ED86ABC004E8522 /* kb_guide_keyboard.gif in Resources */,
diff --git a/keyBoard/Class/AiTalk/VC/KBAiMainVC.h b/keyBoard/Class/AiTalk/VC/KBAiMainVC.h
deleted file mode 100644
index d1c4ea7..0000000
--- a/keyBoard/Class/AiTalk/VC/KBAiMainVC.h
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// KBAiMainVC.h
-// keyBoard
-//
-// Created by Mac on 2026/1/15.
-//
-
-#import
-
-NS_ASSUME_NONNULL_BEGIN
-
-/// AI 语音陪伴聊天主界面
-@interface KBAiMainVC : BaseViewController
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/keyBoard/Class/AiTalk/VC/KBAiMainVC.m b/keyBoard/Class/AiTalk/VC/KBAiMainVC.m
deleted file mode 100644
index 228bff0..0000000
--- a/keyBoard/Class/AiTalk/VC/KBAiMainVC.m
+++ /dev/null
@@ -1,809 +0,0 @@
-//
-// KBAiMainVC.m
-// keyBoard
-//
-// Created by Mac on 2026/1/15.
-//
-
-#import "KBAiMainVC.h"
-#import "ConversationOrchestrator.h"
-#import "AiVM.h"
-#import "AudioSessionManager.h"
-#import "DeepgramStreamingManager.h"
-#import "KBAICommentView.h"
-#import "KBChatTableView.h"
-#import "KBAiRecordButton.h"
-#import "KBHUD.h"
-#import "KBChatLimitPopView.h"
-#import "KBPayMainVC.h"
-#import "LSTPopView.h"
-#import "VoiceChatStreamingManager.h"
-#import "KBUserSessionManager.h"
-#import
-
-@interface KBAiMainVC ()
-@property(nonatomic, weak) LSTPopView *popView;
-@property(nonatomic, weak) LSTPopView *limitPopView;
-
-// UI
-@property(nonatomic, strong) KBChatTableView *chatView;
-@property(nonatomic, strong) KBAiRecordButton *recordButton;
-@property(nonatomic, strong) UILabel *statusLabel;
-@property(nonatomic, strong) UILabel *transcriptLabel;
-@property(nonatomic, strong) UIButton *commentButton;
-@property(nonatomic, strong) KBAICommentView *commentView;
-@property(nonatomic, strong) UIView *tabbarBackgroundView;
-@property(nonatomic, strong) UIVisualEffectView *blurEffectView;
-@property(nonatomic, strong) CAGradientLayer *gradientLayer;
-@property(nonatomic, strong) UIImageView *personImageView;
-
-// 核心模块
-@property(nonatomic, strong) ConversationOrchestrator *orchestrator;
-@property(nonatomic, strong) VoiceChatStreamingManager *streamingManager;
-@property(nonatomic, strong) DeepgramStreamingManager *deepgramManager;
-@property(nonatomic, strong) AiVM *aiVM;
-@property(nonatomic, strong) AVAudioPlayer *aiAudioPlayer;
-@property(nonatomic, strong) NSMutableData *voiceChatAudioBuffer;
-
-// 文本跟踪
-@property(nonatomic, strong) NSMutableString *assistantVisibleText;
-@property(nonatomic, strong) NSMutableString *deepgramFullText;
-
-// 日志节流
-@property(nonatomic, assign) NSTimeInterval lastRMSLogTime;
-
-@end
-
-@implementation KBAiMainVC
-
-#pragma mark - Lifecycle
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
- // 让视图延伸到屏幕边缘(包括状态栏和导航栏下方)
- self.edgesForExtendedLayout = UIRectEdgeAll;
- self.extendedLayoutIncludesOpaqueBars = YES;
-
- [self setupUI];
- [self setupOrchestrator];
- [self setupStreamingManager];
- [self setupDeepgramManager];
-}
-
-- (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
- // TabBar 背景色由 BaseTabBarController 统一管理,这里不需要设置
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
- [super viewWillDisappear:animated];
-
- // 页面消失时停止对话
- [self.orchestrator stop];
- [self.streamingManager disconnect];
- [self.deepgramManager disconnect];
-}
-
-- (void)viewDidLayoutSubviews {
- [super viewDidLayoutSubviews];
-
- // 只更新 mask 的 frame(mask 已在 setupUI 中创建)
- if (self.blurEffectView.layer.mask) {
- self.blurEffectView.layer.mask.frame = self.blurEffectView.bounds;
- }
-}
-
-#pragma mark - UI Setup
-
-- (void)setupUI {
- self.view.backgroundColor = [UIColor whiteColor];
- self.title = @"AI 助手";
-
- // 安全区域
- UILayoutGuide *safeArea = self.view.safeAreaLayoutGuide;
-
- // PersonImageView(背景图,最底层)
- self.personImageView =
- [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"person_icon"]];
- [self.view addSubview:self.personImageView];
- [self.personImageView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.left.right.top.bottom.equalTo(self.view);
- }];
-
- // TabBar 毛玻璃模糊背景(在 personImageView 之上)
- self.tabbarBackgroundView = [[UIView alloc] init];
- self.tabbarBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
- self.tabbarBackgroundView.clipsToBounds = YES;
- [self.view addSubview:self.tabbarBackgroundView];
-
- // 模糊效果
- UIBlurEffect *blurEffect =
- [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
- self.blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
- self.blurEffectView.translatesAutoresizingMaskIntoConstraints = NO;
- [self.tabbarBackgroundView addSubview:self.blurEffectView];
-
- // 为 blurEffectView 创建透明度渐变
- // mask(从底部到中间不透明,从中间到顶部透明)
- CAGradientLayer *maskLayer = [CAGradientLayer layer];
- maskLayer.startPoint = CGPointMake(0.5, 1); // 底部
- maskLayer.endPoint = CGPointMake(0.5, 0); // 顶部
- // 底部到中间保持不透明,从中间到顶部过渡透明
- maskLayer.colors = @[
- (__bridge id)[UIColor whiteColor].CGColor, // 底部:完全不透明
- (__bridge id)[UIColor whiteColor].CGColor, // 中间:完全不透明
- (__bridge id)[UIColor clearColor].CGColor // 顶部:完全透明
- ];
- maskLayer.locations = @[ @(0.0), @(0.5), @(1.0) ];
- self.blurEffectView.layer.mask = maskLayer;
-
- // 状态标签
- self.statusLabel = [[UILabel alloc] init];
- self.statusLabel.text = @"按住按钮开始对话";
- self.statusLabel.font = [UIFont systemFontOfSize:14];
- self.statusLabel.textColor = [UIColor secondaryLabelColor];
- self.statusLabel.textAlignment = NSTextAlignmentCenter;
- self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:self.statusLabel];
-
- // 转写文本标签
- self.transcriptLabel = [[UILabel alloc] init];
- self.transcriptLabel.text = @"";
- self.transcriptLabel.font = [UIFont systemFontOfSize:16];
- self.transcriptLabel.textColor = [UIColor labelColor];
- self.transcriptLabel.numberOfLines = 0;
- self.transcriptLabel.textAlignment = NSTextAlignmentRight;
- self.transcriptLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:self.transcriptLabel];
-
- // 聊天视图
- self.chatView = [[KBChatTableView alloc] init];
- self.chatView.backgroundColor = [UIColor clearColor];
- self.chatView.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:self.chatView];
-
- // 录音按钮
- self.recordButton = [[KBAiRecordButton alloc] init];
- self.recordButton.delegate = self;
- self.recordButton.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:self.recordButton];
-
- // 评论按钮(聊天视图右侧居中)
- self.commentButton = [UIButton buttonWithType:UIButtonTypeCustom];
- [self.commentButton setImage:[UIImage systemImageNamed:@"bubble.right.fill"]
- forState:UIControlStateNormal];
- self.commentButton.tintColor = [UIColor whiteColor];
- self.commentButton.backgroundColor = [UIColor systemBlueColor];
- self.commentButton.layer.cornerRadius = 25;
- self.commentButton.layer.shadowColor = [UIColor blackColor].CGColor;
- self.commentButton.layer.shadowOffset = CGSizeMake(0, 2);
- self.commentButton.layer.shadowOpacity = 0.3;
- self.commentButton.layer.shadowRadius = 4;
- self.commentButton.translatesAutoresizingMaskIntoConstraints = NO;
- [self.commentButton addTarget:self
- action:@selector(showComment)
- forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:self.commentButton];
-
- // 布局约束 - 使用 Masonry
- [self.tabbarBackgroundView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.left.right.bottom.equalTo(self.view);
- make.height.mas_equalTo(KBFit(238));
- }];
-
- [self.blurEffectView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.edges.equalTo(self.tabbarBackgroundView);
- }];
-
- [self.statusLabel mas_makeConstraints:^(MASConstraintMaker *make) {
- make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(8);
- make.left.equalTo(self.view).offset(16);
- make.right.equalTo(self.view).offset(-16);
- // 设置固定高度,避免内容变化导致布局跳动
- make.height.mas_equalTo(20); // 单行文本高度
- }];
-
- [self.transcriptLabel mas_makeConstraints:^(MASConstraintMaker *make) {
- make.top.equalTo(self.statusLabel.mas_bottom).offset(8);
- make.left.equalTo(self.view).offset(16);
- make.right.equalTo(self.view).offset(-16);
- // 设置固定高度,避免内容变化导致布局跳动
- make.height.mas_equalTo(60); // 根据实际需要调整高度
- }];
- // 设置内容压缩阻力,避免被压缩
- [self.transcriptLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
- forAxis:UILayoutConstraintAxisVertical];
-
- [self.chatView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.left.right.equalTo(self.view);
- make.bottom.equalTo(self.tabbarBackgroundView.mas_top).offset(-8);
- make.top.equalTo(self.transcriptLabel.mas_bottom).offset(8);
- // 设置最小高度,避免被压缩为 0
- make.height.greaterThanOrEqualTo(@100).priority(MASLayoutPriorityDefaultHigh);
- }];
- // chatView 应该尽可能占据空间
- [self.chatView setContentCompressionResistancePriority:UILayoutPriorityRequired
- forAxis:UILayoutConstraintAxisVertical];
-
- [self.recordButton mas_makeConstraints:^(MASConstraintMaker *make) {
- make.left.equalTo(self.view.mas_safeAreaLayoutGuideLeft).offset(20);
- make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight).offset(-20);
- make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-16);
- make.height.mas_equalTo(50);
- }];
-
- [self.commentButton mas_makeConstraints:^(MASConstraintMaker *make) {
- make.right.equalTo(self.view.mas_safeAreaLayoutGuideRight).offset(-16);
- make.centerY.equalTo(self.view);
- make.width.height.mas_equalTo(50);
- }];
-}
-
-#pragma mark - Orchestrator Setup
-
-- (void)setupOrchestrator {
- self.orchestrator = [[ConversationOrchestrator alloc] init];
-
- // 配置服务器地址
- // 1. ASR 语音识别服务(WebSocket)
- self.orchestrator.asrServerURL = @"ws://192.168.2.21:7529/ws/asr";
-
- // 2. LLM 大语言模型服务(HTTP Stream)
- self.orchestrator.llmServerURL = @"http://192.168.2.21:7529/api/chat/stream";
-
- // 3. TTS 语音合成服务(HTTP)
- self.orchestrator.ttsServerURL = @"http://192.168.2.21:7529/api/tts/stream";
-
- __weak typeof(self) weakSelf = self;
-
- // 状态变化回调
- self.orchestrator.onStateChange = ^(ConversationState state) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf updateStatusForState:state];
- };
-
- // 实时识别文本回调
- self.orchestrator.onPartialText = ^(NSString *text) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- strongSelf.statusLabel.text = text.length > 0 ? text : @"正在识别...";
- };
-
- // 用户最终文本回调
- self.orchestrator.onUserFinalText = ^(NSString *text) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- if (text.length > 0) {
- [strongSelf.chatView addUserMessage:text];
- }
- };
-
- // AI 可见文本回调(打字机效果)
- self.orchestrator.onAssistantVisibleText = ^(NSString *text) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf.chatView updateLastAssistantMessage:text];
- };
-
- // AI 完整回复回调
- self.orchestrator.onAssistantFullText = ^(NSString *text) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf.chatView updateLastAssistantMessage:text];
- [strongSelf.chatView markLastAssistantMessageComplete];
- };
-
- // 音量更新回调
- self.orchestrator.onVolumeUpdate = ^(float rms) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf.recordButton updateVolumeRMS:rms];
- };
-
- // AI 开始说话
- self.orchestrator.onSpeakingStart = ^{
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- // 添加空的 AI 消息占位
- [strongSelf.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil];
- };
-
- // AI 说话结束
- self.orchestrator.onSpeakingEnd = ^{
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf.chatView markLastAssistantMessageComplete];
- };
-
- // 错误回调
- self.orchestrator.onError = ^(NSError *error) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf)
- return;
-
- [strongSelf showError:error];
- };
-}
-
-#pragma mark - Streaming Manager
-
-- (void)setupStreamingManager {
- self.streamingManager = [[VoiceChatStreamingManager alloc] init];
- self.streamingManager.delegate = self;
- self.streamingManager.serverURL = @"ws://192.168.2.21:7529/api/ws/chat";
- self.assistantVisibleText = [[NSMutableString alloc] init];
- self.voiceChatAudioBuffer = [[NSMutableData alloc] init];
- self.lastRMSLogTime = 0;
-}
-
-#pragma mark - Deepgram Manager
-
-- (void)setupDeepgramManager {
- self.deepgramManager = [[DeepgramStreamingManager alloc] init];
- self.deepgramManager.delegate = self;
- self.deepgramManager.serverURL = @"wss://api.deepgram.com/v1/listen";
- self.deepgramManager.apiKey = @"9c792eb63a65d644cbc95785155754cd1e84f8cf";
- self.deepgramManager.language = @"en";
- self.deepgramManager.model = @"nova-3";
- self.deepgramManager.punctuate = YES;
- self.deepgramManager.smartFormat = YES;
- self.deepgramManager.interimResults = YES;
- self.deepgramManager.encoding = @"linear16";
- self.deepgramManager.sampleRate = 16000.0;
- self.deepgramManager.channels = 1;
- [self.deepgramManager prepareConnection];
-
- self.deepgramFullText = [[NSMutableString alloc] init];
- self.aiVM = [[AiVM alloc] init];
-}
-
-#pragma mark - 事件
-- (void)showComment {
- CGFloat customViewHeight = KB_SCREEN_HEIGHT * (0.8);
- KBAICommentView *customView = [[KBAICommentView alloc]
- initWithFrame:CGRectMake(0, 0, KB_SCREEN_WIDTH, customViewHeight)];
- LSTPopView *popView =
- [LSTPopView initWithCustomView:customView
- parentView:nil
- popStyle:LSTPopStyleSmoothFromBottom
- dismissStyle:LSTDismissStyleSmoothToBottom];
- self.popView = popView;
- popView.priority = 1000;
- popView.isAvoidKeyboard = false;
- popView.hemStyle = LSTHemStyleBottom;
- popView.dragStyle = LSTDragStyleY_Positive;
- popView.dragDistance = customViewHeight * 0.5;
- popView.sweepStyle = LSTSweepStyleY_Positive;
- popView.swipeVelocity = 1600;
- popView.sweepDismissStyle = LSTSweepDismissStyleSmooth;
-
- [popView pop];
-}
-
-- (void)showCommentDirectly {
- if (self.commentView.superview) {
- [self.view bringSubviewToFront:self.commentView];
- return;
- }
-
- CGFloat customViewHeight = KB_SCREEN_HEIGHT * (0.8);
- KBAICommentView *customView =
- [[KBAICommentView alloc] initWithFrame:CGRectZero];
- customView.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:customView];
- [NSLayoutConstraint activateConstraints:@[
- [customView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
- [customView.trailingAnchor
- constraintEqualToAnchor:self.view.trailingAnchor],
- [customView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
- [customView.heightAnchor constraintEqualToConstant:customViewHeight],
- ]];
- self.commentView = customView;
-}
-
-#pragma mark - 次数用尽弹窗
-
-- (void)showChatLimitPopWithMessage:(NSString *)message {
- if (self.limitPopView) {
- [self.limitPopView dismiss];
- }
-
- CGFloat width = 252.0;
- CGFloat height = 252.0 + 18.0 + 53.0 + 18.0 + 28.0;
- KBChatLimitPopView *content =
- [[KBChatLimitPopView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
- content.message = message;
- content.delegate = self;
-
- LSTPopView *popView =
- [LSTPopView initWithCustomView:content
- parentView:nil
- popStyle:LSTPopStyleFade
- dismissStyle:LSTDismissStyleFade];
- popView.bgColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
- popView.hemStyle = LSTHemStyleCenter;
- popView.isClickBgDismiss = YES;
- popView.isAvoidKeyboard = NO;
- self.limitPopView = popView;
- [popView pop];
-}
-
-#pragma mark - KBChatLimitPopViewDelegate
-
-- (void)chatLimitPopViewDidTapCancel:(KBChatLimitPopView *)view {
- [self.limitPopView dismiss];
-}
-
-- (void)chatLimitPopViewDidTapRecharge:(KBChatLimitPopView *)view {
- [self.limitPopView dismiss];
- if (![KBUserSessionManager shared].isLoggedIn) {
- [[KBUserSessionManager shared] goLoginVC];
- return;
- }
- KBPayMainVC *vc = [[KBPayMainVC alloc] init];
- vc.initialSelectedIndex = 1; // SVIP
- [KB_CURRENT_NAV pushViewController:vc animated:true];
-}
-
-#pragma mark - UI Updates
-
-- (void)updateStatusForState:(ConversationState)state {
- switch (state) {
- case ConversationStateIdle:
- self.statusLabel.text = @"按住按钮开始对话";
- self.recordButton.state = KBAiRecordButtonStateNormal;
- break;
-
- case ConversationStateListening:
- self.statusLabel.text = @"正在聆听...";
- self.recordButton.state = KBAiRecordButtonStateRecording;
- break;
-
- case ConversationStateRecognizing:
- self.statusLabel.text = @"正在识别...";
- self.recordButton.state = KBAiRecordButtonStateNormal;
- break;
-
- case ConversationStateThinking:
- self.statusLabel.text = @"AI 正在思考...";
- self.recordButton.state = KBAiRecordButtonStateNormal;
- break;
-
- case ConversationStateSpeaking:
- self.statusLabel.text = @"AI 正在回复...";
- self.recordButton.state = KBAiRecordButtonStateNormal;
- break;
- }
-}
-
-- (void)showError:(NSError *)error {
- UIAlertController *alert =
- [UIAlertController alertControllerWithTitle:@"错误"
- message:error.localizedDescription
- preferredStyle:UIAlertControllerStyleAlert];
- [alert addAction:[UIAlertAction actionWithTitle:@"确定"
- style:UIAlertActionStyleDefault
- handler:nil]];
- [self presentViewController:alert animated:YES completion:nil];
-}
-
-#pragma mark - KBAiRecordButtonDelegate
-
-- (void)recordButtonDidBeginPress:(KBAiRecordButton *)button {
- NSLog(@"[KBAiMainVC] Record button began press");
-
- // 停止正在播放的音频
- [self.chatView stopPlayingAudio];
-
- NSString *token = [[KBUserSessionManager shared] accessToken] ?: @"";
- if (token.length == 0) {
- [[KBUserSessionManager shared] goLoginVC];
- return;
- }
-
- self.statusLabel.text = @"正在连接...";
- self.recordButton.state = KBAiRecordButtonStateRecording;
- [self.deepgramFullText setString:@""];
- self.transcriptLabel.text = @"";
- [self.deepgramManager start];
-}
-
-- (void)recordButtonDidEndPress:(KBAiRecordButton *)button {
- NSLog(@"[KBAiMainVC] Record button end press");
- [self.deepgramManager stopAndFinalize];
-}
-
-- (void)recordButtonDidCancelPress:(KBAiRecordButton *)button {
- NSLog(@"[KBAiMainVC] Record button cancel press");
- [self.deepgramManager cancel];
-}
-
-#pragma mark - VoiceChatStreamingManagerDelegate
-
-- (void)voiceChatStreamingManagerDidConnect {
- self.statusLabel.text = @"已连接,准备中...";
-}
-
-- (void)voiceChatStreamingManagerDidDisconnect:(NSError *_Nullable)error {
- self.recordButton.state = KBAiRecordButtonStateNormal;
- if (error) {
- [self showError:error];
- }
-}
-
-- (void)voiceChatStreamingManagerDidStartSession:(NSString *)sessionId {
- self.statusLabel.text = @"正在聆听...";
- self.recordButton.state = KBAiRecordButtonStateRecording;
-}
-
-- (void)voiceChatStreamingManagerDidStartTurn:(NSInteger)turnIndex {
- self.statusLabel.text = @"正在聆听...";
- self.recordButton.state = KBAiRecordButtonStateRecording;
-}
-
-- (void)voiceChatStreamingManagerDidReceiveEagerEndOfTurnWithTranscript:(NSString *)text
- confidence:(double)confidence {
- self.statusLabel.text = @"准备响应...";
-}
-
-- (void)voiceChatStreamingManagerDidResumeTurn {
- self.statusLabel.text = @"正在聆听...";
-}
-
-- (void)voiceChatStreamingManagerDidUpdateRMS:(float)rms {
- [self.recordButton updateVolumeRMS:rms];
- NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
- if (now - self.lastRMSLogTime >= 1.0) {
- self.lastRMSLogTime = now;
- NSLog(@"[KBAiMainVC] RMS: %.3f", rms);
- }
-}
-
-- (void)voiceChatStreamingManagerDidReceiveInterimTranscript:(NSString *)text {
- self.statusLabel.text = @"正在识别...";
- if (text.length > 0) {
- self.transcriptLabel.text = text;
- }
-}
-
-- (void)voiceChatStreamingManagerDidReceiveFinalTranscript:(NSString *)text {
- if (text.length > 0) {
- self.transcriptLabel.text = @"";
- [self.chatView addUserMessage:text];
- }
-}
-
-- (void)voiceChatStreamingManagerDidReceiveLLMStart {
- self.statusLabel.text = @"AI 正在思考...";
- [self.assistantVisibleText setString:@""];
- [self.chatView addAssistantMessage:@"" audioDuration:0 audioData:nil];
- [self.voiceChatAudioBuffer setLength:0];
-}
-
-- (void)voiceChatStreamingManagerDidReceiveLLMToken:(NSString *)token {
- if (token.length == 0) {
- return;
- }
-
- [self.assistantVisibleText appendString:token];
- [self.chatView updateLastAssistantMessage:self.assistantVisibleText];
-}
-
-- (void)voiceChatStreamingManagerDidReceiveAudioChunk:(NSData *)audioData {
- if (audioData.length == 0) {
- return;
- }
- [self.voiceChatAudioBuffer appendData:audioData];
-}
-
-- (void)voiceChatStreamingManagerDidCompleteWithTranscript:(NSString *)transcript
- aiResponse:(NSString *)aiResponse {
- NSString *finalText = aiResponse.length > 0 ? aiResponse : self.assistantVisibleText;
- if (aiResponse.length > 0) {
- [self.assistantVisibleText setString:aiResponse];
- }
-
- // 计算音频时长
- NSTimeInterval duration = 0;
- if (self.voiceChatAudioBuffer.length > 0) {
- NSError *error = nil;
- AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:self.voiceChatAudioBuffer
- error:&error];
- if (!error && player) {
- duration = player.duration;
- }
- }
-
- if (finalText.length > 0) {
- [self.chatView updateLastAssistantMessage:finalText];
- [self.chatView markLastAssistantMessageComplete];
- } else if (transcript.length > 0) {
- [self.chatView addAssistantMessage:transcript
- audioDuration:duration
- audioData:self.voiceChatAudioBuffer.length > 0 ? self.voiceChatAudioBuffer : nil];
- }
-
- if (self.voiceChatAudioBuffer.length > 0) {
- [self playAiAudioData:self.voiceChatAudioBuffer];
- [self.voiceChatAudioBuffer setLength:0];
- }
-
- self.recordButton.state = KBAiRecordButtonStateNormal;
- self.statusLabel.text = @"完成";
-}
-
-- (void)voiceChatStreamingManagerDidFail:(NSError *)error {
- self.recordButton.state = KBAiRecordButtonStateNormal;
- [self showError:error];
-}
-
-#pragma mark - DeepgramStreamingManagerDelegate
-
-- (void)deepgramStreamingManagerDidConnect {
- self.statusLabel.text = @"已连接,准备中...";
-}
-
-- (void)deepgramStreamingManagerDidDisconnect:(NSError *_Nullable)error {
- self.recordButton.state = KBAiRecordButtonStateNormal;
- if (error) {
- [self showError:error];
- }
-}
-
-- (void)deepgramStreamingManagerDidUpdateRMS:(float)rms {
- [self.recordButton updateVolumeRMS:rms];
- NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
- if (now - self.lastRMSLogTime >= 1.0) {
- self.lastRMSLogTime = now;
- NSLog(@"[KBAiMainVC] RMS: %.3f", rms);
- }
-}
-
-- (void)deepgramStreamingManagerDidReceiveInterimTranscript:(NSString *)text {
- self.statusLabel.text = @"正在识别...";
- NSString *displayText = text ?: @"";
- if (self.deepgramFullText.length > 0 && displayText.length > 0) {
- displayText =
- [NSString stringWithFormat:@"%@ %@", self.deepgramFullText, displayText];
- } else if (self.deepgramFullText.length > 0) {
- displayText = [self.deepgramFullText copy];
- }
- self.transcriptLabel.text = displayText;
-}
-
-- (void)deepgramStreamingManagerDidReceiveFinalTranscript:(NSString *)text {
- if (text.length > 0) {
- if (self.deepgramFullText.length > 0) {
- [self.deepgramFullText appendString:@" "];
- }
- [self.deepgramFullText appendString:text];
- }
- self.transcriptLabel.text = self.deepgramFullText;
- self.statusLabel.text = @"识别完成";
- self.recordButton.state = KBAiRecordButtonStateNormal;
-
- NSString *finalText = [self.deepgramFullText copy];
- if (finalText.length == 0) {
- return;
- }
-
- // 添加用户消息
- [self.chatView addUserMessage:finalText];
-
- __weak typeof(self) weakSelf = self;
- [KBHUD showWithStatus:@"AI 思考中..."];
-
- // 请求 chat/message 接口
- [self.aiVM requestChatMessageWithContent:finalText
- companionId:0
- completion:^(KBAiMessageResponse *_Nullable response,
- NSError *_Nullable error) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- if (!strongSelf) {
- return;
- }
-
- dispatch_async(dispatch_get_main_queue(), ^{
- [KBHUD dismiss];
-
- if (error) {
- [KBHUD showError:error.localizedDescription ?: @"请求失败"];
- return;
- }
-
- if (response.code == 50030) {
- NSString *message = response.message ?: @"";
- [strongSelf showChatLimitPopWithMessage:message];
- return;
- }
-
- if (!response || !response.data) {
- NSString *message = response.message ?: @"AI 回复为空";
- [KBHUD showError:message];
- return;
- }
-
- // 获取 AI 回复文本
- NSString *aiResponse = response.data.aiResponse ?: response.data.content ?: response.data.text ?: response.data.message ?: @"";
-
- if (aiResponse.length == 0) {
- [KBHUD showError:@"AI 回复为空"];
- return;
- }
-
- // 获取 audioId
- NSString *audioId = response.data.audioId;
-
- // 添加 AI 消息(带 audioId)
- [strongSelf.chatView addAssistantMessage:aiResponse
- audioId:audioId];
- });
- }];
-}
-
-- (void)deepgramStreamingManagerDidFail:(NSError *)error {
- self.recordButton.state = KBAiRecordButtonStateNormal;
- [self showError:error];
-}
-
-#pragma mark - Audio Playback
-
-- (void)playAiAudioData:(NSData *)audioData {
- if (audioData.length == 0) {
- return;
- }
-
- NSError *sessionError = nil;
- AudioSessionManager *audioSession = [AudioSessionManager sharedManager];
- if (![audioSession configureForPlayback:&sessionError]) {
- NSLog(@"[KBAiMainVC] Configure playback failed: %@",
- sessionError.localizedDescription ?: @"");
- }
- if (![audioSession activateSession:&sessionError]) {
- NSLog(@"[KBAiMainVC] Activate playback session failed: %@",
- sessionError.localizedDescription ?: @"");
- }
-
- NSError *error = nil;
- self.aiAudioPlayer = [[AVAudioPlayer alloc] initWithData:audioData
- error:&error];
- if (error || !self.aiAudioPlayer) {
- NSLog(@"[KBAiMainVC] Audio player init failed: %@",
- error.localizedDescription ?: @"");
- return;
- }
- self.aiAudioPlayer.delegate = self;
- [self.aiAudioPlayer prepareToPlay];
- [self.aiAudioPlayer play];
-}
-
-#pragma mark - AVAudioPlayerDelegate
-
-- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player
- successfully:(BOOL)flag {
- [[AudioSessionManager sharedManager] deactivateSession];
-}
-
-@end
diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m
index 003aaba..98aa6c7 100644
--- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m
+++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m
@@ -14,6 +14,7 @@
#import "KBChangeNicknamePopView.h"
#import "KBGenderPickerPopView.h"
#import "KBMyVM.h"
+#import "KBAlert.h"
@interface KBPersonInfoVC ()
// 列表
@@ -25,8 +26,10 @@
@property (nonatomic, strong) UIButton *editBadge; // 头像右下角的小铅笔
@property (nonatomic, strong) UILabel *modifyLabel; // “Modify” 文案
-// 底部退出按钮(固定在屏幕底部)
+// 底部退出登录按钮
@property (nonatomic, strong) UIButton *logoutBtn;
+// 底部注销账户按钮
+@property (nonatomic, strong) UIButton *cancelBtn;
// 数据
@property (nonatomic, copy) NSArray *items; // {title,value,arrow,copy}
@@ -64,9 +67,18 @@
// 表头
self.tableView.tableHeaderView = self.headerView;
- // 底部退出按钮固定在屏幕底部
+ // 底部退出登录按钮
[self.view addSubview:self.logoutBtn];
[self.logoutBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.left.equalTo(self.view).offset(16);
+ make.right.equalTo(self.view).offset(-16);
+ make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-(12 + 56 + 10));
+ make.height.mas_equalTo(56);
+ }];
+
+ // 底部注销账户按钮
+ [self.view addSubview:self.cancelBtn];
+ [self.cancelBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(16);
make.right.equalTo(self.view).offset(-16);
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12);
@@ -75,7 +87,7 @@
// 列表底部腾出空间,避免被按钮挡住
UIEdgeInsets inset = self.tableView.contentInset;
- inset.bottom = 56 + 24; // 按钮高度 + 额外间距
+ inset.bottom = 56 + 10 + 56 + 24; // 两个按钮高度 + 间距 + 额外间距
self.tableView.contentInset = inset;
self.viewModel = [[KBMyVM alloc] init];
__weak typeof(self) weakSelf = self;
@@ -275,6 +287,25 @@
[self.myVM logout];
}
+- (void)onTapCancelAccount {
+ [[KBMaiPointReporter sharedReporter] reportClickWithEventName:@"click_person_cancel_account_btn"
+ pageId:@"person_info"
+ elementId:@"cancel_account_btn"
+ extra:nil
+ completion:nil];
+ KBWeakSelf;
+ [KBAlert confirmTitle:KBLocalized(@"Cancel Account")
+ message:KBLocalized(@"After cancellation, your account will be deactivated and local login data will be cleared. Continue?")
+ ok:KBLocalized(@"Confirm")
+ cancel:KBLocalized(@"Cancel")
+ okColor:[UIColor colorWithHex:0xFF0000]
+ cancelColor:nil
+ completion:^(BOOL ok) {
+ if (!ok) { return; }
+ [weakSelf.myVM cancelAccountWithCompletion:nil];
+ }];
+}
+
#pragma mark - Lazy UI(懒加载)
- (UITableView *)tableView {
@@ -378,6 +409,19 @@
return _logoutBtn;
}
+- (UIButton *)cancelBtn {
+ if (!_cancelBtn) {
+ _cancelBtn = [UIButton buttonWithType:UIButtonTypeSystem];
+ [_cancelBtn setTitle:KBLocalized(@"Cancel Account") forState:UIControlStateNormal];
+ [_cancelBtn setTitleColor:[UIColor colorWithHex:0xFF0000] forState:UIControlStateNormal];
+ _cancelBtn.titleLabel.font = [KBFont medium:16];
+ _cancelBtn.backgroundColor = UIColor.whiteColor;
+ _cancelBtn.layer.cornerRadius = 12; _cancelBtn.layer.masksToBounds = YES;
+ [_cancelBtn addTarget:self action:@selector(onTapCancelAccount) forControlEvents:UIControlEventTouchUpInside];
+ }
+ return _cancelBtn;
+}
+
#pragma mark - Image Picker
- (void)presentImagePicker {
diff --git a/keyBoard/Class/Me/VM/KBMyVM.h b/keyBoard/Class/Me/VM/KBMyVM.h
index 4f06002..c4ee0b8 100644
--- a/keyBoard/Class/Me/VM/KBMyVM.h
+++ b/keyBoard/Class/Me/VM/KBMyVM.h
@@ -31,6 +31,7 @@ typedef void(^KBSubmitFeedbackCompletion)(BOOL success, NSError *_Nullable error
typedef void(^KBMyPurchaseRecordCompletion)(NSArray *_Nullable records, NSError *_Nullable error);
typedef void(^KBMyInviteCodeCompletion)(KBInviteCodeModel *_Nullable inviteCode, NSError *_Nullable error);
typedef void(^KBMyCustomerMailCompletion)(NSString *_Nullable customerMail, NSError *_Nullable error);
+typedef void(^KBCancelAccountCompletion)(BOOL success, NSError *_Nullable error);
@interface KBMyVM : NSObject
@@ -77,6 +78,9 @@ typedef void(^KBMyCustomerMailCompletion)(NSString *_Nullable customerMail, NSEr
/// 退出登录
- (void)logout;
+
+/// 注销账号(/user/cancelAccount)
+- (void)cancelAccountWithCompletion:(KBCancelAccountCompletion)completion;
@end
NS_ASSUME_NONNULL_END
diff --git a/keyBoard/Class/Me/VM/KBMyVM.m b/keyBoard/Class/Me/VM/KBMyVM.m
index 68d56a6..d2ba7a0 100644
--- a/keyBoard/Class/Me/VM/KBMyVM.m
+++ b/keyBoard/Class/Me/VM/KBMyVM.m
@@ -457,18 +457,67 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
NSString *message = jsonOrData[KBMessage] ?: KBLocalized(@"Success");
[KBHUD showSuccess:message];
-
- // 本地会话退出
- [[KBUserSessionManager shared] logout];
-
- // 回到登录 / 主界面
- dispatch_async(dispatch_get_main_queue(), ^{
- id appDelegate = UIApplication.sharedApplication.delegate;
- if ([appDelegate respondsToSelector:@selector(toMainTabbarVC)]) {
- AppDelegate *delegate = (AppDelegate *)appDelegate;
- [delegate toMainTabbarVC];
- }
- });
+ [self kb_clearLoginInfoAndRouteHome];
}];
}
+
+- (void)cancelAccountWithCompletion:(KBCancelAccountCompletion)completion {
+ [KBHUD show];
+ [[KBNetworkManager shared] POST:API_USER_CANCEL_ACCOUNT
+ jsonBody:nil
+ headers:nil
+ autoShowBusinessError:NO
+ completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+ [KBHUD dismiss];
+
+ if (error) {
+ NSString *msg = KBBizMessageFromJSONObject(jsonOrData) ?: error.localizedDescription ?: KBLocalized(@"Network error");
+ [KBHUD showInfo:msg];
+ if (completion) {
+ completion(NO, error);
+ }
+ return;
+ }
+
+ NSString *message = jsonOrData[KBMessage] ?: KBLocalized(@"Success");
+ [KBHUD showSuccess:message];
+
+ [self kb_clearLoginInfoAndRouteHome];
+ if (completion) {
+ completion(YES, nil);
+ }
+ }];
+}
+
+#pragma mark - Private
+
+/// 清理本地登录态及关联缓存,并回到首页。
+- (void)kb_clearLoginInfoAndRouteHome {
+ [[KBUserSessionManager shared] logout];
+
+ NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
+ NSArray *sharedKeys = @[
+ AppGroup_MyKbJson,
+ AppGroup_UserAvatarURL,
+ AppGroup_SubscriptionPrefillPayload,
+ AppGroup_ChatUpdatedCompanionId,
+ @"AppGroup_SelectedPersona"
+ ];
+ for (NSString *key in sharedKeys) {
+ [sharedDefaults removeObjectForKey:key];
+ }
+ [sharedDefaults synchronize];
+
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults removeObjectForKey:@"KBAISelectedPersonaId"];
+ [defaults synchronize];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ id appDelegate = UIApplication.sharedApplication.delegate;
+ if ([appDelegate respondsToSelector:@selector(toMainTabbarVC)]) {
+ AppDelegate *delegate = (AppDelegate *)appDelegate;
+ [delegate toMainTabbarVC];
+ }
+ });
+}
@end
diff --git a/keyBoard/Info.plist b/keyBoard/Info.plist
index ab9455d..2aeed88 100644
--- a/keyBoard/Info.plist
+++ b/keyBoard/Info.plist
@@ -24,10 +24,6 @@
NSAllowsArbitraryLoads
- UIBackgroundModes
-
- audio
-
UIDesignRequiresCompatibility
diff --git a/keyBoard/PrivacyInfo.xcprivacy b/keyBoard/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000..da12d3c
--- /dev/null
+++ b/keyBoard/PrivacyInfo.xcprivacy
@@ -0,0 +1,76 @@
+
+
+
+
+ NSPrivacyTracking
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyCollectedDataTypes
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeEmailAddress
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeUserID
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyCollectedDataType
+ NSPrivacyCollectedDataTypeOtherUserContent
+ NSPrivacyCollectedDataTypeLinked
+
+ NSPrivacyCollectedDataTypeTracking
+
+ NSPrivacyCollectedDataTypePurposes
+
+ NSPrivacyCollectedDataTypePurposeAppFunctionality
+
+
+
+ NSPrivacyAccessedAPITypes
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryUserDefaults
+ NSPrivacyAccessedAPITypeReasons
+
+ CA92.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryActiveKeyboards
+ NSPrivacyAccessedAPITypeReasons
+
+ 3EC4.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPITypeReasons
+
+ C617.1
+
+
+
+
+