Add AI batch reconciliation for accessibility bills

This commit is contained in:
2026-07-25 18:41:09 +08:00
parent eb8909a192
commit 7cca34b331
25 changed files with 2771 additions and 230 deletions
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miaoji_zhang/shared/services/screenshot_channel.dart';
import 'package:miaoji_zhang/shared/services/session_store.dart';
@@ -147,6 +148,9 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
!await _confirmLocalOcrConsent()) {
return;
}
if (enabled && key == 'ai_screenshot' && !await _confirmAiBatchConsent()) {
return;
}
if (!enabled) {
_pendingAuthorizationKey = null;
final changed = await ScreenshotChannel.setRecognitionToggle(key, false);
@@ -275,6 +279,71 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
return false;
}
Future<bool> _confirmAiBatchConsent() async {
final preferences = await SharedPreferences.getInstance();
if (preferences.getBool('ai_batch_consent_v2') == true) return true;
if (!mounted) return false;
final accepted = await showModalBottomSheet<bool>(
context: context,
useSafeArea: true,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (sheetContext) => Container(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
decoration: BoxDecoration(
color: sheetContext.jz.card,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const JzSheetHeader(
title: '启用 AI 批次对账',
subtitle: '请确认支付页截图的批量处理方式',
),
const SizedBox(height: 12),
const _ConsentPoint(
icon: Icons.schedule_rounded,
text: '支付结果会按 30 秒空闲窗口归为一批,最长等待 2 分钟或累计 10 条。',
),
const _ConsentPoint(
icon: Icons.auto_fix_high_rounded,
text: '本批截图和本地候选会发送给 AI,仅允许在当前批次内保留、修正、补全或剔除。',
),
const _ConsentPoint(
icon: Icons.enhanced_encryption_outlined,
text: '待提交图片仅在本机临时加密保存,批次完成或回退后立即删除,不进入最近对账记录。',
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: JzActionButton(
label: '暂不开启',
secondary: true,
onPressed: () => Navigator.pop(sheetContext, false),
),
),
const SizedBox(width: 10),
Expanded(
child: JzActionButton(
label: '同意并开启',
onPressed: () => Navigator.pop(sheetContext, true),
),
),
],
),
],
),
),
);
if (accepted == true) {
await preferences.setBool('ai_batch_consent_v2', true);
return true;
}
return false;
}
void _showMessage(String message) {
ScaffoldMessenger.of(
context,
@@ -413,24 +482,46 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
_RecognitionCard(
icon: Icons.document_scanner_outlined,
title: 'AI 截图补全',
description: '本地 OCR 已确认支付成功但字段仍不足时才在线分析。截图完成、失败或超时后立即释放',
description: '支付结果按 30 秒空闲窗口批量提交,AI 只在本批次内纠错、补全或剔除。处理结束立即删除图片',
authorized: aiAvailable,
connected: aiAvailable && status.accessibilityConnected,
statusLabel: !aiAvailable ? 'AI 不可用' : null,
onOpenSettings: status.accessibilityAuthorized
? null
: ScreenshotChannel.openAccessibilitySettings,
child: JzSwitchTile(
value: status.aiScreenshot,
title: '补全开关',
subtitle: !aiAvailable
? '需要登录且账号具备 AI 权限'
: !status.accessibilityAuthorized
? '需要先授权无障碍截屏能力'
: '默认关闭,仅在支付应用前台运行',
onChanged: !aiAvailable
? null
: (value) => _toggle('ai_screenshot', value),
child: Column(
children: [
JzSwitchTile(
value: status.aiScreenshot,
title: '批次对账开关',
subtitle: !aiAvailable
? '需要登录且账号具备 AI 权限'
: !status.accessibilityAuthorized
? '需要先授权无障碍截屏能力'
: '默认关闭,仅在支付应用前台运行',
onChanged: !aiAvailable
? null
: (value) => _toggle('ai_screenshot', value),
),
Divider(height: 1, color: context.jz.line),
InkWell(
onTap: () => context.push('/recognition-batches'),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 4,
vertical: 13,
),
child: Row(
children: [
Icon(Icons.fact_check_outlined, size: 20),
SizedBox(width: 10),
Expanded(child: Text('最近 AI 对账')),
Icon(Icons.chevron_right_rounded),
],
),
),
),
],
),
),
if (status.latestDiagnostic != null) ...[
@@ -487,7 +578,7 @@ class _ScreenshotSettingsPageState extends State<ScreenshotSettingsPage>
const SizedBox(width: 9),
Expanded(
child: Text(
'银行密码页等安全窗口由系统禁止截屏,记之不会绕过限制。本地 OCR 图片仅在内存中处理;只有你开启 AI 截图补全时才会上传当前支付页',
'银行密码页等安全窗口由系统禁止截屏,记之不会绕过限制。本地 OCR 图片仅在内存中处理;开启 AI 批次对账后,支付结果页会临时加密并按批上传,处理结束立即删除',
style: TextStyle(
color: palette.text2,
fontSize: 11.5,