neoagent 2.4.2-beta.4 → 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.
- package/.env.example +0 -9
- package/docs/configuration.md +0 -10
- package/flutter_app/lib/main.dart +0 -1
- package/flutter_app/lib/main_controller.dart +0 -160
- package/flutter_app/lib/main_operations.dart +17 -1
- package/flutter_app/lib/main_runtime.dart +0 -22
- package/flutter_app/lib/main_shared.dart +0 -161
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +0 -2
- package/flutter_app/pubspec.lock +0 -8
- package/flutter_app/pubspec.yaml +0 -1
- package/package.json +1 -1
- package/server/admin/admin.css +23 -0
- package/server/admin/admin.js +17 -19
- package/server/admin/analytics.js +254 -48
- package/server/admin/index.html +9 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +0 -183
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +77809 -78575
- package/server/routes/admin.js +73 -24
- package/server/routes/runtime.js +1 -4
- package/server/services/ai/models.js +4 -4
- package/flutter_app/lib/src/analytics_service.dart +0 -294
- package/server/config/analytics.js +0 -30
package/server/routes/admin.js
CHANGED
|
@@ -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:
|
|
425
|
-
activeToday:
|
|
426
|
-
newThisWeek:
|
|
427
|
-
totalRuns:
|
|
428
|
-
runsToday:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
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
|
|
504
|
+
ORDER BY r.created_at DESC LIMIT 25
|
|
457
505
|
`).all();
|
|
458
|
-
|
|
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
|
}
|
|
@@ -645,20 +694,20 @@ router.get('/api/models', requireAdminAuth, async (req, res) => {
|
|
|
645
694
|
const { getSupportedModels } = require('../services/ai/models');
|
|
646
695
|
try {
|
|
647
696
|
const models = await getSupportedModels(null, null);
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
res.json({ models,
|
|
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 });
|
|
651
700
|
} catch (err) {
|
|
652
701
|
res.status(500).json({ error: String(err.message || err) });
|
|
653
702
|
}
|
|
654
703
|
});
|
|
655
704
|
|
|
656
|
-
router.put('/api/models/
|
|
657
|
-
const {
|
|
658
|
-
if (!Array.isArray(
|
|
659
|
-
const value =
|
|
660
|
-
upsertEnvValue(ENV_FILE, '
|
|
661
|
-
process.env.
|
|
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;
|
|
662
711
|
res.json({ ok: true });
|
|
663
712
|
});
|
|
664
713
|
|
package/server/routes/runtime.js
CHANGED
|
@@ -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;
|
|
@@ -430,8 +430,8 @@ async function getSupportedModels(userId, agentId = null) {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
const
|
|
434
|
-
const
|
|
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,9 +440,9 @@ 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 (
|
|
445
|
+
if (available && globalDisabledSet?.has(model.id)) {
|
|
446
446
|
available = false;
|
|
447
447
|
}
|
|
448
448
|
|
|
@@ -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
|
-
};
|