feat: AI 意图路由/草稿存储 + Prompts 模块化 + UI 视觉统一 + AI 同意门 + 资源更新
后端: - 新增 ai_intent_router 意图路由 - 新增 AiEntryDraft 草稿存储 + EF 迁移 - 新增 Prompts 模块化(global/modules/rag/router) - AI Agent handlers 扩展 前端: - 新增 AI 同意门 (ai_consent_gate/provider/details_page) - HealthMetricVisuals 视觉统一 - 设备扫描/管理页面优化 - agent 插图替换 + 趋势指标图标 配置: - DeepSeek 模型改 deepseek-v4-flash - .gitignore 加 artifacts/audit - build.gradle.kts 签名配置调整
This commit is contained in:
154
health_app/lib/pages/settings/ai_consent_details_page.dart
Normal file
154
health_app/lib/pages/settings/ai_consent_details_page.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_design_tokens.dart';
|
||||
import '../../providers/ai_consent_provider.dart';
|
||||
import '../../providers/auth_provider.dart';
|
||||
|
||||
class AiConsentDetailsPage extends ConsumerWidget {
|
||||
const AiConsentDetailsPage({super.key});
|
||||
|
||||
Future<void> _revoke(BuildContext context, WidgetRef ref) async {
|
||||
final userId = ref.read(authProvider).user?.id;
|
||||
if (userId == null || userId.isEmpty) return;
|
||||
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('撤回 AI 授权?'),
|
||||
content: const Text('撤回后将退出当前账号,重新使用 App 前需要再次完成 AI 数据授权。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('撤回并退出'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed != true || !context.mounted) return;
|
||||
|
||||
final revoked = await ref.read(aiConsentProvider.notifier).revoke(userId);
|
||||
if (!revoked) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('撤回授权失败,请稍后重试')));
|
||||
}
|
||||
return;
|
||||
}
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final consent = ref.watch(aiConsentProvider);
|
||||
final canRevoke = consent.granted && ref.watch(authProvider).user != null;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: AppColors.textPrimary),
|
||||
onPressed: () => Navigator.of(context).maybePop(),
|
||||
),
|
||||
title: const Text(
|
||||
'AI 数据授权说明',
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_section(
|
||||
'我们会处理什么数据',
|
||||
'当你使用 AI 对话、饮食识别或报告预整理功能时,可能会处理你主动输入的文字、健康数据、健康档案摘要、图片和 PDF 报告。',
|
||||
),
|
||||
_section(
|
||||
'发送给哪些服务商',
|
||||
'DeepSeek:用于生成 AI 对话和健康信息整理。\n\n通义千问视觉服务:用于识别你主动上传的饮食图片、报告图片或 PDF 内容。\n\nFastGPT:用于检索与问题相关的医学参考资料。',
|
||||
),
|
||||
_section(
|
||||
'使用目的',
|
||||
'上述数据仅用于提供 AI 对话、健康记录辅助、饮食图片识别、医学报告预整理和医学资料检索功能。AI 内容仅供健康管理参考,不能替代医生诊断或治疗。',
|
||||
),
|
||||
_section(
|
||||
'你的选择',
|
||||
'你可以在首次进入首页时选择是否授权。未授权时无法进入本 App 的主要服务。你也可以在设置中撤回授权;撤回后将退出当前账号,再次使用前需要重新授权。',
|
||||
),
|
||||
_section(
|
||||
'更多信息',
|
||||
'数据保存、删除、第三方服务保护措施和联系方式,请同时查看 App 内《隐私政策》和《第三方 SDK 共享清单》。',
|
||||
),
|
||||
if (canRevoke) ...[
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: consent.isSaving
|
||||
? null
|
||||
: () => _revoke(context, ref),
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: AppColors.error,
|
||||
side: BorderSide(
|
||||
color: AppColors.error.withValues(alpha: 0.4),
|
||||
),
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
),
|
||||
child: consent.isSaving
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.2,
|
||||
color: AppColors.error,
|
||||
),
|
||||
)
|
||||
: const Text('撤回 AI 数据授权'),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _section(String title, String body) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 22),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: AppTextStyles.sectionTitle),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
body,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
height: 1.65,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user