New chat UI#207
Conversation
- Add LocaleInterceptor that attaches the active Accept-Language header to outgoing requests - Wire the interceptor into the Dio client in http_module
… and actions. - New chat UI: launch landing (greeting, stats, continue/new), full-width assistant responses, chat history, and background polling for in-flight turns - Typed agent blocks (markdown, table, kpi, chart, comparison, list, callout, timeline, progress, question, quick_actions, import_review, canvas) parsed in a sealed model, with a legacy SmartQL fallback - Proposed actions: confirm, edit, or dismiss via a sealed, typed ActionField form (currency, wallet, party, enum, number, datetime, categories) - Attachments: file picking with square image and document preview tiles, upload with deferred-turn release, inline retry, and file-too-large handling - Add AI use cases (confirm action, reject action, upload files) with matching repository and datasource methods - Add a file-too-large failure and surface it through the error handlers - Localize the AI chat strings across all six languages - Supporting updates: import document scan and file picker, AI chat navigation entry
- Add bloc lint and Patrol e2e testing plans - Add sample AI query examples
Code Review SummaryThis PR implements a significant upgrade to the AI chat experience. It adds support for multi-modal interactions (files), rich data visualization (charts, tables, KPIs), and interactive 'Proposed Actions' which allow the AI to draft transactions for user confirmation. 🚀 Key Improvements
💡 Minor Suggestions
|
| void initState() { | ||
| super.initState(); | ||
| _status = ImportSessionStatus.parse(widget.block.status); | ||
| if (widget.block.importSessionId != null && _status.isInFlight) { |
There was a problem hiding this comment.
Polling logic is implemented directly within the Widget's initState. This makes the logic difficult to test and susceptible to memory leaks if not handled perfectly. This logic belongs in the AiChatCubit or a specialized Bloc to maintain a clean separation between UI and business logic.
| if (widget.block.importSessionId != null && _status.isInFlight) { | |
| // Logic should be handled in Cubit via a trigger | |
| // context.read<AiChatCubit>().monitorImport(id); |
| String _fileName(File f) { | ||
| final last = | ||
| f.uri.pathSegments.isNotEmpty ? f.uri.pathSegments.last : 'file'; | ||
| return last.replaceFirst(RegExp(r'^\d+_'), ''); |
There was a problem hiding this comment.
The regex used to clean filenames from temporary paths assumes a specific prefix pattern that might change across OS versions or different image picker implementations. It's safer to use path.basename from the path package.
| return last.replaceFirst(RegExp(r'^\d+_'), ''); | |
| import 'package:path/path.dart' as p; | |
| return p.basename(f.path); |
| if (v == null) return '-'; | ||
| if (v is num) { |
There was a problem hiding this comment.
The fmtNum helper uses simple equality for round number detection. This can fail with precision issues common in floating point math. Consider using a threshold or num.toInt() check more defensively.
| if (v == null) return '-'; | |
| if (v is num) { | |
| if (v is num) { | |
| return v % 1 == 0 ? v.toInt().toString() : v.toStringAsFixed(2); | |
| } |
Description
Redesign AI chat with typed blocks, attachments & actions
Added Blocks for rendering AI content from the server.
Type of Change