import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import '../widgets/layout/app_layout.dart'; import '../widgets/common/app_button.dart'; import '../widgets/common/app_card.dart'; import '../widgets/common/app_tag.dart'; import '../providers/document_provider.dart'; import '../providers/element_provider.dart'; import '../models/element.dart'; import '../utils/constants.dart'; import '../theme/app_colors.dart'; /// AI处理页 class AIProcessPage extends StatefulWidget { final String documentId; const AIProcessPage({Key? key, required this.documentId}) : super(key: key); @override State createState() => _AIProcessPageState(); } class _AIProcessPageState extends State { String _selectedMode = 'highlight'; // highlight, polish, spellcheck @override Widget build(BuildContext context) { return Consumer2( builder: (context, docProvider, elementProvider, child) { final document = docProvider.getDocumentById(widget.documentId); final parsedText = document?.parsedText ?? _getMockText(); final elements = elementProvider.elements; return AppLayout( maxContentWidth: 1600, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 24), // 标题栏 _buildHeader(context, document), const SizedBox(height: 32), // 工具栏 _buildToolbar(context), const SizedBox(height: 24), // 主内容区 LayoutBuilder( builder: (context, constraints) { final isWide = constraints.maxWidth > 900; return isWide ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: _buildTextEditor( context, parsedText, elements, ), ), const SizedBox(width: 20), SizedBox( width: 320, child: _buildAnnotationPanel( context, elements, ), ), ], ) : Column( children: [ _buildTextEditor( context, parsedText, elements, ), const SizedBox(height: 20), _buildAnnotationPanel( context, elements, ), ], ); }, ), ], ), ), ); }, ); } Widget _buildHeader(BuildContext context, document) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.pop(), style: IconButton.styleFrom( backgroundColor: AppColors.backgroundLight, padding: const EdgeInsets.all(12), ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( gradient: LinearGradient( colors: [Colors.purple, Colors.purple.shade300], ), borderRadius: BorderRadius.circular(8), ), child: const Icon( LucideIcons.sparkles, color: Colors.white, size: 18, ), ), const SizedBox(width: 12), Text( document?.name ?? 'AI智能处理', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), const SizedBox(height: 8), Text( 'AI润色、错别字修正、要素高亮', style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), ], ), ), ], ), ); } Widget _buildToolbar(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( children: [ _buildToolbarButton( icon: LucideIcons.highlighter, label: '要素高亮', isActive: _selectedMode == 'highlight', onTap: () => setState(() => _selectedMode = 'highlight'), ), const SizedBox(width: 8), _buildToolbarButton( icon: LucideIcons.sparkles, label: 'AI润色', isActive: _selectedMode == 'polish', onTap: () => setState(() => _selectedMode = 'polish'), ), const SizedBox(width: 8), _buildToolbarButton( icon: LucideIcons.circle_check, label: '错别字修正', isActive: _selectedMode == 'spellcheck', onTap: () => setState(() => _selectedMode = 'spellcheck'), ), const Spacer(), AppButton( text: '保存', type: ButtonType.primary, size: ButtonSize.small, icon: LucideIcons.save, onPressed: () {}, ), ], ), ); } Widget _buildToolbarButton({ required IconData icon, required String label, required bool isActive, required VoidCallback onTap, }) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isActive ? AppColors.primary.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(8), border: isActive ? Border.all(color: AppColors.primary.withOpacity(0.3)) : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, size: 18, color: isActive ? AppColors.primary : AppColors.textSecondary, ), const SizedBox(width: 6), Text( label, style: TextStyle( fontSize: 13, fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, color: isActive ? AppColors.primary : AppColors.textSecondary, ), ), ], ), ), ), ); } Widget _buildTextEditor( BuildContext context, String text, List elements, ) { return AppCard( title: Row( children: [ const Icon(LucideIcons.file_text, size: 18), const SizedBox(width: 8), const Text('文档内容'), const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppColors.success.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( LucideIcons.circle_check, size: 12, color: AppColors.success, ), const SizedBox(width: 4), Text( '${elements.length} 个要素已提取', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.success, ), ), ], ), ), ], ), child: Container( constraints: const BoxConstraints(minHeight: 600), padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), ), child: SelectableText.rich( TextSpan( children: _buildTextSpans(text, elements), style: const TextStyle( fontSize: 15, height: 1.8, color: AppColors.textPrimary, ), ), ), ), ); } List _buildTextSpans( String text, List elements, ) { final spans = []; int lastIndex = 0; // 为每个要素创建高亮 for (final element in elements) { final index = text.indexOf(element.value, lastIndex); if (index != -1) { // 添加高亮前的文本 if (index > lastIndex) { spans.add(TextSpan(text: text.substring(lastIndex, index))); } // 添加高亮的要素文本 spans.add( TextSpan( text: element.value, style: TextStyle( backgroundColor: _getElementColor(element.type).withOpacity(0.2), color: _getElementColor(element.type), fontWeight: FontWeight.w600, decoration: TextDecoration.underline, decorationColor: _getElementColor(element.type), ), mouseCursor: SystemMouseCursors.click, ), ); lastIndex = index + element.value.length; } } // 添加剩余文本 if (lastIndex < text.length) { spans.add(TextSpan(text: text.substring(lastIndex))); } return spans.isEmpty ? [TextSpan(text: text)] : spans; } Color _getElementColor(ElementType type) { switch (type) { case ElementType.amount: return AppColors.amount; case ElementType.company: return AppColors.company; case ElementType.person: return AppColors.person; case ElementType.location: return AppColors.location; default: return AppColors.textSecondary; } } Widget _buildAnnotationPanel( BuildContext context, List elements, ) { return Column( children: [ AppCard( title: Row( children: [ const Icon(LucideIcons.tags, size: 18), const SizedBox(width: 8), const Text('要素标签'), const Spacer(), Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Text( '${elements.length}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ), ], ), child: elements.isEmpty ? Padding( padding: const EdgeInsets.all(32), child: Column( children: [ Icon( LucideIcons.tag, size: 48, color: AppColors.textSecondary.withOpacity(0.5), ), const SizedBox(height: 16), Text( '暂无要素', style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), const SizedBox(height: 8), Text( '点击"要素提取"按钮开始', style: TextStyle( fontSize: 12, color: AppColors.textSecondary.withOpacity(0.7), ), ), ], ), ) : Container( constraints: const BoxConstraints(maxHeight: 400), child: SingleChildScrollView( child: Wrap( spacing: 8, runSpacing: 8, children: elements.map((element) { return AppTag( label: element.displayText, type: element.type, closable: true, onDeleted: () {}, ); }).toList(), ), ), ), ), const SizedBox(height: 16), AppCard( child: Column( children: [ AppButton( text: '继续人工牵引', type: ButtonType.primary, icon: LucideIcons.git_branch, fullWidth: true, onPressed: () { context.push('${AppRoutes.traction}/${widget.documentId}'); }, ), const SizedBox(height: 12), AppButton( text: '重新提取要素', type: ButtonType.secondary, icon: LucideIcons.refresh_cw, fullWidth: true, onPressed: () {}, ), ], ), ), ], ); } String _getMockText() { return '''一、项目概述 本项目由腾讯科技有限公司(以下简称"腾讯")投资建设,旨在打造一套面向大型企业财务共享中心的"智能票据处理系统"。系统通过OCR识别、版面理解、要素抽取与人机协同核验,提升发票、合同、报销单据等票据类文档的处理效率与准确率。 二、投资与资金安排 项目总投资金额为人民币3,700万元,其中已支付1,000万元用于样机研制、数据治理与前期试点。剩余资金将依据阶段性里程碑进行分批拨付,涵盖核心算法优化、系统平台化改造、以及与客户生态的对接集成。 三、技术与产品路线 系统采用多模态文档理解技术,将版面结构、语义上下文与业务词典结合,实现对复杂票据的鲁棒解析。产品形态包含:上传解析端、人工牵引标注端、规则与关系构建端,以及结果交付端。 四、收益预期 预计上线后,票据处理效率提升60%以上,人工核验工作量下降40%,并显著降低AI"幻觉"导致的错判风险,从而提升财务数据的可靠性与可审计性。'''; } }