neoagent 2.4.2-beta.3 → 2.4.2-beta.5

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.
Files changed (33) hide show
  1. package/.env.example +0 -9
  2. package/docs/configuration.md +0 -10
  3. package/flutter_app/lib/main.dart +0 -1
  4. package/flutter_app/lib/main_account_settings.dart +57 -16
  5. package/flutter_app/lib/main_controller.dart +0 -160
  6. package/flutter_app/lib/main_models.dart +6 -0
  7. package/flutter_app/lib/main_operations.dart +17 -1
  8. package/flutter_app/lib/main_runtime.dart +0 -22
  9. package/flutter_app/lib/main_shared.dart +0 -161
  10. package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
  11. package/flutter_app/pubspec.lock +0 -8
  12. package/flutter_app/pubspec.yaml +0 -1
  13. package/landing/index.html +1 -1
  14. package/package.json +1 -1
  15. package/server/admin/admin.css +23 -0
  16. package/server/admin/admin.js +89 -32
  17. package/server/admin/analytics.js +254 -48
  18. package/server/admin/index.html +36 -2
  19. package/server/admin/users.js +94 -37
  20. package/server/public/.last_build_id +1 -1
  21. package/server/public/assets/AssetManifest.bin +1 -1
  22. package/server/public/assets/AssetManifest.bin.json +1 -1
  23. package/server/public/assets/NOTICES +0 -183
  24. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  25. package/server/public/flutter_bootstrap.js +1 -1
  26. package/server/public/main.dart.js +77607 -78350
  27. package/server/routes/account.js +11 -2
  28. package/server/routes/admin.js +106 -24
  29. package/server/routes/runtime.js +1 -4
  30. package/server/services/ai/engine.js +13 -11
  31. package/server/services/ai/models.js +10 -4
  32. package/flutter_app/lib/src/analytics_service.dart +0 -294
  33. package/server/config/analytics.js +0 -30
@@ -116,10 +116,19 @@ router.get('/usage', (req, res) => {
116
116
  const userLimits = db.prepare('SELECT rate_limit_4h, rate_limit_weekly FROM users WHERE id = ?').get(userId);
117
117
  const h4Tokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-4 hours')").get(userId).t;
118
118
  const weeklyTokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-7 days')").get(userId).t;
119
+
120
+ const globalLimit4h = process.env.NEOAGENT_RATE_LIMIT_4H ? parseInt(process.env.NEOAGENT_RATE_LIMIT_4H, 10) : null;
121
+ const globalLimitWeekly = process.env.NEOAGENT_RATE_LIMIT_WEEKLY ? parseInt(process.env.NEOAGENT_RATE_LIMIT_WEEKLY, 10) : null;
122
+
123
+ const custom4h = userLimits?.rate_limit_4h ?? null;
124
+ const customWeekly = userLimits?.rate_limit_weekly ?? null;
125
+
119
126
  res.json({
120
127
  limits: {
121
- fourHour: userLimits?.rate_limit_4h || null,
122
- weekly: userLimits?.rate_limit_weekly || null,
128
+ fourHour: custom4h ?? globalLimit4h,
129
+ weekly: customWeekly ?? globalLimitWeekly,
130
+ fourHourIsCustom: custom4h !== null,
131
+ weeklyIsCustom: customWeekly !== null,
123
132
  },
124
133
  usage: {
125
134
  fourHour: h4Tokens,
@@ -416,20 +416,69 @@ router.delete('/api/settings/apikey', requireAdminAuth, settingsLimiter, (req, r
416
416
 
417
417
  router.get('/api/analytics', requireAdminAuth, (req, res) => {
418
418
  const db = require('../db/database');
419
+ const range = Math.min(Math.max(parseInt(req.query.range) || 30, 1), 365);
419
420
  const now = new Date().toISOString();
420
421
  const dayAgo = new Date(Date.now() - 86_400_000).toISOString();
421
422
  const weekAgo = new Date(Date.now() - 7 * 86_400_000).toISOString();
423
+ const rangeAgo = new Date(Date.now() - range * 86_400_000).toISOString();
422
424
  try {
425
+ const totalRunsRow = db.prepare('SELECT COUNT(*) AS n, COALESCE(SUM(total_tokens),0) AS t FROM agent_runs').get();
426
+ const successRow = db.prepare("SELECT COUNT(*) AS n FROM agent_runs WHERE status = 'completed'").get();
423
427
  const stats = {
424
- totalUsers: db.prepare('SELECT COUNT(*) AS n FROM users').get().n,
425
- activeToday: db.prepare('SELECT COUNT(*) AS n FROM users WHERE last_login > ?').get(dayAgo).n,
426
- newThisWeek: db.prepare('SELECT COUNT(*) AS n FROM users WHERE created_at > ?').get(weekAgo).n,
427
- totalRuns: db.prepare('SELECT COUNT(*) AS n FROM agent_runs').get().n,
428
- runsToday: db.prepare('SELECT COUNT(*) AS n FROM agent_runs WHERE created_at > ?').get(dayAgo).n,
429
- totalTokens: db.prepare('SELECT COALESCE(SUM(total_tokens),0) AS n FROM agent_runs').get().n,
430
- activeSessions:db.prepare('SELECT COUNT(*) AS n FROM user_sessions WHERE revoked_at IS NULL AND expires_at > ?').get(now).n,
431
- totalStorage: db.prepare('SELECT COALESCE(SUM(byte_size),0) AS n FROM artifacts').get().n,
428
+ totalUsers: db.prepare('SELECT COUNT(*) AS n FROM users').get().n,
429
+ activeToday: db.prepare('SELECT COUNT(*) AS n FROM users WHERE last_login > ?').get(dayAgo).n,
430
+ newThisWeek: db.prepare('SELECT COUNT(*) AS n FROM users WHERE created_at > ?').get(weekAgo).n,
431
+ totalRuns: totalRunsRow.n,
432
+ runsToday: db.prepare('SELECT COUNT(*) AS n FROM agent_runs WHERE created_at > ?').get(dayAgo).n,
433
+ runsThisWeek: db.prepare('SELECT COUNT(*) AS n FROM agent_runs WHERE created_at > ?').get(weekAgo).n,
434
+ totalTokens: totalRunsRow.t,
435
+ tokensToday: db.prepare("SELECT COALESCE(SUM(total_tokens),0) AS n FROM agent_runs WHERE created_at > ?").get(dayAgo).n,
436
+ avgTokensPerRun: totalRunsRow.n > 0 ? Math.round(totalRunsRow.t / totalRunsRow.n) : 0,
437
+ successRate: totalRunsRow.n > 0 ? Math.round((successRow.n / totalRunsRow.n) * 100) : 0,
438
+ activeSessions: db.prepare('SELECT COUNT(*) AS n FROM user_sessions WHERE revoked_at IS NULL AND expires_at > ?').get(now).n,
439
+ totalStorage: db.prepare('SELECT COALESCE(SUM(byte_size),0) AS n FROM artifacts').get().n,
432
440
  };
441
+
442
+ // Time-series: runs + tokens per day for selected range
443
+ const runsByDay = db.prepare(`
444
+ SELECT date(created_at) AS date,
445
+ COUNT(*) AS runs,
446
+ COALESCE(SUM(total_tokens), 0) AS tokens
447
+ FROM agent_runs
448
+ WHERE created_at > ?
449
+ GROUP BY date(created_at)
450
+ ORDER BY date
451
+ `).all(rangeAgo);
452
+
453
+ // New users per day for selected range
454
+ const usersByDay = db.prepare(`
455
+ SELECT date(created_at) AS date, COUNT(*) AS newUsers
456
+ FROM users
457
+ WHERE created_at > ?
458
+ GROUP BY date(created_at)
459
+ ORDER BY date
460
+ `).all(rangeAgo);
461
+
462
+ // Model breakdown (top 10 by runs)
463
+ const modelBreakdown = db.prepare(`
464
+ SELECT COALESCE(model, 'unknown') AS model,
465
+ COUNT(*) AS runs,
466
+ COALESCE(SUM(total_tokens), 0) AS tokens
467
+ FROM agent_runs
468
+ WHERE created_at > ?
469
+ GROUP BY model
470
+ ORDER BY runs DESC
471
+ LIMIT 10
472
+ `).all(rangeAgo);
473
+
474
+ // Run status breakdown
475
+ const statusBreakdown = db.prepare(`
476
+ SELECT status, COUNT(*) AS count
477
+ FROM agent_runs
478
+ GROUP BY status
479
+ ORDER BY count DESC
480
+ `).all();
481
+
433
482
  const topUsers = db.prepare(`
434
483
  SELECT u.id, u.username, u.display_name,
435
484
  COALESCE(r.runs, 0) AS runs,
@@ -437,25 +486,25 @@ router.get('/api/analytics', requireAdminAuth, (req, res) => {
437
486
  COALESCE(a.storage, 0) AS storage
438
487
  FROM users u
439
488
  LEFT JOIN (
440
- SELECT user_id,
441
- COUNT(*) AS runs,
442
- COALESCE(SUM(total_tokens),0) AS tokens
489
+ SELECT user_id, COUNT(*) AS runs, COALESCE(SUM(total_tokens),0) AS tokens
443
490
  FROM agent_runs GROUP BY user_id
444
491
  ) r ON r.user_id = u.id
445
492
  LEFT JOIN (
446
493
  SELECT user_id, COALESCE(SUM(byte_size),0) AS storage
447
494
  FROM artifacts GROUP BY user_id
448
495
  ) a ON a.user_id = u.id
449
- ORDER BY runs DESC LIMIT 8
496
+ ORDER BY tokens DESC LIMIT 10
450
497
  `).all();
498
+
451
499
  const recentRuns = db.prepare(`
452
- SELECT r.id, u.username, r.title, r.status, r.total_tokens,
500
+ SELECT r.id, u.username, r.title, r.status, r.model, r.total_tokens,
453
501
  r.created_at, r.completed_at
454
502
  FROM agent_runs r
455
503
  JOIN users u ON u.id = r.user_id
456
- ORDER BY r.created_at DESC LIMIT 20
504
+ ORDER BY r.created_at DESC LIMIT 25
457
505
  `).all();
458
- res.json({ stats, topUsers, recentRuns });
506
+
507
+ res.json({ stats, runsByDay, usersByDay, modelBreakdown, statusBreakdown, topUsers, recentRuns });
459
508
  } catch (err) {
460
509
  res.status(500).json({ error: String(err.message || err) });
461
510
  }
@@ -618,29 +667,62 @@ router.put('/api/providers', requireAdminAuth, express.json(), (req, res) => {
618
667
  res.json({ ok: true });
619
668
  });
620
669
 
670
+ // --- Global default rate limits ---
671
+
672
+ router.get('/api/config/rate-limits', requireAdminAuth, (req, res) => {
673
+ res.json({
674
+ rate_limit_4h: process.env.NEOAGENT_RATE_LIMIT_4H ? parseInt(process.env.NEOAGENT_RATE_LIMIT_4H, 10) : null,
675
+ rate_limit_weekly: process.env.NEOAGENT_RATE_LIMIT_WEEKLY ? parseInt(process.env.NEOAGENT_RATE_LIMIT_WEEKLY, 10) : null,
676
+ });
677
+ });
678
+
679
+ router.put('/api/config/rate-limits', requireAdminAuth, express.json(), (req, res) => {
680
+ const { rate_limit_4h, rate_limit_weekly } = req.body || {};
681
+ const parse = (v) => (v !== null && v !== undefined && v !== '' ? parseInt(v, 10) : null);
682
+ const v4h = parse(rate_limit_4h);
683
+ const vWeekly = parse(rate_limit_weekly);
684
+ upsertEnvValue(ENV_FILE, 'NEOAGENT_RATE_LIMIT_4H', v4h !== null ? String(v4h) : '');
685
+ upsertEnvValue(ENV_FILE, 'NEOAGENT_RATE_LIMIT_WEEKLY', vWeekly !== null ? String(vWeekly) : '');
686
+ process.env.NEOAGENT_RATE_LIMIT_4H = v4h !== null ? String(v4h) : '';
687
+ process.env.NEOAGENT_RATE_LIMIT_WEEKLY = vWeekly !== null ? String(vWeekly) : '';
688
+ res.json({ ok: true });
689
+ });
690
+
621
691
  // --- Models ---
622
692
 
623
693
  router.get('/api/models', requireAdminAuth, async (req, res) => {
624
694
  const { getSupportedModels } = require('../services/ai/models');
625
695
  try {
626
696
  const models = await getSupportedModels(null, null);
627
- const enabledModelsStr = process.env.NEOAGENT_ENABLED_MODELS || '';
628
- const enabledModelsList = enabledModelsStr ? enabledModelsStr.split(',').map(s => s.trim()).filter(s => s.length > 0) : [];
629
- res.json({ models, enabledModels: enabledModelsList });
697
+ const disabledStr = process.env.NEOAGENT_DISABLED_MODELS || '';
698
+ const disabledModels = disabledStr ? disabledStr.split(',').map(s => s.trim()).filter(Boolean) : [];
699
+ res.json({ models, disabledModels });
630
700
  } catch (err) {
631
701
  res.status(500).json({ error: String(err.message || err) });
632
702
  }
633
703
  });
634
704
 
635
- router.put('/api/models/enabled', requireAdminAuth, express.json(), (req, res) => {
636
- const { enabledModels } = req.body || {};
637
- if (!Array.isArray(enabledModels)) return res.status(400).json({ error: 'enabledModels must be an array' });
638
- const value = enabledModels.join(',');
639
- upsertEnvValue(ENV_FILE, 'NEOAGENT_ENABLED_MODELS', value);
640
- process.env.NEOAGENT_ENABLED_MODELS = value;
705
+ router.put('/api/models/config', requireAdminAuth, express.json(), (req, res) => {
706
+ const { disabledModels } = req.body || {};
707
+ if (!Array.isArray(disabledModels)) return res.status(400).json({ error: 'disabledModels must be an array' });
708
+ const value = disabledModels.join(',');
709
+ upsertEnvValue(ENV_FILE, 'NEOAGENT_DISABLED_MODELS', value);
710
+ process.env.NEOAGENT_DISABLED_MODELS = value;
641
711
  res.json({ ok: true });
642
712
  });
643
713
 
714
+ router.get('/api/users/:id/rate-limits', requireAdminAuth, (req, res) => {
715
+ const db = require('../db/database');
716
+ const { id } = req.params;
717
+ try {
718
+ const row = db.prepare('SELECT rate_limit_4h, rate_limit_weekly FROM users WHERE id = ?').get(id);
719
+ if (!row) return res.status(404).json({ error: 'User not found' });
720
+ res.json({ limits: row });
721
+ } catch (err) {
722
+ res.status(500).json({ error: String(err.message || err) });
723
+ }
724
+ });
725
+
644
726
  router.put('/api/users/:id/rate-limits', requireAdminAuth, express.json(), (req, res) => {
645
727
  const db = require('../db/database');
646
728
  const { id } = req.params;
@@ -1,14 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const express = require('express');
4
- const { getAnalyticsConfig } = require('../config/analytics');
5
4
 
6
5
  const router = express.Router();
7
6
 
8
7
  router.get('/config', (req, res) => {
9
- res.json({
10
- analytics: getAnalyticsConfig(),
11
- });
8
+ res.json({});
12
9
  });
13
10
 
14
11
  module.exports = router;
@@ -1215,18 +1215,20 @@ class AgentEngine {
1215
1215
  const aiSettings = getAiSettings(userId, agentId);
1216
1216
 
1217
1217
  const userLimits = db.prepare('SELECT rate_limit_4h, rate_limit_weekly FROM users WHERE id = ?').get(userId);
1218
- if (userLimits) {
1219
- if (userLimits.rate_limit_4h) {
1220
- const h4Tokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-4 hours')").get(userId).t;
1221
- if (h4Tokens >= userLimits.rate_limit_4h) {
1222
- throw new Error(`Rate limit exceeded: You have used ${h4Tokens} tokens in the last 4 hours (limit: ${userLimits.rate_limit_4h}).`);
1223
- }
1218
+ const globalLimit4h = process.env.NEOAGENT_RATE_LIMIT_4H ? parseInt(process.env.NEOAGENT_RATE_LIMIT_4H, 10) : null;
1219
+ const globalLimitWeekly = process.env.NEOAGENT_RATE_LIMIT_WEEKLY ? parseInt(process.env.NEOAGENT_RATE_LIMIT_WEEKLY, 10) : null;
1220
+ const effective4h = userLimits?.rate_limit_4h ?? globalLimit4h;
1221
+ const effectiveWeekly = userLimits?.rate_limit_weekly ?? globalLimitWeekly;
1222
+ if (effective4h) {
1223
+ const h4Tokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-4 hours')").get(userId).t;
1224
+ if (h4Tokens >= effective4h) {
1225
+ throw new Error(`Rate limit exceeded: You have used ${h4Tokens} tokens in the last 4 hours (limit: ${effective4h}).`);
1224
1226
  }
1225
- if (userLimits.rate_limit_weekly) {
1226
- const weeklyTokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-7 days')").get(userId).t;
1227
- if (weeklyTokens >= userLimits.rate_limit_weekly) {
1228
- throw new Error(`Rate limit exceeded: You have used ${weeklyTokens} tokens in the last 7 days (limit: ${userLimits.rate_limit_weekly}).`);
1229
- }
1227
+ }
1228
+ if (effectiveWeekly) {
1229
+ const weeklyTokens = db.prepare("SELECT COALESCE(SUM(total_tokens), 0) as t FROM agent_runs WHERE user_id = ? AND created_at > datetime('now', '-7 days')").get(userId).t;
1230
+ if (weeklyTokens >= effectiveWeekly) {
1231
+ throw new Error(`Rate limit exceeded: You have used ${weeklyTokens} tokens in the last 7 days (limit: ${effectiveWeekly}).`);
1230
1232
  }
1231
1233
  }
1232
1234
 
@@ -430,8 +430,8 @@ async function getSupportedModels(userId, agentId = null) {
430
430
  }
431
431
  }
432
432
 
433
- const globalEnabledStr = process.env.NEOAGENT_ENABLED_MODELS || '';
434
- const globalEnabledSet = globalEnabledStr ? new Set(globalEnabledStr.split(',').map(s => s.trim()).filter(Boolean)) : null;
433
+ const globalDisabledStr = process.env.NEOAGENT_DISABLED_MODELS || '';
434
+ const globalDisabledSet = globalDisabledStr ? new Set(globalDisabledStr.split(',').map(s => s.trim()).filter(Boolean)) : null;
435
435
 
436
436
  return all.map((model) => {
437
437
  const provider = providerById.get(model.provider);
@@ -440,15 +440,21 @@ async function getSupportedModels(userId, agentId = null) {
440
440
  const priceTier = model.provider === 'ollama'
441
441
  ? 'free'
442
442
  : (model.priceTier ?? classifyPriceTier(model.id));
443
-
443
+
444
444
  let available = provider?.available !== false;
445
- if (globalEnabledSet && !globalEnabledSet.has(model.id)) {
445
+ if (available && globalDisabledSet?.has(model.id)) {
446
446
  available = false;
447
447
  }
448
448
 
449
+ const bareId = model.id.includes('/') ? model.id.slice(model.id.indexOf('/') + 1) : null;
450
+ const inputCostPerM = model.provider === 'ollama'
451
+ ? 0
452
+ : (openrouterPricingCache.get(model.id) ?? (bareId ? openrouterPricingCache.get(bareId) : undefined) ?? null);
453
+
449
454
  return {
450
455
  ...model,
451
456
  priceTier,
457
+ inputCostPerM,
452
458
  available,
453
459
  providerStatus: provider?.status || 'unknown',
454
460
  providerStatusLabel: provider?.statusLabel || 'Unknown'
@@ -1,294 +0,0 @@
1
- import 'package:mixpanel_flutter/mixpanel_flutter.dart';
2
-
3
- class AppAnalytics {
4
- Mixpanel? _mixpanel;
5
- bool _initialized = false;
6
- bool _enabled = false;
7
- String? _currentToken;
8
- bool _consentGranted = false;
9
- final List<_QueuedEvent> _queue = <_QueuedEvent>[];
10
-
11
- bool get enabled => _enabled && _consentGranted;
12
- bool get consentGranted => _consentGranted;
13
- bool get isConfigured => (_currentToken ?? '').isNotEmpty;
14
-
15
- Future<void> initialize({
16
- required String? token,
17
- required bool consentGranted,
18
- }) async {
19
- final normalizedToken = token?.trim() ?? '';
20
- final shouldEnable = normalizedToken.isNotEmpty;
21
- if (_initialized &&
22
- normalizedToken == _currentToken &&
23
- shouldEnable == _enabled &&
24
- consentGranted == _consentGranted) {
25
- return;
26
- }
27
-
28
- _consentGranted = consentGranted;
29
- _initialized = false;
30
- _enabled = false;
31
- _currentToken = normalizedToken.isEmpty ? null : normalizedToken;
32
- _mixpanel = null;
33
-
34
- if (!shouldEnable) {
35
- _queue.clear();
36
- _initialized = true;
37
- return;
38
- }
39
-
40
- if (!consentGranted) {
41
- _queue.clear();
42
- _initialized = true;
43
- return;
44
- }
45
-
46
- try {
47
- _mixpanel = await Mixpanel.init(
48
- normalizedToken,
49
- trackAutomaticEvents: false,
50
- );
51
- _enabled = true;
52
- _initialized = true;
53
- await _flushQueue();
54
- } catch (_) {
55
- _queue.clear();
56
- _mixpanel = null;
57
- _enabled = false;
58
- _initialized = true;
59
- }
60
- }
61
-
62
- Future<void> setConsentGranted(bool consentGranted) async {
63
- if (_consentGranted == consentGranted && _initialized) {
64
- return;
65
- }
66
-
67
- _consentGranted = consentGranted;
68
- if (!consentGranted) {
69
- _mixpanel = null;
70
- _enabled = false;
71
- _initialized = true;
72
- _queue.clear();
73
- return;
74
- }
75
-
76
- await initialize(token: _currentToken, consentGranted: true);
77
- }
78
-
79
- Future<void> track(
80
- String eventName, {
81
- Map<String, Object?> properties = const <String, Object?>{},
82
- }) async {
83
- final event = _QueuedEvent(
84
- eventName: eventName,
85
- properties: _cleanProperties(properties),
86
- );
87
-
88
- if (!_initialized) {
89
- _queue.add(event);
90
- return;
91
- }
92
-
93
- if (!enabled || _mixpanel == null) {
94
- return;
95
- }
96
-
97
- await _sendEvent(event);
98
- }
99
-
100
- Future<void> trackAppOpened({
101
- required String appMode,
102
- required String platform,
103
- required String backendMode,
104
- required String selectedSection,
105
- required String deploymentProfile,
106
- required bool authenticated,
107
- }) {
108
- return track(
109
- 'app_opened',
110
- properties: <String, Object?>{
111
- 'app_mode': appMode,
112
- 'platform': platform,
113
- 'backend_mode': backendMode,
114
- 'selected_section': selectedSection,
115
- 'deployment_profile': deploymentProfile,
116
- 'authenticated': authenticated,
117
- },
118
- );
119
- }
120
-
121
- Future<void> trackBackendUrlSaved({
122
- required String backendMode,
123
- }) {
124
- return track(
125
- 'backend_url_saved',
126
- properties: <String, Object?>{
127
- 'backend_mode': backendMode,
128
- },
129
- );
130
- }
131
-
132
- Future<void> trackSectionChanged({
133
- required String section,
134
- required String previousSection,
135
- }) {
136
- return track(
137
- 'section_changed',
138
- properties: <String, Object?>{
139
- 'section': section,
140
- 'previous_section': previousSection,
141
- },
142
- );
143
- }
144
-
145
- Future<void> trackChatMessageSent({
146
- required int length,
147
- required bool steeringLiveRun,
148
- }) {
149
- return track(
150
- 'chat_message_sent',
151
- properties: <String, Object?>{
152
- 'length': length,
153
- 'steering_live_run': steeringLiveRun,
154
- },
155
- );
156
- }
157
-
158
- Future<void> trackRecordingStarted({
159
- required String kind,
160
- }) {
161
- return track(
162
- 'recording_started',
163
- properties: <String, Object?>{
164
- 'kind': kind,
165
- },
166
- );
167
- }
168
-
169
- Future<void> trackRecordingStopped({
170
- required String kind,
171
- required String stopReason,
172
- }) {
173
- return track(
174
- 'recording_stopped',
175
- properties: <String, Object?>{
176
- 'kind': kind,
177
- 'stop_reason': stopReason,
178
- },
179
- );
180
- }
181
-
182
- Future<void> trackAppUpdateCheck({
183
- required bool silent,
184
- }) {
185
- return track(
186
- 'app_update_check',
187
- properties: <String, Object?>{
188
- 'silent': silent,
189
- },
190
- );
191
- }
192
-
193
- Future<void> trackTaskRunRequested({
194
- required int taskId,
195
- }) {
196
- return track(
197
- 'task_run_requested',
198
- properties: <String, Object?>{
199
- 'task_id': taskId,
200
- },
201
- );
202
- }
203
-
204
- Future<void> trackWidgetRefreshRequested({
205
- required bool all,
206
- }) {
207
- return track(
208
- 'widget_refresh_requested',
209
- properties: <String, Object?>{
210
- 'all': all,
211
- },
212
- );
213
- }
214
-
215
- Future<void> trackAppUpdateTriggered() {
216
- return track('app_update_triggered');
217
- }
218
-
219
- Future<void> trackSignedIn({
220
- required String authMethod,
221
- required bool isRegistration,
222
- }) {
223
- return track(
224
- 'signed_in',
225
- properties: <String, Object?>{
226
- 'auth_method': authMethod,
227
- 'is_registration': isRegistration,
228
- },
229
- );
230
- }
231
-
232
- Future<void> trackSignedOut() {
233
- return track('signed_out');
234
- }
235
-
236
- Future<void> trackOnboardingDismissed() {
237
- return track('onboarding_dismissed');
238
- }
239
-
240
- Future<void> dispose() async {
241
- try {
242
- await _flushQueue();
243
- } catch (_) {}
244
- _queue.clear();
245
- _mixpanel = null;
246
- _initialized = false;
247
- _enabled = false;
248
- _currentToken = null;
249
- _consentGranted = false;
250
- }
251
-
252
- Future<void> _flushQueue() async {
253
- if (!enabled || _mixpanel == null || _queue.isEmpty) {
254
- return;
255
- }
256
-
257
- final pending = List<_QueuedEvent>.from(_queue);
258
- _queue.clear();
259
- for (final event in pending) {
260
- await _sendEvent(event);
261
- }
262
- }
263
-
264
- Future<void> _sendEvent(_QueuedEvent event) async {
265
- final mixpanel = _mixpanel;
266
- if (mixpanel == null) {
267
- return;
268
- }
269
-
270
- try {
271
- await mixpanel.track(event.eventName, properties: event.properties);
272
- } catch (_) {}
273
- }
274
-
275
- Map<String, Object?> _cleanProperties(Map<String, Object?> properties) {
276
- final cleaned = <String, Object?>{};
277
- for (final entry in properties.entries) {
278
- final value = entry.value;
279
- if (value == null) continue;
280
- cleaned[entry.key] = value;
281
- }
282
- return cleaned;
283
- }
284
- }
285
-
286
- class _QueuedEvent {
287
- const _QueuedEvent({
288
- required this.eventName,
289
- required this.properties,
290
- });
291
-
292
- final String eventName;
293
- final Map<String, Object?> properties;
294
- }
@@ -1,30 +0,0 @@
1
- 'use strict';
2
-
3
- const DEFAULT_MIXPANEL_TOKEN = '4a47ae6a05cf39a8faf0438a1200dde6';
4
-
5
- function normalizeToken(value) {
6
- return String(value || '').trim();
7
- }
8
-
9
- function resolveMixpanelToken() {
10
- if (Object.prototype.hasOwnProperty.call(process.env, 'NEOAGENT_MIXPANEL_TOKEN')) {
11
- return normalizeToken(process.env.NEOAGENT_MIXPANEL_TOKEN);
12
- }
13
- return normalizeToken(DEFAULT_MIXPANEL_TOKEN);
14
- }
15
-
16
- function getAnalyticsConfig() {
17
- const mixpanelToken = resolveMixpanelToken();
18
- return {
19
- mixpanel: {
20
- enabled: mixpanelToken.length > 0,
21
- token: mixpanelToken || null,
22
- },
23
- };
24
- }
25
-
26
- module.exports = {
27
- DEFAULT_MIXPANEL_TOKEN,
28
- getAnalyticsConfig,
29
- resolveMixpanelToken,
30
- };