import 'package:flutter/material.dart'; import 'package:flutter/services.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 '../providers/document_provider.dart'; import '../providers/element_provider.dart'; import '../models/element.dart'; import '../services/mock_data_service.dart'; import '../theme/app_colors.dart'; /// AI结果页 class ResultPage extends StatefulWidget { final String documentId; const ResultPage({Key? key, required this.documentId}) : super(key: key); @override State createState() => _ResultPageState(); } class _ResultPageState extends State { final prompt = MockDataService.getMockPrompt(); final response = MockDataService.getMockAIResponse(); bool _isRegenerating = false; bool _isSending = false; late final TextEditingController _inputController; @override void initState() { super.initState(); _inputController = TextEditingController(); } @override void dispose() { _inputController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer2( builder: (context, docProvider, elementProvider, child) { final document = docProvider.getDocumentById(widget.documentId); var documentElements = elementProvider.elements .where( (element) => element.documentId == widget.documentId || element.documentId == null, ) .toList(); if (documentElements.isEmpty) { documentElements = elementProvider.elements.take(10).toList(); } return AppLayout( maxContentWidth: 1200, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context, document), const SizedBox(height: 24), _buildChatInterface(context), const SizedBox(height: 32), _buildActionComposer(context, documentElements), ], ), ); }, ); } 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: [AppColors.success, Colors.green.shade300], ), borderRadius: BorderRadius.circular(8), ), child: const Icon( LucideIcons.circle_check, color: Colors.white, size: 18, ), ), const SizedBox(width: 12), const Text( 'AI处理结果', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ], ), const SizedBox(height: 8), Text( '基于人工牵引生成的精准Prompt与AI回复', style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), ], ), ), AppButton( text: '导出', type: ButtonType.secondary, size: ButtonSize.small, icon: LucideIcons.download, onPressed: () {}, ), ], ), ); } Widget _buildChatInterface(BuildContext context) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28), decoration: BoxDecoration( color: const Color(0xFFF7F8FA), borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.borderLight), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.02), blurRadius: 18, offset: const Offset(0, 8), ), ], ), child: SingleChildScrollView( // 改为可滚动 child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _buildChatMessage( context, isAI: false, title: '系统生成的精准 Prompt', content: prompt, icon: LucideIcons.sparkles, ), const SizedBox(height: 28), _buildChatMessage( context, isAI: true, title: 'AI 回复', content: response, icon: LucideIcons.bot, ), // 可以继续添加更多消息 ], ), ), ), ); } Widget _buildChatMessage( BuildContext context, { required bool isAI, required String title, required String content, required IconData icon, }) { final theme = Theme.of(context); final bubbleColor = isAI ? Colors.white : AppColors.primary.withOpacity(0.08); final borderColor = isAI ? AppColors.borderLight : AppColors.primary.withOpacity(0.25); final bubble = Container( constraints: const BoxConstraints(maxWidth: 760), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: bubbleColor, borderRadius: BorderRadius.only( topLeft: const Radius.circular(20), topRight: const Radius.circular(20), bottomLeft: Radius.circular(isAI ? 20 : 6), bottomRight: Radius.circular(isAI ? 6 : 20), ), border: Border.all(color: borderColor), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 12, offset: const Offset(0, 6), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( icon, size: 18, color: isAI ? AppColors.success : AppColors.primary, ), const SizedBox(width: 8), Text( title, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (!isAI) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.12), borderRadius: BorderRadius.circular(20), ), child: Text( '系统生成', style: theme.textTheme.bodySmall?.copyWith( fontSize: 11, color: AppColors.primary, fontWeight: FontWeight.w600, ), ), ), ], ], ), const SizedBox(height: 12), SelectableText.rich( TextSpan( children: _buildFormattedContent(content), style: const TextStyle( fontSize: 15, height: 1.8, color: AppColors.textPrimary, ), ), ), ], ), ); final avatar = CircleAvatar( radius: 20, backgroundColor: isAI ? AppColors.success.withOpacity(0.15) : AppColors.primary.withOpacity(0.15), child: Icon( icon, color: isAI ? AppColors.success : AppColors.primary, size: 20, ), ); return Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: isAI ? MainAxisAlignment.start : MainAxisAlignment.end, children: [ if (isAI) ...[ avatar, const SizedBox(width: 12), ], Flexible(child: bubble), if (!isAI) ...[ const SizedBox(width: 12), avatar, ], ], ); } List _buildFormattedContent(String content) { final lines = content.split('\n'); final spans = []; for (int i = 0; i < lines.length; i++) { final line = lines[i]; if (line.trim().isEmpty) { spans.add(const TextSpan(text: '\n')); continue; } // 检测公式或计算结果 if (line.contains('=') || line.contains('计算')) { spans.add( TextSpan( text: line, style: TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, backgroundColor: AppColors.primary.withOpacity(0.1), ), ), ); } else if (line.startsWith('-') || line.startsWith('•')) { // 列表项 spans.add( TextSpan( text: line, style: const TextStyle( fontWeight: FontWeight.w500, ), ), ); } else { spans.add(TextSpan(text: line)); } if (i < lines.length - 1) { spans.add(const TextSpan(text: '\n')); } } return spans; } Widget _buildActionComposer( BuildContext context, List elements, ) { final theme = Theme.of(context); final hasElements = elements.isNotEmpty; return Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), border: Border.all(color: AppColors.borderLight), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 18, offset: const Offset(0, 10), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.12), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ Icon( LucideIcons.sparkles, size: 16, color: AppColors.primary, ), const SizedBox(width: 6), Text( '生成新的 AI 回复', style: theme.textTheme.bodySmall?.copyWith( color: AppColors.primary, fontWeight: FontWeight.w600, ), ), ], ), ), const SizedBox(width: 12), Text( '可结合要素补充或修改 Prompt 内容', style: theme.textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ], ), if (hasElements) ...[ const SizedBox(height: 16), SizedBox( height: 44, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ for (int i = 0; i < elements.length; i++) ...[ if (i > 0) const SizedBox(width: 8), _buildElementChip(elements[i]), ], ], ), ), ), ], const SizedBox(height: 16), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextField( controller: _inputController, minLines: 4, maxLines: 10, onChanged: (_) => setState(() {}), decoration: InputDecoration( hintText: '描述需要 AI 调整或补充的内容,可点击要素快速引用…', filled: true, fillColor: AppColors.backgroundLight, border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: AppColors.border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: AppColors.border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide( color: AppColors.primary, width: 2, ), ), contentPadding: const EdgeInsets.symmetric( horizontal: 18, vertical: 16, ), suffixIcon: _inputController.text.isEmpty ? null : IconButton( icon: const Icon(LucideIcons.x), onPressed: () { _inputController.clear(); setState(() {}); }, ), ), ), ), const SizedBox(width: 16), SizedBox( width: 152, child: Column( children: [ AppButton( text: '发送', type: ButtonType.primary, icon: LucideIcons.send, loading: _isSending, onPressed: _isSending || _inputController.text.trim().isEmpty ? null : _handleSend, fullWidth: true, ), const SizedBox(height: 10), AppButton( text: '重新生成', type: ButtonType.secondary, icon: LucideIcons.refresh_cw, loading: _isRegenerating, onPressed: _isRegenerating ? null : _handleRegenerate, fullWidth: true, ), ], ), ), ], ), const SizedBox(height: 16), Row( children: [ AppButton( text: '导出结果', type: ButtonType.secondary, icon: LucideIcons.download, onPressed: () {}, ), const SizedBox(width: 12), AppButton( text: '复制回复', type: ButtonType.text, icon: LucideIcons.copy, onPressed: () { Clipboard.setData(ClipboardData(text: response)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('AI 回复已复制')), ); }, ), ], ), ], ), ); } Widget _buildElementChip(DocumentElement element) { final color = _getElementColor(element.type); return ActionChip( label: Text( '${element.type.label}|${element.value}', style: TextStyle( fontSize: 12, color: color, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), avatar: Icon( LucideIcons.tag, size: 14, color: color, ), backgroundColor: color.withOpacity(0.12), shape: StadiumBorder( side: BorderSide(color: color.withOpacity(0.3)), ), onPressed: () => _insertElementIntoInput(element), ); } void _insertElementIntoInput(DocumentElement element) { final insertion = '[${element.label}: ${element.value}]'; final text = _inputController.text; TextSelection selection = _inputController.selection; if (!selection.isValid) { selection = TextSelection.collapsed(offset: text.length); } final newText = text.replaceRange( selection.start, selection.end, insertion, ); final cursorPosition = selection.start + insertion.length; _inputController.value = TextEditingValue( text: newText, selection: TextSelection.collapsed(offset: cursorPosition), ); setState(() {}); } Future _handleSend() async { final content = _inputController.text.trim(); if (content.isEmpty) return; setState(() => _isSending = true); await Future.delayed(const Duration(seconds: 2)); if (!mounted) return; _inputController.clear(); setState(() => _isSending = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('已发送请求,等待 AI 生成新回复')), ); } Future _handleRegenerate() async { setState(() => _isRegenerating = true); await Future.delayed(const Duration(seconds: 2)); if (!mounted) return; setState(() => _isRegenerating = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('已重新生成 AI 回复')), ); } 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; case ElementType.date: return AppColors.date; case ElementType.other: return AppColors.other; } } }