neoagent 3.0.1-beta.19 → 3.0.1-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,10 @@ class _ChatPanelState extends State<ChatPanel> with WidgetsBindingObserver {
24
24
  bool _ignoreScrollUpdates = false;
25
25
  bool _loadingOlderHistory = false;
26
26
  int _scrollGeneration = 0;
27
+ // Opacity-hide the list while the initial batch of messages settles to the
28
+ // bottom, so the user never sees the layout jitter across settle passes.
29
+ bool _awaitingInitialScrollSettle = false;
30
+ int _visibleMessageCountAtLastSettle = 0;
27
31
  bool _isSendingChatMessage = false;
28
32
  bool _isDictating = false;
29
33
  bool _isTranscribing = false;
@@ -56,6 +60,8 @@ class _ChatPanelState extends State<ChatPanel> with WidgetsBindingObserver {
56
60
  _appliedSharedPayloadSignature = null;
57
61
  _lastScrollContentSignature = '';
58
62
  _stickToBottom = true;
63
+ _awaitingInitialScrollSettle = false;
64
+ _visibleMessageCountAtLastSettle = 0;
59
65
  _consumeQueuedDraft();
60
66
  _scheduleScrollToBottom(force: true);
61
67
  }
@@ -446,6 +452,12 @@ class _ChatPanelState extends State<ChatPanel> with WidgetsBindingObserver {
446
452
  settle(remainingPasses - 1);
447
453
  }
448
454
  });
455
+ } else if (_awaitingInitialScrollSettle) {
456
+ setState(() {
457
+ _awaitingInitialScrollSettle = false;
458
+ _visibleMessageCountAtLastSettle =
459
+ widget.controller.visibleChatMessages.length;
460
+ });
449
461
  }
450
462
  });
451
463
  }
@@ -462,6 +474,15 @@ class _ChatPanelState extends State<ChatPanel> with WidgetsBindingObserver {
462
474
  final isInitialContent = _lastScrollContentSignature.isEmpty;
463
475
  final shouldFollow = _stickToBottom || isInitialContent;
464
476
  _lastScrollContentSignature = signature;
477
+
478
+ if (messages.isEmpty) {
479
+ // Agent switch resets the settle tracker so the next batch triggers hiding.
480
+ _visibleMessageCountAtLastSettle = 0;
481
+ } else if (_visibleMessageCountAtLastSettle == 0 && !_awaitingInitialScrollSettle) {
482
+ // Messages just went from 0 → N: hide until the scroll position settles.
483
+ _awaitingInitialScrollSettle = true;
484
+ }
485
+
465
486
  if (shouldFollow) {
466
487
  _scheduleScrollToBottom(force: isInitialContent);
467
488
  }
@@ -561,26 +582,29 @@ class _ChatPanelState extends State<ChatPanel> with WidgetsBindingObserver {
561
582
  Expanded(
562
583
  child: Stack(
563
584
  children: <Widget>[
564
- SelectionArea(
565
- child: ListView(
566
- controller: _scrollController,
567
- padding: EdgeInsets.fromLTRB(
568
- sidePadding,
569
- 30,
570
- sidePadding,
571
- 18,
572
- ),
573
- children: <Widget>[
574
- Center(
575
- child: ConstrainedBox(
576
- constraints: const BoxConstraints(maxWidth: 860),
577
- child: Column(
578
- crossAxisAlignment: CrossAxisAlignment.stretch,
579
- children: threadChildren,
585
+ Opacity(
586
+ opacity: _awaitingInitialScrollSettle ? 0.0 : 1.0,
587
+ child: SelectionArea(
588
+ child: ListView(
589
+ controller: _scrollController,
590
+ padding: EdgeInsets.fromLTRB(
591
+ sidePadding,
592
+ 30,
593
+ sidePadding,
594
+ 18,
595
+ ),
596
+ children: <Widget>[
597
+ Center(
598
+ child: ConstrainedBox(
599
+ constraints: const BoxConstraints(maxWidth: 860),
600
+ child: Column(
601
+ crossAxisAlignment: CrossAxisAlignment.stretch,
602
+ children: threadChildren,
603
+ ),
580
604
  ),
581
605
  ),
582
- ),
583
- ],
606
+ ],
607
+ ),
584
608
  ),
585
609
  ),
586
610
  if (!_stickToBottom)
@@ -4027,113 +4051,105 @@ class _RunHeroCard extends StatelessWidget {
4027
4051
 
4028
4052
  @override
4029
4053
  Widget build(BuildContext context) {
4030
- return Container(
4031
- width: double.infinity,
4032
- padding: const EdgeInsets.all(22),
4033
- decoration: BoxDecoration(
4034
- gradient: LinearGradient(
4035
- colors: <Color>[
4036
- run.statusColor.withValues(alpha: 0.18),
4037
- _bgSecondary,
4038
- ],
4039
- begin: Alignment.topLeft,
4040
- end: Alignment.bottomRight,
4041
- ),
4042
- borderRadius: BorderRadius.circular(24),
4043
- border: Border.all(color: _borderLight),
4044
- ),
4045
- child: Column(
4046
- crossAxisAlignment: CrossAxisAlignment.start,
4047
- children: <Widget>[
4048
- Row(
4049
- crossAxisAlignment: CrossAxisAlignment.start,
4050
- children: <Widget>[
4051
- Expanded(
4052
- child: Column(
4053
- crossAxisAlignment: CrossAxisAlignment.start,
4054
- children: <Widget>[
4055
- Wrap(
4056
- spacing: 10,
4057
- runSpacing: 10,
4058
- children: <Widget>[
4059
- _StatusPill(
4060
- label: run.statusLabel,
4061
- color: run.statusColor,
4062
- ),
4063
- _MetaPill(
4064
- label: run.triggerLabel,
4065
- icon: Icons.bolt_outlined,
4066
- ),
4067
- _MetaPill(
4068
- label: run.modelLabel,
4069
- icon: Icons.memory_outlined,
4070
- ),
4071
- if (run.deliverableType.trim().isNotEmpty)
4072
- _MetaPill(
4073
- label: run.deliverableType.replaceAll('_', ' '),
4074
- icon: Icons.inventory_2_outlined,
4075
- ),
4076
- ],
4077
- ),
4078
- const SizedBox(height: 16),
4079
- Text(
4080
- run.title,
4081
- style: TextStyle(
4082
- fontSize: 24,
4083
- fontWeight: FontWeight.w800,
4084
- height: 1.15,
4085
- ),
4086
- ),
4087
- const SizedBox(height: 10),
4088
- Wrap(
4089
- spacing: 10,
4090
- runSpacing: 10,
4091
- children: <Widget>[
4092
- _MetaPill(
4093
- label: 'Started ${run.createdAtLabel}',
4094
- icon: Icons.schedule_outlined,
4095
- ),
4096
- _MetaPill(
4097
- label: run.durationLabel,
4098
- icon: Icons.timer_outlined,
4099
- ),
4100
- _MetaPill(
4101
- label: '${run.totalTokensLabel} tokens',
4102
- icon: Icons.toll_outlined,
4103
- ),
4104
- _MetaPill(
4105
- label: run.id.length <= 12
4106
- ? run.id
4107
- : '${run.id.substring(0, 12)}…',
4108
- icon: Icons.tag_outlined,
4109
- ),
4110
- ],
4111
- ),
4112
- ],
4054
+ final statusColor = run.statusColor;
4055
+ return Card(
4056
+ child: Padding(
4057
+ padding: const EdgeInsets.all(14),
4058
+ child: Column(
4059
+ crossAxisAlignment: CrossAxisAlignment.start,
4060
+ children: <Widget>[
4061
+ Row(
4062
+ crossAxisAlignment: CrossAxisAlignment.center,
4063
+ children: <Widget>[
4064
+ Container(
4065
+ width: 8,
4066
+ height: 8,
4067
+ decoration: BoxDecoration(
4068
+ color: statusColor,
4069
+ shape: BoxShape.circle,
4070
+ ),
4113
4071
  ),
4072
+ const SizedBox(width: 7),
4073
+ Text(
4074
+ run.statusLabel,
4075
+ style: TextStyle(
4076
+ color: statusColor,
4077
+ fontWeight: FontWeight.w600,
4078
+ fontSize: 12,
4079
+ ),
4080
+ ),
4081
+ const Spacer(),
4082
+ IconButton(
4083
+ tooltip: 'Delete run',
4084
+ icon: const Icon(Icons.delete_outline, size: 18),
4085
+ onPressed: onDelete,
4086
+ visualDensity: VisualDensity.compact,
4087
+ color: _textSecondary,
4088
+ ),
4089
+ ],
4090
+ ),
4091
+ const SizedBox(height: 8),
4092
+ Text(
4093
+ run.title,
4094
+ style: const TextStyle(
4095
+ fontSize: 15,
4096
+ fontWeight: FontWeight.w700,
4097
+ height: 1.3,
4114
4098
  ),
4115
- const SizedBox(width: 12),
4116
- OutlinedButton.icon(
4117
- onPressed: onDelete,
4118
- icon: Icon(Icons.delete_outline),
4119
- label: Text('Delete'),
4099
+ maxLines: 3,
4100
+ overflow: TextOverflow.ellipsis,
4101
+ ),
4102
+ const SizedBox(height: 10),
4103
+ Wrap(
4104
+ spacing: 6,
4105
+ runSpacing: 6,
4106
+ children: <Widget>[
4107
+ _MetaPill(
4108
+ label: run.triggerLabel,
4109
+ icon: Icons.bolt_outlined,
4110
+ ),
4111
+ _MetaPill(
4112
+ label: run.modelLabel,
4113
+ icon: Icons.memory_outlined,
4114
+ ),
4115
+ _MetaPill(
4116
+ label: run.createdAtLabel,
4117
+ icon: Icons.schedule_outlined,
4118
+ ),
4119
+ _MetaPill(
4120
+ label: run.durationLabel,
4121
+ icon: Icons.timer_outlined,
4122
+ ),
4123
+ if (run.totalTokensLabel.isNotEmpty)
4124
+ _MetaPill(
4125
+ label: '${run.totalTokensLabel} tok',
4126
+ icon: Icons.toll_outlined,
4127
+ ),
4128
+ if (run.deliverableType.trim().isNotEmpty)
4129
+ _MetaPill(
4130
+ label: run.deliverableType.replaceAll('_', ' '),
4131
+ icon: Icons.inventory_2_outlined,
4132
+ ),
4133
+ ],
4134
+ ),
4135
+ if (run.error.trim().isNotEmpty) ...<Widget>[
4136
+ const SizedBox(height: 10),
4137
+ Container(
4138
+ width: double.infinity,
4139
+ padding: const EdgeInsets.all(10),
4140
+ decoration: BoxDecoration(
4141
+ color: const Color(0x19EF4444),
4142
+ borderRadius: BorderRadius.circular(10),
4143
+ border: Border.all(color: const Color(0x4CEF4444)),
4144
+ ),
4145
+ child: Text(
4146
+ run.error,
4147
+ style: TextStyle(fontSize: 12, height: 1.45),
4148
+ ),
4120
4149
  ),
4121
4150
  ],
4122
- ),
4123
- if (run.error.trim().isNotEmpty) ...<Widget>[
4124
- const SizedBox(height: 16),
4125
- Container(
4126
- width: double.infinity,
4127
- padding: const EdgeInsets.all(14),
4128
- decoration: BoxDecoration(
4129
- color: const Color(0x19EF4444),
4130
- borderRadius: BorderRadius.circular(14),
4131
- border: Border.all(color: const Color(0x4CEF4444)),
4132
- ),
4133
- child: Text(run.error, style: TextStyle(height: 1.45)),
4134
- ),
4135
4151
  ],
4136
- ],
4152
+ ),
4137
4153
  ),
4138
4154
  );
4139
4155
  }
@@ -67,7 +67,7 @@ class NeoAgentController extends ChangeNotifier {
67
67
  final Map<String, DateTime> _manualRunCooldowns = <String, DateTime>{};
68
68
  static const Duration _manualRunCooldownDuration = Duration(seconds: 10);
69
69
  static const Duration _homeWidgetSyncCooldown = Duration(seconds: 5);
70
- static const int _chatHistoryPageSize = 40;
70
+ static const int _chatHistoryPageSize = 20;
71
71
  DateTime? _lastHomeWidgetSyncAt;
72
72
  int _authCycle = 0;
73
73
  bool _isPollingQrLogin = false;
@@ -950,7 +950,7 @@ class _DeviceSurfaceHeader extends StatelessWidget {
950
950
  ? 'Live'
951
951
  : 'Offline'))
952
952
  : surface == _DeviceSurface.files
953
- ? 'Isolated'
953
+ ? ''
954
954
  : (androidOnline
955
955
  ? 'Live'
956
956
  : androidStarting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.0.1-beta.19",
3
+ "version": "3.0.1-beta.20",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",