Browse Source
默认昵称不再使用会被词库拦下的 User_ 前缀,资料保存时名字未改不再整单拒绝。 Co-authored-by: Cursor <cursoragent@cursor.com>along
26 changed files with 403 additions and 45 deletions
@ -0,0 +1,97 @@ |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:get_storage/get_storage.dart'; |
|||
|
|||
/// 用户拒绝把数据发给这家服务商。调用方据此停掉本次请求,不要再弹错误提示。 |
|||
class AiDataConsentDenied implements Exception { |
|||
const AiDataConsentDenied(); |
|||
@override |
|||
String toString() => 'AiDataConsentDenied'; |
|||
} |
|||
|
|||
/// 第一次把个人数据发给某一家第三方 AI 之前,先说明发什么、发给谁,并取得同意。 |
|||
/// |
|||
/// 同意记在本机,之后同一家不再问。不同意不落盘,下次再使用还会问; |
|||
/// 短时间内的自动重试不会把确认框连弹两次。 |
|||
enum AiDataVendor { alibaba, microsoft, iflytek, meeting } |
|||
|
|||
class AiDataConsent { |
|||
static final Map<AiDataVendor, Future<bool>> _inflight = {}; |
|||
static final Map<AiDataVendor, DateTime> _declinedAt = {}; |
|||
|
|||
static String _key(AiDataVendor vendor) => 'ai_data_consent_${vendor.name}'; |
|||
|
|||
static Future<bool> allow(AiDataVendor vendor) async { |
|||
if (GetStorage().read(_key(vendor)) == true) return true; |
|||
final declined = _declinedAt[vendor]; |
|||
if (declined != null && |
|||
DateTime.now().difference(declined) < const Duration(seconds: 2)) { |
|||
return false; |
|||
} |
|||
final pending = _inflight[vendor]; |
|||
if (pending != null) return pending; |
|||
final fut = _ask(vendor); |
|||
_inflight[vendor] = fut; |
|||
try { |
|||
final ok = await fut; |
|||
if (ok) { |
|||
await GetStorage().write(_key(vendor), true); |
|||
} else { |
|||
_declinedAt[vendor] = DateTime.now(); |
|||
} |
|||
return ok; |
|||
} finally { |
|||
_inflight.remove(vendor); |
|||
} |
|||
} |
|||
|
|||
static Future<bool> _ask(AiDataVendor vendor) async { |
|||
final zh = (Get.locale ?? Get.deviceLocale) |
|||
?.languageCode |
|||
.toLowerCase() |
|||
.startsWith('zh') ?? |
|||
false; |
|||
final result = await Get.dialog<bool>( |
|||
PopScope( |
|||
canPop: false, |
|||
child: AlertDialog( |
|||
title: Text(zh ? '发送数据确认' : 'Confirm data sharing'), |
|||
content: Text(_body(vendor, zh)), |
|||
actions: [ |
|||
TextButton( |
|||
onPressed: () => Get.back(result: false), |
|||
child: Text(zh ? '不同意' : "Don't Allow"), |
|||
), |
|||
TextButton( |
|||
onPressed: () => Get.back(result: true), |
|||
child: Text(zh ? '同意' : 'Allow'), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
barrierDismissible: false, |
|||
); |
|||
return result == true; |
|||
} |
|||
|
|||
static String _body(AiDataVendor vendor, bool zh) { |
|||
switch (vendor) { |
|||
case AiDataVendor.alibaba: |
|||
return zh |
|||
? '继续将把你的语音、对话内容、待翻译文字或照片发送给阿里云(通义千问、百炼),用于翻译、对话和图片识别。不同意则不会发送。' |
|||
: 'To continue, your voice, conversation, text, or photos will be sent to Alibaba Cloud (Tongyi Qianwen / Bailian) for translation, chat, and image recognition. If you do not agree, nothing will be sent.'; |
|||
case AiDataVendor.microsoft: |
|||
return zh |
|||
? '继续将把你的语音发送给微软(Azure 语音服务),用于语音识别和翻译。不同意则不会发送。' |
|||
: 'To continue, your voice will be sent to Microsoft (Azure Speech) for speech recognition and translation. If you do not agree, nothing will be sent.'; |
|||
case AiDataVendor.iflytek: |
|||
return zh |
|||
? '继续将把你的语音发送给科大讯飞,用于语音识别和翻译。不同意则不会发送。' |
|||
: 'To continue, your voice will be sent to iFLYTEK for speech recognition and translation. If you do not agree, nothing will be sent.'; |
|||
case AiDataVendor.meeting: |
|||
return zh |
|||
? '继续将把这段录音上传到 EAIMAR 服务器,并交给第三方做转写和总结。接收方由当前服务配置决定,可能是阿里云(通义)、微软(Azure 语音)或字节跳动(火山引擎)。不同意则不会提交。' |
|||
: 'To continue, this recording will be uploaded to EAIMAR servers and sent to a third party for transcription and summary. Depending on the current service configuration, the recipient may be Alibaba Cloud (Tongyi), Microsoft (Azure Speech), or ByteDance (Volcano Engine). If you do not agree, it will not be submitted.'; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue