feat: AI 对话附件上下文解析 + 历史会话归档 + 多页面 UI 重构
- 后端: 新增 AttachmentContextBuilder 解析图片/PDF 摘要并拼入 LLM 上下文; ai_chat_endpoints 扩展附件接口; 新增 ReportAnalysisService - 前端: 新增历史会话页与 conversation_history_provider; chat 链路支持附件展示与回放 - UI: 重构 medication_checkin / notification_center / profile / health_drawer 等多页面 - 配置: api_client baseUrl 适配当前 WiFi IP
This commit is contained in:
@@ -21,7 +21,8 @@ class HomePage extends ConsumerStatefulWidget {
|
||||
ConsumerState<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends ConsumerState<HomePage> {
|
||||
class _HomePageState extends ConsumerState<HomePage>
|
||||
with WidgetsBindingObserver {
|
||||
final _textCtrl = TextEditingController();
|
||||
final _scrollCtrl = ScrollController();
|
||||
final _focusNode = FocusNode();
|
||||
@@ -32,6 +33,7 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => ref.invalidate(notificationUnreadCountProvider),
|
||||
);
|
||||
@@ -41,8 +43,19 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
// 键盘动画期间每帧都会回调,让列表底部始终贴住输入区上沿
|
||||
if (!_focusNode.hasFocus) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || !_scrollCtrl.hasClients) return;
|
||||
_scrollCtrl.jumpTo(_scrollCtrl.position.maxScrollExtent);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_notificationTimer?.cancel();
|
||||
_textCtrl.dispose();
|
||||
_scrollCtrl.dispose();
|
||||
@@ -102,16 +115,18 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFFFFCFF),
|
||||
drawer: const HealthDrawer(),
|
||||
drawerEdgeDragWidth: MediaQuery.of(context).size.width * 0.35,
|
||||
drawerEdgeDragWidth: MediaQuery.sizeOf(context).width * 0.65,
|
||||
body: AppBackground(
|
||||
safeArea: true,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(user),
|
||||
Expanded(
|
||||
child: ChatMessagesView(
|
||||
scrollCtrl: _scrollCtrl,
|
||||
messages: chatState.messages,
|
||||
child: RepaintBoundary(
|
||||
child: ChatMessagesView(
|
||||
scrollCtrl: _scrollCtrl,
|
||||
messages: chatState.messages,
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildBottomBar(context),
|
||||
@@ -472,17 +487,29 @@ class _HomePageState extends ConsumerState<HomePage> {
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.file_open_outlined,
|
||||
Icons.picture_as_pdf_outlined,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
title: const Text('传文件'),
|
||||
title: const Text('上传 PDF'),
|
||||
onTap: () async {
|
||||
Navigator.pop(ctx);
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
_textCtrl.text = '[文件已选择] ${result.files.first.name}';
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['pdf'],
|
||||
withData: false,
|
||||
);
|
||||
if (result == null || result.files.isEmpty) return;
|
||||
final pdfFile = result.files.first;
|
||||
final path = pdfFile.path;
|
||||
if (path == null || path.isEmpty) return;
|
||||
// 上传 + 让 AI 看 PDF 内容
|
||||
await ref.read(chatProvider.notifier).sendPdf(
|
||||
path,
|
||||
pdfFile.name,
|
||||
_textCtrl.text.trim(),
|
||||
);
|
||||
_textCtrl.clear();
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user