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.
- package/.env.example +0 -9
- package/docs/configuration.md +0 -10
- package/flutter_app/lib/main.dart +0 -1
- package/flutter_app/lib/main_account_settings.dart +57 -16
- package/flutter_app/lib/main_controller.dart +0 -160
- package/flutter_app/lib/main_models.dart +6 -0
- 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/landing/index.html +1 -1
- package/package.json +1 -1
- package/server/admin/admin.css +23 -0
- package/server/admin/admin.js +89 -32
- package/server/admin/analytics.js +254 -48
- package/server/admin/index.html +36 -2
- package/server/admin/users.js +94 -37
- 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 +77607 -78350
- package/server/routes/account.js +11 -2
- package/server/routes/admin.js +106 -24
- package/server/routes/runtime.js +1 -4
- package/server/services/ai/engine.js +13 -11
- package/server/services/ai/models.js +10 -4
- package/flutter_app/lib/src/analytics_service.dart +0 -294
- package/server/config/analytics.js +0 -30
|
@@ -2393,167 +2393,6 @@ class _GlobalWebUpdateBanner extends StatelessWidget {
|
|
|
2393
2393
|
}
|
|
2394
2394
|
}
|
|
2395
2395
|
|
|
2396
|
-
class _GlobalAnalyticsConsentBanner extends StatelessWidget {
|
|
2397
|
-
const _GlobalAnalyticsConsentBanner({required this.controller});
|
|
2398
|
-
|
|
2399
|
-
final NeoAgentController controller;
|
|
2400
|
-
|
|
2401
|
-
@override
|
|
2402
|
-
Widget build(BuildContext context) {
|
|
2403
|
-
return LayoutBuilder(
|
|
2404
|
-
builder: (context, constraints) {
|
|
2405
|
-
final compact = constraints.maxWidth < 700;
|
|
2406
|
-
final body = Text(
|
|
2407
|
-
'NeoAgent uses anonymous analytics to understand startup, navigation, and setup flows. No messages, credentials, or personal content are collected.',
|
|
2408
|
-
style: TextStyle(color: _textSecondary, height: 1.35),
|
|
2409
|
-
);
|
|
2410
|
-
return Material(
|
|
2411
|
-
color: Colors.transparent,
|
|
2412
|
-
child: _GlassSurface(
|
|
2413
|
-
borderRadius: BorderRadius.circular(22),
|
|
2414
|
-
blurSigma: 26,
|
|
2415
|
-
fillColor: _bgCard.withValues(alpha: 0.94),
|
|
2416
|
-
borderColor: _accent.withValues(alpha: 0.18),
|
|
2417
|
-
overlayGradient: LinearGradient(
|
|
2418
|
-
colors: <Color>[
|
|
2419
|
-
_accent.withValues(alpha: 0.12),
|
|
2420
|
-
_bgCard.withValues(alpha: 0.88),
|
|
2421
|
-
_bgSecondary.withValues(alpha: 0.86),
|
|
2422
|
-
],
|
|
2423
|
-
stops: const <double>[0, 0.22, 1],
|
|
2424
|
-
begin: const Alignment(-1, -1),
|
|
2425
|
-
end: const Alignment(1, 1),
|
|
2426
|
-
),
|
|
2427
|
-
boxShadow: <BoxShadow>[
|
|
2428
|
-
BoxShadow(
|
|
2429
|
-
color: Colors.black.withValues(alpha: 0.22),
|
|
2430
|
-
blurRadius: 34,
|
|
2431
|
-
offset: const Offset(0, 18),
|
|
2432
|
-
),
|
|
2433
|
-
BoxShadow(
|
|
2434
|
-
color: _accent.withValues(alpha: 0.1),
|
|
2435
|
-
blurRadius: 24,
|
|
2436
|
-
offset: const Offset(0, 8),
|
|
2437
|
-
),
|
|
2438
|
-
],
|
|
2439
|
-
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
|
|
2440
|
-
child: compact
|
|
2441
|
-
? Column(
|
|
2442
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2443
|
-
children: <Widget>[
|
|
2444
|
-
Row(
|
|
2445
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2446
|
-
children: <Widget>[
|
|
2447
|
-
Container(
|
|
2448
|
-
padding: const EdgeInsets.all(10),
|
|
2449
|
-
decoration: BoxDecoration(
|
|
2450
|
-
color: _accent.withValues(alpha: 0.16),
|
|
2451
|
-
shape: BoxShape.circle,
|
|
2452
|
-
border: Border.all(
|
|
2453
|
-
color: _accent.withValues(alpha: 0.24),
|
|
2454
|
-
),
|
|
2455
|
-
),
|
|
2456
|
-
child: Icon(
|
|
2457
|
-
Icons.cookie_outlined,
|
|
2458
|
-
color: _accent,
|
|
2459
|
-
size: 22,
|
|
2460
|
-
),
|
|
2461
|
-
),
|
|
2462
|
-
const SizedBox(width: 12),
|
|
2463
|
-
Expanded(
|
|
2464
|
-
child: Column(
|
|
2465
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2466
|
-
children: <Widget>[
|
|
2467
|
-
Text(
|
|
2468
|
-
'Analytics cookies',
|
|
2469
|
-
style: TextStyle(
|
|
2470
|
-
color: _textPrimary,
|
|
2471
|
-
fontSize: 15,
|
|
2472
|
-
fontWeight: FontWeight.w700,
|
|
2473
|
-
),
|
|
2474
|
-
),
|
|
2475
|
-
const SizedBox(height: 6),
|
|
2476
|
-
body,
|
|
2477
|
-
],
|
|
2478
|
-
),
|
|
2479
|
-
),
|
|
2480
|
-
],
|
|
2481
|
-
),
|
|
2482
|
-
const SizedBox(height: 14),
|
|
2483
|
-
Row(
|
|
2484
|
-
children: <Widget>[
|
|
2485
|
-
Expanded(
|
|
2486
|
-
child: OutlinedButton(
|
|
2487
|
-
onPressed: controller.declineAnalyticsConsent,
|
|
2488
|
-
child: const Text('Decline'),
|
|
2489
|
-
),
|
|
2490
|
-
),
|
|
2491
|
-
const SizedBox(width: 10),
|
|
2492
|
-
Expanded(
|
|
2493
|
-
child: FilledButton(
|
|
2494
|
-
onPressed: controller.acceptAnalyticsConsent,
|
|
2495
|
-
child: const Text('Allow analytics'),
|
|
2496
|
-
),
|
|
2497
|
-
),
|
|
2498
|
-
],
|
|
2499
|
-
),
|
|
2500
|
-
],
|
|
2501
|
-
)
|
|
2502
|
-
: Row(
|
|
2503
|
-
crossAxisAlignment: CrossAxisAlignment.center,
|
|
2504
|
-
children: <Widget>[
|
|
2505
|
-
Container(
|
|
2506
|
-
padding: const EdgeInsets.all(10),
|
|
2507
|
-
decoration: BoxDecoration(
|
|
2508
|
-
color: _accent.withValues(alpha: 0.16),
|
|
2509
|
-
shape: BoxShape.circle,
|
|
2510
|
-
border: Border.all(
|
|
2511
|
-
color: _accent.withValues(alpha: 0.24),
|
|
2512
|
-
),
|
|
2513
|
-
),
|
|
2514
|
-
child: Icon(
|
|
2515
|
-
Icons.cookie_outlined,
|
|
2516
|
-
color: _accent,
|
|
2517
|
-
size: 22,
|
|
2518
|
-
),
|
|
2519
|
-
),
|
|
2520
|
-
const SizedBox(width: 14),
|
|
2521
|
-
Expanded(
|
|
2522
|
-
child: Column(
|
|
2523
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2524
|
-
children: <Widget>[
|
|
2525
|
-
Text(
|
|
2526
|
-
'Analytics cookies',
|
|
2527
|
-
style: TextStyle(
|
|
2528
|
-
color: _textPrimary,
|
|
2529
|
-
fontSize: 15,
|
|
2530
|
-
fontWeight: FontWeight.w700,
|
|
2531
|
-
),
|
|
2532
|
-
),
|
|
2533
|
-
const SizedBox(height: 6),
|
|
2534
|
-
body,
|
|
2535
|
-
],
|
|
2536
|
-
),
|
|
2537
|
-
),
|
|
2538
|
-
const SizedBox(width: 16),
|
|
2539
|
-
OutlinedButton(
|
|
2540
|
-
onPressed: controller.declineAnalyticsConsent,
|
|
2541
|
-
child: const Text('Decline'),
|
|
2542
|
-
),
|
|
2543
|
-
const SizedBox(width: 10),
|
|
2544
|
-
FilledButton(
|
|
2545
|
-
onPressed: controller.acceptAnalyticsConsent,
|
|
2546
|
-
child: const Text('Allow analytics'),
|
|
2547
|
-
),
|
|
2548
|
-
],
|
|
2549
|
-
),
|
|
2550
|
-
),
|
|
2551
|
-
);
|
|
2552
|
-
},
|
|
2553
|
-
);
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
2396
|
class _DesktopCloseDecision {
|
|
2558
2397
|
const _DesktopCloseDecision({
|
|
2559
2398
|
required this.keepRunning,
|
|
@@ -12,7 +12,6 @@ import file_picker
|
|
|
12
12
|
import flutter_secure_storage_macos
|
|
13
13
|
import geolocator_apple
|
|
14
14
|
import hotkey_manager_macos
|
|
15
|
-
import mixpanel_flutter
|
|
16
15
|
import mobile_scanner
|
|
17
16
|
import package_info_plus
|
|
18
17
|
import path_provider_foundation
|
|
@@ -33,7 +32,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|
|
33
32
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
|
34
33
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
|
35
34
|
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
|
36
|
-
MixpanelFlutterPlugin.register(with: registry.registrar(forPlugin: "MixpanelFlutterPlugin"))
|
|
37
35
|
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
|
38
36
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
|
39
37
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
package/flutter_app/pubspec.lock
CHANGED
|
@@ -598,14 +598,6 @@ packages:
|
|
|
598
598
|
url: "https://pub.dev"
|
|
599
599
|
source: hosted
|
|
600
600
|
version: "1.18.0"
|
|
601
|
-
mixpanel_flutter:
|
|
602
|
-
dependency: "direct main"
|
|
603
|
-
description:
|
|
604
|
-
name: mixpanel_flutter
|
|
605
|
-
sha256: "6962b8ce61eb6202d6545bee648e21a7bc9f7e804fa293912e9673329f499ece"
|
|
606
|
-
url: "https://pub.dev"
|
|
607
|
-
source: hosted
|
|
608
|
-
version: "2.7.0"
|
|
609
601
|
mobile_scanner:
|
|
610
602
|
dependency: "direct main"
|
|
611
603
|
description:
|
package/flutter_app/pubspec.yaml
CHANGED
package/landing/index.html
CHANGED
|
@@ -1015,7 +1015,7 @@ p { margin: 0; text-wrap: pretty; }
|
|
|
1015
1015
|
<div class="frame">
|
|
1016
1016
|
<div class="frame-bar">
|
|
1017
1017
|
<span class="dot" style="background:#e26e61"></span><span class="dot" style="background:#e0b24a"></span><span class="dot" style="background:#4bbf85"></span>
|
|
1018
|
-
<span class="url">
|
|
1018
|
+
<span class="url">control surface</span>
|
|
1019
1019
|
</div>
|
|
1020
1020
|
<div class="frame-body shot-pair">
|
|
1021
1021
|
<img class="lm" src="images/dashboard-light.png" alt="NeoAgent dashboard" loading="eager">
|
package/package.json
CHANGED
package/server/admin/admin.css
CHANGED
|
@@ -185,6 +185,29 @@ button:disabled,
|
|
|
185
185
|
border: 1px solid rgba(222, 138, 120, 0.20);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
.range-btn {
|
|
189
|
+
padding: 5px 12px;
|
|
190
|
+
font-size: 12px;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
border: 1px solid var(--border);
|
|
193
|
+
border-radius: 6px;
|
|
194
|
+
background: transparent;
|
|
195
|
+
color: var(--text-muted);
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.range-btn:hover {
|
|
201
|
+
background: rgba(224, 240, 224, 0.07);
|
|
202
|
+
color: var(--text);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.range-btn.active {
|
|
206
|
+
background: var(--accent-muted);
|
|
207
|
+
color: var(--accent);
|
|
208
|
+
border-color: var(--accent);
|
|
209
|
+
}
|
|
210
|
+
|
|
188
211
|
.btn-danger:hover:not(:disabled) {
|
|
189
212
|
background: rgba(222, 138, 120, 0.20);
|
|
190
213
|
}
|
package/server/admin/admin.js
CHANGED
|
@@ -328,50 +328,88 @@ async function clearProvider(key, btn) {
|
|
|
328
328
|
|
|
329
329
|
// ── Models ─────────────────────────────────────────────────────────────────
|
|
330
330
|
|
|
331
|
+
function fmtModelPrice(inputCostPerM) {
|
|
332
|
+
if (inputCostPerM === null || inputCostPerM === undefined) return '—';
|
|
333
|
+
if (inputCostPerM === 0) return 'Free';
|
|
334
|
+
if (inputCostPerM < 0.01) return `$${inputCostPerM.toFixed(4)}/M`;
|
|
335
|
+
if (inputCostPerM < 1) return `$${inputCostPerM.toFixed(3)}/M`;
|
|
336
|
+
return `$${inputCostPerM.toFixed(2)}/M`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function priceTierClass(tier) {
|
|
340
|
+
if (tier === 'free') return 'badge-ok';
|
|
341
|
+
if (tier === 'cheap') return 'badge-ok';
|
|
342
|
+
if (tier === 'medium') return 'badge-warn';
|
|
343
|
+
if (tier === 'expensive') return 'badge-err';
|
|
344
|
+
return 'badge-idle';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function purposeIcon(purpose) {
|
|
348
|
+
const icons = { fast: '⚡', planning: '🧠', coding: '💻', general: '✦' };
|
|
349
|
+
return icons[purpose] || '✦';
|
|
350
|
+
}
|
|
351
|
+
|
|
331
352
|
async function loadModels() {
|
|
332
353
|
const el = document.getElementById('models-content');
|
|
333
354
|
if (!el) return;
|
|
334
355
|
try {
|
|
335
356
|
const data = await api('/admin/api/models').then((r) => r.json());
|
|
336
357
|
const models = data.models || [];
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
if (!models.length) { el.innerHTML = '<div class="empty">No models found
|
|
340
|
-
|
|
341
|
-
// Sort
|
|
358
|
+
const disabledSet = new Set(data.disabledModels || []);
|
|
359
|
+
|
|
360
|
+
if (!models.length) { el.innerHTML = '<div class="empty">No models found — configure providers first.</div>'; return; }
|
|
361
|
+
|
|
362
|
+
// Sort: provider alpha, then price low→high within provider
|
|
342
363
|
models.sort((a, b) => {
|
|
343
364
|
if (a.provider !== b.provider) return a.provider.localeCompare(b.provider);
|
|
344
|
-
|
|
365
|
+
const ac = a.inputCostPerM ?? Infinity;
|
|
366
|
+
const bc = b.inputCostPerM ?? Infinity;
|
|
367
|
+
return ac - bc;
|
|
345
368
|
});
|
|
346
|
-
|
|
347
|
-
|
|
369
|
+
|
|
370
|
+
const enabledCount = models.length - disabledSet.size;
|
|
371
|
+
|
|
372
|
+
let html = `
|
|
373
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;">
|
|
374
|
+
<button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(true)">Enable All</button>
|
|
375
|
+
<button class="btn btn-ghost" style="padding:5px 12px;font-size:12px;" onclick="toggleAllModels(false)">Disable All</button>
|
|
376
|
+
<span style="margin-left:4px;font-size:12px;color:var(--text-muted);" id="model-count-label">${enabledCount} of ${models.length} enabled</span>
|
|
377
|
+
</div>
|
|
378
|
+
<table class="users-table">
|
|
348
379
|
<thead><tr>
|
|
349
|
-
<th style="width:40px;">
|
|
350
|
-
<th>Model
|
|
380
|
+
<th style="width:40px;">On</th>
|
|
381
|
+
<th>Model</th>
|
|
351
382
|
<th>Provider</th>
|
|
352
|
-
<th>Purpose</th>
|
|
353
|
-
<th>
|
|
383
|
+
<th style="width:90px;">Purpose</th>
|
|
384
|
+
<th style="width:80px;">Tier</th>
|
|
385
|
+
<th style="width:110px;text-align:right;">Input / 1M tokens</th>
|
|
354
386
|
</tr></thead>
|
|
355
387
|
<tbody>`;
|
|
356
|
-
|
|
388
|
+
|
|
357
389
|
for (const m of models) {
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
const
|
|
390
|
+
const isEnabled = !disabledSet.has(m.id);
|
|
391
|
+
const priceStr = fmtModelPrice(m.inputCostPerM);
|
|
392
|
+
const tierCls = priceTierClass(m.priceTier);
|
|
393
|
+
const icon = purposeIcon(m.purpose);
|
|
361
394
|
html += `
|
|
362
|
-
<tr style="opacity:
|
|
395
|
+
<tr style="opacity:${isEnabled ? '1' : '0.45'}">
|
|
363
396
|
<td style="text-align:center;">
|
|
364
|
-
<input type="checkbox" class="model-cb" value="${esc(m.id)}" ${
|
|
397
|
+
<input type="checkbox" class="model-cb" value="${esc(m.id)}" ${isEnabled ? 'checked' : ''}
|
|
398
|
+
onchange="onModelToggle(this)">
|
|
399
|
+
</td>
|
|
400
|
+
<td>
|
|
401
|
+
<div style="font-weight:600;color:var(--text);font-size:13px;">${esc(m.label)}</div>
|
|
402
|
+
<div style="font-size:11px;color:var(--text-muted);font-family:var(--font-mono);margin-top:2px;">${esc(m.id)}</div>
|
|
365
403
|
</td>
|
|
366
|
-
<td style="font-
|
|
367
|
-
<td>${esc(m.
|
|
368
|
-
<td><span class="badge
|
|
369
|
-
<td
|
|
404
|
+
<td style="font-size:13px;text-transform:capitalize;">${esc(m.provider)}</td>
|
|
405
|
+
<td><span class="badge badge-idle">${icon} ${esc(m.purpose)}</span></td>
|
|
406
|
+
<td><span class="badge ${tierCls}">${esc(m.priceTier ?? '?')}</span></td>
|
|
407
|
+
<td style="text-align:right;font-family:var(--font-mono);font-size:13px;font-weight:600;color:var(--text);">${priceStr}</td>
|
|
370
408
|
</tr>
|
|
371
409
|
`;
|
|
372
410
|
}
|
|
373
|
-
|
|
374
|
-
html +=
|
|
411
|
+
|
|
412
|
+
html += `</tbody></table>`;
|
|
375
413
|
el.innerHTML = html;
|
|
376
414
|
} catch (err) {
|
|
377
415
|
if (err.message !== 'unauthorized') {
|
|
@@ -380,23 +418,42 @@ async function loadModels() {
|
|
|
380
418
|
}
|
|
381
419
|
}
|
|
382
420
|
|
|
421
|
+
function onModelToggle(cb) {
|
|
422
|
+
const row = cb.closest('tr');
|
|
423
|
+
if (row) row.style.opacity = cb.checked ? '1' : '0.45';
|
|
424
|
+
const all = document.querySelectorAll('.model-cb');
|
|
425
|
+
const checked = Array.from(all).filter(c => c.checked).length;
|
|
426
|
+
const label = document.getElementById('model-count-label');
|
|
427
|
+
if (label) label.textContent = `${checked} of ${all.length} enabled`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function toggleAllModels(enable) {
|
|
431
|
+
document.querySelectorAll('.model-cb').forEach(cb => {
|
|
432
|
+
cb.checked = enable;
|
|
433
|
+
const row = cb.closest('tr');
|
|
434
|
+
if (row) row.style.opacity = enable ? '1' : '0.45';
|
|
435
|
+
});
|
|
436
|
+
const all = document.querySelectorAll('.model-cb');
|
|
437
|
+
const label = document.getElementById('model-count-label');
|
|
438
|
+
if (label) label.textContent = `${enable ? all.length : 0} of ${all.length} enabled`;
|
|
439
|
+
}
|
|
440
|
+
|
|
383
441
|
async function saveEnabledModels(btn) {
|
|
384
442
|
const cbs = document.querySelectorAll('.model-cb');
|
|
385
443
|
if (!cbs.length) return;
|
|
386
|
-
|
|
387
|
-
//
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
444
|
+
|
|
445
|
+
// Persist only the disabled (unchecked) models
|
|
446
|
+
const disabledModels = Array.from(cbs).filter(cb => !cb.checked).map(cb => cb.value);
|
|
447
|
+
|
|
391
448
|
btn.disabled = true;
|
|
392
449
|
const original = btn.textContent;
|
|
393
450
|
btn.textContent = 'Saving…';
|
|
394
|
-
|
|
451
|
+
|
|
395
452
|
try {
|
|
396
|
-
const res = await api('/admin/api/models/
|
|
453
|
+
const res = await api('/admin/api/models/config', {
|
|
397
454
|
method: 'PUT',
|
|
398
455
|
headers: { 'Content-Type': 'application/json' },
|
|
399
|
-
body: JSON.stringify({
|
|
456
|
+
body: JSON.stringify({ disabledModels }),
|
|
400
457
|
});
|
|
401
458
|
if (!res.ok) {
|
|
402
459
|
const body = await res.json().catch(() => ({}));
|