neoagent 2.3.1-beta.53 → 2.3.1-beta.56

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.
@@ -30,33 +30,60 @@ Color get _danger => _palette.danger;
30
30
  Color get _info => _palette.info;
31
31
 
32
32
  LinearGradient get _appBackgroundGradient => LinearGradient(
33
- colors: <Color>[_bgPrimary, _bgSecondary.withValues(alpha: 0.96), _bgPrimary],
34
- begin: Alignment.topLeft,
35
- end: Alignment.bottomRight,
33
+ colors: <Color>[
34
+ _bgPrimary,
35
+ Color.lerp(_bgSecondary, _accentAlt, 0.08)!.withValues(alpha: 0.98),
36
+ Color.lerp(_bgPrimary, _accent, 0.05)!,
37
+ ],
38
+ stops: const <double>[0, 0.52, 1],
39
+ begin: const Alignment(-0.95, -1),
40
+ end: const Alignment(1, 0.92),
36
41
  );
37
42
 
38
43
  LinearGradient get _panelGradient => LinearGradient(
39
44
  colors: <Color>[
40
- _bgCard.withValues(alpha: 0.96),
41
- _bgSecondary.withValues(alpha: 0.92),
45
+ Colors.white.withValues(alpha: 0.08),
46
+ _bgCard.withValues(alpha: 0.9),
47
+ _bgSecondary.withValues(alpha: 0.82),
42
48
  ],
43
- begin: Alignment.topLeft,
44
- end: Alignment.bottomRight,
49
+ stops: const <double>[0, 0.18, 1],
50
+ begin: const Alignment(-0.85, -1),
51
+ end: const Alignment(1, 1),
45
52
  );
46
53
 
47
54
  List<BoxShadow> get _softPanelShadow => <BoxShadow>[
48
55
  BoxShadow(
49
- color: Colors.black.withValues(alpha: 0.16),
50
- blurRadius: 40,
51
- offset: const Offset(0, 16),
56
+ color: Colors.black.withValues(alpha: 0.22),
57
+ blurRadius: 52,
58
+ offset: const Offset(0, 22),
52
59
  ),
53
60
  BoxShadow(
54
- color: _accent.withValues(alpha: 0.05),
55
- blurRadius: 28,
56
- offset: const Offset(0, 6),
61
+ color: _accent.withValues(alpha: 0.08),
62
+ blurRadius: 30,
63
+ offset: const Offset(0, 8),
57
64
  ),
58
65
  ];
59
66
 
67
+ Color get _glassFill =>
68
+ _bgCard.withValues(alpha: _palette == _darkPalette ? 0.58 : 0.72);
69
+ Color get _glassOverlay =>
70
+ Colors.white.withValues(alpha: _palette == _darkPalette ? 0.08 : 0.24);
71
+ Color get _glassBorder =>
72
+ Colors.white.withValues(alpha: _palette == _darkPalette ? 0.15 : 0.42);
73
+ Color get _glassHighlight =>
74
+ Colors.white.withValues(alpha: _palette == _darkPalette ? 0.18 : 0.4);
75
+
76
+ LinearGradient get _liquidMetalGradient => LinearGradient(
77
+ colors: <Color>[
78
+ _glassOverlay,
79
+ Colors.white.withValues(alpha: 0.02),
80
+ _accentMuted.withValues(alpha: 0.16),
81
+ ],
82
+ stops: const <double>[0, 0.44, 1],
83
+ begin: const Alignment(-1, -1),
84
+ end: const Alignment(1, 1),
85
+ );
86
+
60
87
  TextStyle _displayTitleStyle([double size = 28]) => GoogleFonts.spaceGrotesk(
61
88
  fontSize: size,
62
89
  fontWeight: FontWeight.w700,
@@ -95,35 +122,39 @@ ThemeData _buildNeoAgentTheme(NeoAgentPalette palette, Brightness brightness) {
95
122
  ).apply(bodyColor: palette.textPrimary, displayColor: palette.textPrimary),
96
123
  cardTheme: CardThemeData(
97
124
  color: palette.bgCard.withValues(
98
- alpha: brightness == Brightness.dark ? 0.9 : 0.94,
125
+ alpha: brightness == Brightness.dark ? 0.58 : 0.78,
99
126
  ),
100
127
  shadowColor: Colors.black.withValues(
101
- alpha: brightness == Brightness.dark ? 0.24 : 0.08,
128
+ alpha: brightness == Brightness.dark ? 0.24 : 0.12,
102
129
  ),
103
130
  surfaceTintColor: Colors.transparent,
104
- elevation: brightness == Brightness.dark ? 8 : 3,
131
+ elevation: brightness == Brightness.dark ? 10 : 5,
105
132
  margin: EdgeInsets.zero,
106
133
  shape: RoundedRectangleBorder(
107
- borderRadius: BorderRadius.circular(24),
108
- side: BorderSide(color: palette.borderLight),
134
+ borderRadius: BorderRadius.circular(28),
135
+ side: BorderSide(
136
+ color: Colors.white.withValues(
137
+ alpha: brightness == Brightness.dark ? 0.12 : 0.3,
138
+ ),
139
+ ),
109
140
  ),
110
141
  ),
111
142
  inputDecorationTheme: InputDecorationTheme(
112
143
  filled: true,
113
144
  fillColor: palette.bgSecondary.withValues(
114
- alpha: brightness == Brightness.dark ? 0.76 : 0.68,
145
+ alpha: brightness == Brightness.dark ? 0.54 : 0.52,
115
146
  ),
116
147
  border: OutlineInputBorder(
117
148
  borderRadius: BorderRadius.circular(18),
118
- borderSide: BorderSide(color: palette.border),
149
+ borderSide: BorderSide(color: palette.borderLight),
119
150
  ),
120
151
  enabledBorder: OutlineInputBorder(
121
152
  borderRadius: BorderRadius.circular(18),
122
- borderSide: BorderSide(color: palette.border),
153
+ borderSide: BorderSide(color: palette.borderLight),
123
154
  ),
124
155
  focusedBorder: OutlineInputBorder(
125
156
  borderRadius: BorderRadius.circular(18),
126
- borderSide: BorderSide(color: palette.accent, width: 1.4),
157
+ borderSide: BorderSide(color: palette.accentHover, width: 1.4),
127
158
  ),
128
159
  contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
129
160
  labelStyle: TextStyle(
@@ -144,9 +175,17 @@ ThemeData _buildNeoAgentTheme(NeoAgentPalette palette, Brightness brightness) {
144
175
  : Colors.white,
145
176
  disabledBackgroundColor: palette.bgTertiary,
146
177
  disabledForegroundColor: palette.textMuted,
178
+ shadowColor: palette.accent.withValues(alpha: 0.3),
147
179
  elevation: 0,
148
180
  padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
149
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
181
+ shape: RoundedRectangleBorder(
182
+ borderRadius: BorderRadius.circular(18),
183
+ side: BorderSide(
184
+ color: Colors.white.withValues(
185
+ alpha: brightness == Brightness.dark ? 0.14 : 0.26,
186
+ ),
187
+ ),
188
+ ),
150
189
  textStyle: GoogleFonts.manrope(
151
190
  fontSize: 14,
152
191
  fontWeight: FontWeight.w700,
@@ -157,7 +196,14 @@ ThemeData _buildNeoAgentTheme(NeoAgentPalette palette, Brightness brightness) {
157
196
  outlinedButtonTheme: OutlinedButtonThemeData(
158
197
  style: OutlinedButton.styleFrom(
159
198
  foregroundColor: palette.textPrimary,
160
- side: BorderSide(color: palette.borderLight),
199
+ side: BorderSide(
200
+ color: Colors.white.withValues(
201
+ alpha: brightness == Brightness.dark ? 0.12 : 0.26,
202
+ ),
203
+ ),
204
+ backgroundColor: palette.bgCard.withValues(
205
+ alpha: brightness == Brightness.dark ? 0.2 : 0.5,
206
+ ),
161
207
  padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
162
208
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
163
209
  textStyle: GoogleFonts.manrope(
@@ -166,6 +212,15 @@ ThemeData _buildNeoAgentTheme(NeoAgentPalette palette, Brightness brightness) {
166
212
  ),
167
213
  ),
168
214
  ),
215
+ pageTransitionsTheme: const PageTransitionsTheme(
216
+ builders: <TargetPlatform, PageTransitionsBuilder>{
217
+ TargetPlatform.android: FadeForwardsPageTransitionsBuilder(),
218
+ TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
219
+ TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
220
+ TargetPlatform.windows: FadeForwardsPageTransitionsBuilder(),
221
+ TargetPlatform.linux: FadeForwardsPageTransitionsBuilder(),
222
+ },
223
+ ),
169
224
  textButtonTheme: TextButtonThemeData(
170
225
  style: TextButton.styleFrom(
171
226
  foregroundColor: palette.accentHover,
@@ -0,0 +1,17 @@
1
+ import 'package:flutter/foundation.dart';
2
+
3
+ import 'web_app_update_monitor_stub.dart'
4
+ if (dart.library.html) 'web_app_update_monitor_web.dart';
5
+
6
+ abstract class WebAppUpdateMonitor implements Listenable {
7
+ bool get isSupported;
8
+ bool get updateAvailable;
9
+ bool get isReloading;
10
+
11
+ void start();
12
+ Future<void> reloadToLatest();
13
+ void dispose();
14
+ }
15
+
16
+ WebAppUpdateMonitor createWebAppUpdateMonitor() =>
17
+ createPlatformWebAppUpdateMonitor();
@@ -0,0 +1,24 @@
1
+ import 'package:flutter/foundation.dart';
2
+
3
+ import 'web_app_update_monitor.dart';
4
+
5
+ WebAppUpdateMonitor createPlatformWebAppUpdateMonitor() =>
6
+ _UnsupportedWebAppUpdateMonitor();
7
+
8
+ class _UnsupportedWebAppUpdateMonitor extends ChangeNotifier
9
+ implements WebAppUpdateMonitor {
10
+ @override
11
+ bool get isSupported => false;
12
+
13
+ @override
14
+ bool get isReloading => false;
15
+
16
+ @override
17
+ bool get updateAvailable => false;
18
+
19
+ @override
20
+ void start() {}
21
+
22
+ @override
23
+ Future<void> reloadToLatest() async {}
24
+ }
@@ -0,0 +1,123 @@
1
+ // ignore_for_file: deprecated_member_use, avoid_web_libraries_in_flutter
2
+
3
+ import 'dart:async';
4
+ import 'dart:convert';
5
+ import 'dart:html' as html;
6
+
7
+ import 'package:flutter/foundation.dart';
8
+ import 'package:http/http.dart' as http;
9
+
10
+ import 'web_app_update_monitor.dart';
11
+
12
+ const String _currentWebBuildId = String.fromEnvironment(
13
+ 'NEOAGENT_WEB_BUILD_ID',
14
+ );
15
+
16
+ WebAppUpdateMonitor createPlatformWebAppUpdateMonitor() =>
17
+ _WebPlatformAppUpdateMonitor();
18
+
19
+ class _WebPlatformAppUpdateMonitor extends ChangeNotifier
20
+ implements WebAppUpdateMonitor {
21
+ static const Duration _pollInterval = Duration(minutes: 1);
22
+
23
+ Timer? _pollTimer;
24
+ bool _updateAvailable = false;
25
+ bool _isReloading = false;
26
+
27
+ @override
28
+ bool get isSupported => _currentWebBuildId.trim().isNotEmpty;
29
+
30
+ @override
31
+ bool get updateAvailable => _updateAvailable;
32
+
33
+ @override
34
+ bool get isReloading => _isReloading;
35
+
36
+ @override
37
+ void start() {
38
+ if (!isSupported || _pollTimer != null) {
39
+ return;
40
+ }
41
+ unawaited(_checkForUpdate());
42
+ _pollTimer = Timer.periodic(
43
+ _pollInterval,
44
+ (_) => unawaited(_checkForUpdate()),
45
+ );
46
+ }
47
+
48
+ Future<void> _checkForUpdate() async {
49
+ if (_updateAvailable || _isReloading) {
50
+ return;
51
+ }
52
+ try {
53
+ final buildInfoUri = Uri.base.resolve(
54
+ '/app-build.json?ts=${DateTime.now().microsecondsSinceEpoch}',
55
+ );
56
+ final response = await http.get(
57
+ buildInfoUri,
58
+ headers: const <String, String>{
59
+ 'cache-control': 'no-cache, no-store, max-age=0',
60
+ 'pragma': 'no-cache',
61
+ },
62
+ );
63
+ if (response.statusCode != 200) {
64
+ return;
65
+ }
66
+ final payload = jsonDecode(response.body);
67
+ if (payload is! Map) {
68
+ return;
69
+ }
70
+ final latestBuildId = payload['buildId']?.toString().trim() ?? '';
71
+ if (latestBuildId.isEmpty || latestBuildId == _currentWebBuildId) {
72
+ return;
73
+ }
74
+ _updateAvailable = true;
75
+ notifyListeners();
76
+ } catch (_) {}
77
+ }
78
+
79
+ @override
80
+ Future<void> reloadToLatest() async {
81
+ if (!isSupported || _isReloading) {
82
+ return;
83
+ }
84
+ _isReloading = true;
85
+ notifyListeners();
86
+
87
+ try {
88
+ final serviceWorker = html.window.navigator.serviceWorker;
89
+ if (serviceWorker != null) {
90
+ final registrations = await serviceWorker.getRegistrations();
91
+ for (final registration in registrations) {
92
+ await registration.unregister();
93
+ }
94
+ }
95
+ } catch (_) {}
96
+
97
+ try {
98
+ final cacheNames = await html.window.caches?.keys();
99
+ if (cacheNames != null) {
100
+ for (final cacheName in cacheNames) {
101
+ await html.window.caches?.delete(cacheName);
102
+ }
103
+ }
104
+ } catch (_) {}
105
+
106
+ final reloadUri = Uri(
107
+ path: Uri.base.path.isEmpty ? '/' : Uri.base.path,
108
+ queryParameters: <String, String>{
109
+ ...Uri.base.queryParameters,
110
+ 'reload': DateTime.now().microsecondsSinceEpoch.toString(),
111
+ },
112
+ fragment: Uri.base.fragment.isEmpty ? null : Uri.base.fragment,
113
+ );
114
+ html.window.location.replace(reloadUri.toString());
115
+ }
116
+
117
+ @override
118
+ void dispose() {
119
+ _pollTimer?.cancel();
120
+ _pollTimer = null;
121
+ super.dispose();
122
+ }
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.53",
3
+ "version": "2.3.1-beta.56",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -35,8 +35,8 @@
35
35
  "docs:dev": "docusaurus start",
36
36
  "docs:build": "docusaurus build",
37
37
  "docs:preview": "docusaurus serve build",
38
- "flutter:run:web": "cd flutter_app && flutter run -d chrome",
39
- "flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-}",
38
+ "flutter:run:web": "cd flutter_app && flutter run -d chrome --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
39
+ "flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-} --dart-define=NEOAGENT_WEB_BUILD_ID=$(node ../scripts/web_build_id.js)",
40
40
  "manage": "node bin/neoagent.js",
41
41
  "test": "node --test",
42
42
  "benchmark:tokens": "node scripts/benchmark-token-cost.js",
@@ -7,6 +7,35 @@ const { DATA_DIR } = require('../../runtime/paths');
7
7
  const { requireAuth } = require('../middleware/auth');
8
8
 
9
9
  const FLUTTER_WEB_DIR = path.join(__dirname, '..', 'public');
10
+ const BUILD_ID_PATH = path.join(FLUTTER_WEB_DIR, '.last_build_id');
11
+
12
+ function setNoStoreHeaders(res) {
13
+ res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
14
+ res.set('Pragma', 'no-cache');
15
+ res.set('Expires', '0');
16
+ res.set('Surrogate-Control', 'no-store');
17
+ res.set('CDN-Cache-Control', 'no-store');
18
+ }
19
+
20
+ function readCurrentBuildId() {
21
+ try {
22
+ return fs.readFileSync(BUILD_ID_PATH, 'utf8').trim() || null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function setFlutterStaticHeaders(res, filePath) {
29
+ const relativePath = path.relative(FLUTTER_WEB_DIR, filePath).replaceAll('\\', '/');
30
+ if (
31
+ relativePath === 'index.html' ||
32
+ relativePath === 'flutter_bootstrap.js' ||
33
+ relativePath === 'flutter_service_worker.js' ||
34
+ relativePath === 'version.json'
35
+ ) {
36
+ setNoStoreHeaders(res);
37
+ }
38
+ }
10
39
 
11
40
  function registerStaticRoutes(app) {
12
41
  app.use(
@@ -27,7 +56,17 @@ function registerStaticRoutes(app) {
27
56
  express.static(path.join(DATA_DIR, 'screenshots'))
28
57
  );
29
58
 
30
- app.use(express.static(FLUTTER_WEB_DIR, { index: false }));
59
+ app.get('/app-build.json', (req, res) => {
60
+ setNoStoreHeaders(res);
61
+ res.json({
62
+ buildId: readCurrentBuildId(),
63
+ });
64
+ });
65
+
66
+ app.use(express.static(FLUTTER_WEB_DIR, {
67
+ index: false,
68
+ setHeaders: setFlutterStaticHeaders,
69
+ }));
31
70
  app.get(/^\/(?!api|screenshots|telnyx-audio).*/, serveFlutterApp);
32
71
  }
33
72
 
@@ -40,6 +79,7 @@ function serveFlutterApp(req, res) {
40
79
  'Flutter web build not found. Run "npm run flutter:build:web" to generate the bundled client.'
41
80
  );
42
81
  }
82
+ setNoStoreHeaders(res);
43
83
  return res.sendFile(entry);
44
84
  }
45
85
 
@@ -1 +1 @@
1
- 31a20dcc543d16b6423454439e96e69a
1
+ af264d9a8e6140cadf8a95b90b714112
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "2193380936" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3546406361" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });