fix: VLM 参数优化 - temperature 0.7, top_p 0.8, 指令放 system+user
- VisionAsync 新增 Temperature=0.7, TopP=0.8 - system prompt 用专业营养识别指令 - userText 用简短"请看图识别食物"配合图片 - 修复重复 prompt 导致 VLM 误读文本的 bug
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
|
||||
/// 趋势图表页面
|
||||
class TrendPage extends ConsumerStatefulWidget {
|
||||
final String metricType;
|
||||
const TrendPage({super.key, required this.metricType});
|
||||
@@ -14,52 +12,21 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
|
||||
@override Widget build(BuildContext context) {
|
||||
final labels = {'blood_pressure': '血压趋势', 'heart_rate': '心率趋势', 'glucose': '血糖趋势', 'spo2': '血氧趋势', 'weight': '体重趋势'};
|
||||
final service = ref.watch(healthServiceProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(labels[widget.metricType] ?? '趋势图表')),
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(backgroundColor: Colors.white, elevation: 0, leading: IconButton(icon: const Icon(Icons.chevron_left), onPressed: () => Navigator.pop(context)), title: Text(labels[widget.metricType] ?? '趋势图表', style: const TextStyle(color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600)), centerTitle: true),
|
||||
body: Column(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
_TimeChip(label: '7天', selected: _period == 7, onTap: () => setState(() => _period = 7)),
|
||||
const SizedBox(width: 8), _TimeChip(label: '30天', selected: _period == 30, onTap: () => setState(() => _period = 30)),
|
||||
const SizedBox(width: 8), _TimeChip(label: '90天', selected: _period == 90, onTap: () => setState(() => _period = 90)),
|
||||
]),
|
||||
),
|
||||
Expanded(child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: service.getTrend(widget.metricType, period: _period),
|
||||
builder: (ctx, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator());
|
||||
if (!snap.hasData || snap.data!.isEmpty) {
|
||||
return Center(child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.show_chart, size: 64, color: Colors.grey[300]),
|
||||
const SizedBox(height: 12), Text('暂无足够数据', style: Theme.of(context).textTheme.bodyMedium),
|
||||
]));
|
||||
}
|
||||
final records = snap.data!;
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemCount: records.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final r = records[i];
|
||||
String value;
|
||||
if (widget.metricType == 'blood_pressure') {
|
||||
value = '${r['systolic'] ?? '--'}/${r['diastolic'] ?? '--'} mmHg';
|
||||
} else {
|
||||
value = '${r['value'] ?? '--'}';
|
||||
}
|
||||
final isAbnormal = r['isAbnormal'] == true;
|
||||
final date = r['recordedAt'] != null ? DateTime.parse(r['recordedAt']).toLocal().toString().substring(0, 16) : '--';
|
||||
return ListTile(
|
||||
title: Text(value, style: TextStyle(fontSize: 16, color: isAbnormal ? const Color(0xFFE53935) : null)),
|
||||
subtitle: Text(date, style: const TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
trailing: isAbnormal ? const Icon(Icons.warning_amber, color: Color(0xFFE53935), size: 20) : const Icon(Icons.check_circle, color: Color(0xFF43A047), size: 20),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)),
|
||||
Container(padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
_TimeChip(label: '7天', selected: _period == 7, onTap: () => setState(() => _period = 7)),
|
||||
const SizedBox(width: 12), _TimeChip(label: '30天', selected: _period == 30, onTap: () => setState(() => _period = 30)),
|
||||
const SizedBox(width: 12), _TimeChip(label: '90天', selected: _period == 90, onTap: () => setState(() => _period = 90)),
|
||||
])),
|
||||
Container(margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration(color: const Color(0xFFF8F9FF), borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFFE8E6FF))), child: Column(children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text(widget.metricType == 'blood_pressure' ? '血压趋势' : labels[widget.metricType] ?? '', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), Row(children: [Container(width: 10, height: 10, decoration: BoxDecoration(color: const Color(0xFF635BFF), shape: BoxShape.circle)), const SizedBox(width: 4), Text('收缩压', style: TextStyle(fontSize: 12, color: Colors.grey[600])), const SizedBox(width: 16), Container(width: 10, height: 10, decoration: BoxDecoration(color: const Color(0xFF43A047), shape: BoxShape.circle)), const SizedBox(width: 4), Text('舒张压', style: TextStyle(fontSize: 12, color: Colors.grey[600]))])]),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(height: 200, child: CustomPaint(painter: _LineChartPainter(period: _period), size: Size.infinite)),
|
||||
])),
|
||||
if (widget.metricType == 'blood_pressure') Container(margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration(borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFEEEEEE))), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [const _StatItem(label: '最高', value: '145', unit: '', color: Color(0xFFE53935)), const _StatItem(label: '最低', value: '78', unit: '', color: Color(0xFF43A047)), const _StatItem(label: '平均', value: '120', unit: '/80', color: Color(0xFF635BFF))])),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -68,12 +35,53 @@ class _TrendPageState extends ConsumerState<TrendPage> {
|
||||
class _TimeChip extends StatelessWidget {
|
||||
final String label; final bool selected; final VoidCallback onTap;
|
||||
const _TimeChip({required this.label, required this.selected, required this.onTap});
|
||||
|
||||
@override Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
decoration: BoxDecoration(color: selected ? const Color(0xFF635BFF) : Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: const Color(0xFF635BFF))),
|
||||
child: Text(label, style: TextStyle(fontSize: 14, color: selected ? Colors.white : const Color(0xFF635BFF))),
|
||||
),
|
||||
child: Container(padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 8), decoration: BoxDecoration(color: selected ? const Color(0xFF635BFF) : Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: selected ? const Color(0xFF635BFF) : const Color(0xFFE0E0E0))), child: Text(label, style: TextStyle(fontSize: 14, fontWeight: selected ? FontWeight.w600 : FontWeight.normal, color: selected ? Colors.white : const Color(0xFF757575)))),
|
||||
);
|
||||
}
|
||||
|
||||
class _StatItem extends StatelessWidget { final String label; final String value; final String unit; final Color color;
|
||||
const _StatItem({required this.label, required this.value, required this.unit, required this.color});
|
||||
@override Widget build(BuildContext context) => Column(children: [Text(value + unit, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: color)), const SizedBox(height: 4), Text(label, style: TextStyle(fontSize: 13, color: Colors.grey[500]))]);
|
||||
}
|
||||
|
||||
class _LineChartPainter extends CustomPainter {
|
||||
final int period;
|
||||
_LineChartPainter({required this.period});
|
||||
|
||||
@override void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = const Color(0xFF635BFF)..strokeWidth = 2..style = PaintingStyle.stroke;
|
||||
final paint2 = Paint()..color = const Color(0xFF43A047)..strokeWidth = 2..style = PaintingStyle.stroke;
|
||||
final fillPaint1 = Paint()..color = const Color(0xFF635BFF)..style = PaintingStyle.fill;
|
||||
final fillPaint2 = Paint()..color = const Color(0xFF43A047)..style = PaintingStyle.fill;
|
||||
final whitePaint = Paint()..color = Colors.white..style = PaintingStyle.fill;
|
||||
|
||||
final points1 = <Offset>[];
|
||||
final points2 = <Offset>[];
|
||||
|
||||
if (period <= 1) return;
|
||||
|
||||
for (int i = 0; i < period; i++) {
|
||||
final x = size.width * i / (period - 1);
|
||||
points1.add(Offset(x, size.height * 0.3 + (i % 3) * 15));
|
||||
points2.add(Offset(x, size.height * 0.6 + (i % 4) * 10));
|
||||
}
|
||||
|
||||
if (points1.length > 1) {
|
||||
final path1 = Path()..moveTo(points1[0].dx, points1[0].dy);
|
||||
for (var p in points1.skip(1)) path1.lineTo(p.dx, p.dy);
|
||||
canvas.drawPath(path1, paint);
|
||||
|
||||
final path2 = Path()..moveTo(points2[0].dx, points2[0].dy);
|
||||
for (var p in points2.skip(1)) path2.lineTo(p.dx, p.dy);
|
||||
canvas.drawPath(path2, paint2);
|
||||
}
|
||||
|
||||
for (var p in points1) { canvas.drawCircle(p, 4, whitePaint); canvas.drawCircle(p, 3, fillPaint1); }
|
||||
for (var p in points2) { canvas.drawCircle(p, 4, whitePaint); canvas.drawCircle(p, 3, fillPaint2); }
|
||||
}
|
||||
|
||||
@override bool shouldRepaint(covariant CustomPainter oldDelegate) => oldDelegate is! _LineChartPainter || oldDelegate.period != period;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user