neoagent 2.4.1-beta.9 → 2.4.1
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 +33 -3
- package/LICENSE +111 -56
- package/README.md +8 -3
- package/docs/configuration.md +8 -0
- package/docs/getting-started.md +9 -3
- package/docs/index.md +4 -0
- package/extensions/chrome-browser/background.mjs +36 -4
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +39 -8
- package/extensions/chrome-browser/manifest.json +3 -3
- package/extensions/chrome-browser/popup.html +5 -1
- package/extensions/chrome-browser/popup.js +13 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
- package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
- package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +3 -0
- package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_account_settings.dart +10 -34
- package/flutter_app/lib/main_admin.dart +14 -1
- package/flutter_app/lib/main_app_shell.dart +377 -340
- package/flutter_app/lib/main_chat.dart +707 -227
- package/flutter_app/lib/main_controller.dart +197 -42
- package/flutter_app/lib/main_devices.dart +436 -4
- package/flutter_app/lib/main_integrations.dart +255 -6
- package/flutter_app/lib/main_launcher.dart +1 -1
- package/flutter_app/lib/main_model_picker.dart +685 -0
- package/flutter_app/lib/main_models.dart +212 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_operations.dart +1417 -614
- package/flutter_app/lib/main_settings.dart +673 -871
- package/flutter_app/lib/main_shared.dart +971 -443
- package/flutter_app/lib/main_spacing.dart +4 -4
- package/flutter_app/lib/main_theme.dart +22 -14
- package/flutter_app/lib/main_unified.dart +5 -72
- package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
- package/flutter_app/lib/src/backend_client.dart +46 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +30 -7
- package/flutter_app/lib/src/desktop_companion_io.dart +71 -1
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/stream_renderer.dart +42 -3
- package/flutter_app/lib/src/theme/palette.dart +76 -34
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/pubspec.lock +2 -2
- package/flutter_app/pubspec.yaml +7 -1
- package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
- package/flutter_app/tool/generate_desktop_branding.py +154 -152
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +40 -9
- package/flutter_app/web/favicon_light.svg +43 -0
- package/flutter_app/web/icons/Icon-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/windows/runner/main.cpp +7 -1
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/lib/manager.js +445 -83
- package/package.json +17 -3
- package/runtime/paths.js +3 -1
- package/server/admin/access.js +198 -0
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/analytics.js +128 -0
- package/server/admin/index.html +1015 -0
- package/server/admin/login.html +290 -0
- package/server/admin/logo.svg +43 -0
- package/server/admin/sql.js +134 -0
- package/server/admin/users.js +147 -0
- package/server/config/origins.js +3 -1
- package/server/db/database.js +92 -0
- package/server/db/ftsQuery.js +27 -0
- package/server/http/routes.js +1 -0
- package/server/http/static.js +23 -6
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +48 -0
- package/server/middleware/auth.js +1 -40
- package/server/public/.last_build_id +1 -1
- package/server/public/app_icon.png +0 -0
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_512.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
- package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
- package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
- package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/web/icons/Icon-192.png +0 -0
- package/server/public/favicon.png +0 -0
- package/server/public/favicon.svg +40 -9
- package/server/public/favicon_light.svg +43 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/icons/Icon-192-light.png +0 -0
- package/server/public/icons/Icon-192.png +0 -0
- package/server/public/icons/Icon-512-light.png +0 -0
- package/server/public/icons/Icon-512.png +0 -0
- package/server/public/icons/Icon-maskable-192-light.png +0 -0
- package/server/public/icons/Icon-maskable-192.png +0 -0
- package/server/public/icons/Icon-maskable-512-light.png +0 -0
- package/server/public/icons/Icon-maskable-512.png +0 -0
- package/server/public/main.dart.js +88537 -85936
- package/server/routes/admin.js +632 -0
- package/server/routes/agent_profiles.js +3 -0
- package/server/routes/agents.js +1 -1
- package/server/routes/auth.js +3 -1
- package/server/routes/mcp.js +29 -13
- package/server/routes/memory.js +23 -1
- package/server/routes/messaging.js +2 -0
- package/server/routes/screenHistory.js +14 -8
- package/server/routes/settings.js +10 -2
- package/server/routes/stream.js +1 -1
- package/server/routes/triggers.js +4 -4
- package/server/routes/voice_assistant.js +36 -1
- package/server/routes/workspace.js +86 -0
- package/server/services/account/admin_two_factor.js +132 -0
- package/server/services/account/password_policy.js +6 -1
- package/server/services/ai/completion.js +44 -0
- package/server/services/ai/engine.js +321 -500
- package/server/services/ai/imageAnalysis.js +9 -5
- package/server/services/ai/logFormat.js +46 -0
- package/server/services/ai/loopPolicy.js +11 -0
- package/server/services/ai/messagingFallback.js +228 -0
- package/server/services/ai/models.js +194 -55
- package/server/services/ai/providerRetry.js +169 -0
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +38 -11
- package/server/services/ai/providers/grok.js +19 -68
- package/server/services/ai/providers/grokOauth.js +141 -0
- package/server/services/ai/providers/nvidia.js +154 -0
- package/server/services/ai/providers/ollama.js +76 -39
- package/server/services/ai/providers/openai.js +20 -31
- package/server/services/ai/providers/openaiCodex.js +6 -2
- package/server/services/ai/providers/openaiCompatible.js +70 -0
- package/server/services/ai/providers/openrouter.js +162 -0
- package/server/services/ai/settings.js +30 -0
- package/server/services/ai/systemPrompt.js +51 -29
- package/server/services/ai/taskAnalysis.js +60 -1
- package/server/services/ai/toolEvidence.js +207 -0
- package/server/services/ai/toolSelector.js +8 -1
- package/server/services/ai/tools.js +78 -13
- package/server/services/browser/extension/registry.js +94 -9
- package/server/services/desktop/registry.js +104 -1
- package/server/services/desktop/screenRecorder.js +208 -98
- package/server/services/desktop/screen_recorder_support.js +46 -0
- package/server/services/integrations/google/provider.js +13 -0
- package/server/services/integrations/home_assistant/constants.js +88 -0
- package/server/services/integrations/home_assistant/network.js +207 -0
- package/server/services/integrations/home_assistant/provider.js +249 -0
- package/server/services/integrations/home_assistant/snapshot.js +98 -0
- package/server/services/integrations/home_assistant/tools.js +101 -0
- package/server/services/integrations/manager.js +5 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/trello/provider.js +8 -2
- package/server/services/manager.js +51 -53
- package/server/services/mcp/client.js +208 -268
- package/server/services/mcp/client_support.js +172 -0
- package/server/services/mcp/recovery.js +116 -0
- package/server/services/mcp/tool_operations.js +123 -0
- package/server/services/memory/ingestion.js +185 -370
- package/server/services/memory/ingestion_coverage.js +129 -0
- package/server/services/memory/ingestion_documents.js +123 -0
- package/server/services/memory/ingestion_support.js +171 -0
- package/server/services/memory/intelligence.js +181 -0
- package/server/services/memory/manager.js +476 -32
- package/server/services/messaging/automation.js +71 -134
- package/server/services/messaging/formatting_guides.js +26 -1
- package/server/services/messaging/inbound_queue.js +111 -0
- package/server/services/messaging/manager.js +25 -8
- package/server/services/messaging/typing_keepalive.js +110 -0
- package/server/services/streaming/android-stream.js +293 -40
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +415 -56
- package/server/services/tasks/task_repository.js +48 -6
- package/server/services/websocket.js +8 -2
- package/server/services/widgets/service.js +17 -1
- package/server/services/workspace/manager.js +116 -0
- package/server/utils/logger.js +44 -6
- package/server/utils/security.js +3 -0
- package/server/utils/version.js +29 -19
- package/server/services/memory/openhuman_uplift.test.js +0 -98
|
@@ -25,10 +25,19 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
25
25
|
final DesktopCompanionActions _actions;
|
|
26
26
|
WebSocket? _socket;
|
|
27
27
|
Timer? _reconnectTimer;
|
|
28
|
+
Timer? _connectionWatchdogTimer;
|
|
28
29
|
Timer? _streamTimer;
|
|
29
30
|
bool _streamCaptureInFlight = false;
|
|
31
|
+
// Set true while a click / drag / scroll / typeText / pressKey command is
|
|
32
|
+
// being executed. _captureAndSendBinaryFrame respects this flag so it does
|
|
33
|
+
// not compete with the input command for the native bridge or the WebSocket
|
|
34
|
+
// send buffer, and a fresh frame is forced immediately after the action.
|
|
35
|
+
bool _inputCommandInFlight = false;
|
|
30
36
|
int _frameSeq = 0;
|
|
31
37
|
int _streamGeneration = 0;
|
|
38
|
+
// Tracks the current stream quality so the forced post-input capture can use
|
|
39
|
+
// the same setting without re-parsing the original startStream payload.
|
|
40
|
+
int _currentStreamQuality = 80;
|
|
32
41
|
|
|
33
42
|
String _backendUrl = '';
|
|
34
43
|
String _sessionCookie = '';
|
|
@@ -86,6 +95,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
86
95
|
await disconnect();
|
|
87
96
|
return;
|
|
88
97
|
}
|
|
98
|
+
_ensureConnectionWatchdog();
|
|
89
99
|
await _ensureConnected();
|
|
90
100
|
}
|
|
91
101
|
|
|
@@ -130,6 +140,8 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
130
140
|
Future<void> disconnect() async {
|
|
131
141
|
_reconnectTimer?.cancel();
|
|
132
142
|
_reconnectTimer = null;
|
|
143
|
+
_connectionWatchdogTimer?.cancel();
|
|
144
|
+
_connectionWatchdogTimer = null;
|
|
133
145
|
_stopStreaming();
|
|
134
146
|
_connecting = false;
|
|
135
147
|
_connected = false;
|
|
@@ -216,6 +228,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
216
228
|
uri.toString(),
|
|
217
229
|
headers: <String, Object>{'Cookie': _sessionCookie},
|
|
218
230
|
);
|
|
231
|
+
socket.pingInterval = const Duration(seconds: 25);
|
|
219
232
|
_socket = socket;
|
|
220
233
|
socket.listen(
|
|
221
234
|
_handleMessage,
|
|
@@ -286,6 +299,19 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
286
299
|
}
|
|
287
300
|
}
|
|
288
301
|
|
|
302
|
+
// Commands that interact with the remote machine's input system. While one
|
|
303
|
+
// of these is executing we pause frame captures so the WebSocket send buffer
|
|
304
|
+
// is clear for the result message, and to avoid the native bridge being busy
|
|
305
|
+
// with a screenshot when the click/drag/etc. needs to run.
|
|
306
|
+
static const _inputCommands = <String>{
|
|
307
|
+
'click',
|
|
308
|
+
'mouseMove',
|
|
309
|
+
'drag',
|
|
310
|
+
'scroll',
|
|
311
|
+
'typeText',
|
|
312
|
+
'pressKey',
|
|
313
|
+
};
|
|
314
|
+
|
|
289
315
|
Future<void> _handleCommand(Map<String, Object?> message) async {
|
|
290
316
|
final id = message['id']?.toString() ?? '';
|
|
291
317
|
final command = message['command']?.toString() ?? '';
|
|
@@ -294,6 +320,10 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
294
320
|
(key, value) => MapEntry(key.toString(), value),
|
|
295
321
|
)
|
|
296
322
|
: const <String, Object?>{};
|
|
323
|
+
|
|
324
|
+
final isInput = _inputCommands.contains(command);
|
|
325
|
+
if (isInput) _inputCommandInFlight = true;
|
|
326
|
+
|
|
297
327
|
try {
|
|
298
328
|
final response = await _dispatchCommand(command, payload);
|
|
299
329
|
_socket?.add(
|
|
@@ -304,6 +334,18 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
304
334
|
'payload': response,
|
|
305
335
|
}),
|
|
306
336
|
);
|
|
337
|
+
// Immediately capture a fresh frame after an input action so the user
|
|
338
|
+
// sees the result of their interaction without waiting for the next
|
|
339
|
+
// timer tick.
|
|
340
|
+
if (isInput && _streamTimer != null && _connected) {
|
|
341
|
+
unawaited(
|
|
342
|
+
_captureAndSendBinaryFrame(
|
|
343
|
+
_currentStreamQuality,
|
|
344
|
+
_streamGeneration,
|
|
345
|
+
forced: true,
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
307
349
|
} catch (error) {
|
|
308
350
|
_socket?.add(
|
|
309
351
|
jsonEncode(<String, Object?>{
|
|
@@ -313,6 +355,8 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
313
355
|
'error': '$error',
|
|
314
356
|
}),
|
|
315
357
|
);
|
|
358
|
+
} finally {
|
|
359
|
+
if (isInput) _inputCommandInFlight = false;
|
|
316
360
|
}
|
|
317
361
|
}
|
|
318
362
|
|
|
@@ -431,6 +475,8 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
431
475
|
void dispose() {
|
|
432
476
|
_reconnectTimer?.cancel();
|
|
433
477
|
_reconnectTimer = null;
|
|
478
|
+
_connectionWatchdogTimer?.cancel();
|
|
479
|
+
_connectionWatchdogTimer = null;
|
|
434
480
|
_stopStreaming();
|
|
435
481
|
_connecting = false;
|
|
436
482
|
_connected = false;
|
|
@@ -447,12 +493,28 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
447
493
|
|
|
448
494
|
void _scheduleReconnect() {
|
|
449
495
|
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
496
|
+
_ensureConnectionWatchdog();
|
|
450
497
|
_reconnectTimer?.cancel();
|
|
451
498
|
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
|
452
499
|
unawaited(_ensureConnected());
|
|
453
500
|
});
|
|
454
501
|
}
|
|
455
502
|
|
|
503
|
+
void _ensureConnectionWatchdog() {
|
|
504
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) return;
|
|
505
|
+
if (_connectionWatchdogTimer != null) return;
|
|
506
|
+
_connectionWatchdogTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
|
507
|
+
if (!_enabled || !_authenticated || _sessionCookie.isEmpty) {
|
|
508
|
+
_connectionWatchdogTimer?.cancel();
|
|
509
|
+
_connectionWatchdogTimer = null;
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (!_connected && !_connecting) {
|
|
513
|
+
unawaited(_ensureConnected());
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
456
518
|
Future<void> _sendEvent(String event, Map<String, Object?> payload) async {
|
|
457
519
|
final socket = _socket;
|
|
458
520
|
if (socket == null || !_connected) return;
|
|
@@ -478,6 +540,7 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
478
540
|
}
|
|
479
541
|
final interval = Duration(milliseconds: max(1, (1000 / fps).floor()));
|
|
480
542
|
_frameSeq = 0;
|
|
543
|
+
_currentStreamQuality = quality;
|
|
481
544
|
_streamTimer = Timer.periodic(interval, (_) {
|
|
482
545
|
unawaited(_captureAndSendBinaryFrame(quality, generation));
|
|
483
546
|
});
|
|
@@ -498,7 +561,11 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
498
561
|
return <String, Object?>{'success': true};
|
|
499
562
|
}
|
|
500
563
|
|
|
501
|
-
Future<void> _captureAndSendBinaryFrame(
|
|
564
|
+
Future<void> _captureAndSendBinaryFrame(
|
|
565
|
+
int quality,
|
|
566
|
+
int generation, {
|
|
567
|
+
bool forced = false,
|
|
568
|
+
}) async {
|
|
502
569
|
final socket = _socket;
|
|
503
570
|
if (socket == null ||
|
|
504
571
|
!_connected ||
|
|
@@ -506,6 +573,9 @@ class DesktopCompanionManager extends ChangeNotifier {
|
|
|
506
573
|
generation != _streamGeneration) {
|
|
507
574
|
return;
|
|
508
575
|
}
|
|
576
|
+
// If an input command is actively running, skip this frame unless we were
|
|
577
|
+
// explicitly forced (i.e. this IS the post-input refresh capture).
|
|
578
|
+
if (!forced && _inputCommandInFlight) return;
|
|
509
579
|
_streamCaptureInFlight = true;
|
|
510
580
|
try {
|
|
511
581
|
final snapshot = await _actions.captureSnapshot(
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
class PasswordStrengthEvaluation {
|
|
2
|
+
const PasswordStrengthEvaluation({
|
|
3
|
+
required this.score,
|
|
4
|
+
required this.containsUserInfo,
|
|
5
|
+
required this.obviousPattern,
|
|
6
|
+
required this.hasMinimumLength,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
final int score;
|
|
10
|
+
final bool containsUserInfo;
|
|
11
|
+
final bool obviousPattern;
|
|
12
|
+
final bool hasMinimumLength;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
bool hasSequentialPattern(String input) {
|
|
16
|
+
if (input.length < 4) return false;
|
|
17
|
+
|
|
18
|
+
// check for ascending/descending sequences of length >= 4
|
|
19
|
+
for (int i = 0; i <= input.length - 4; i++) {
|
|
20
|
+
bool asc = true;
|
|
21
|
+
bool desc = true;
|
|
22
|
+
for (int j = 0; j < 3; j++) {
|
|
23
|
+
if (input.codeUnitAt(i + j + 1) != input.codeUnitAt(i + j) + 1) asc = false;
|
|
24
|
+
if (input.codeUnitAt(i + j + 1) != input.codeUnitAt(i + j) - 1) desc = false;
|
|
25
|
+
}
|
|
26
|
+
if (asc || desc) return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
PasswordStrengthEvaluation evaluatePasswordStrength({
|
|
32
|
+
required String password,
|
|
33
|
+
String username = '',
|
|
34
|
+
String email = '',
|
|
35
|
+
}) {
|
|
36
|
+
final value = password.trim();
|
|
37
|
+
if (value.isEmpty) {
|
|
38
|
+
return const PasswordStrengthEvaluation(
|
|
39
|
+
score: 0,
|
|
40
|
+
containsUserInfo: false,
|
|
41
|
+
obviousPattern: false,
|
|
42
|
+
hasMinimumLength: false,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
final lower = RegExp(r'[a-z]').hasMatch(value);
|
|
47
|
+
final upper = RegExp(r'[A-Z]').hasMatch(value);
|
|
48
|
+
final digits = RegExp(r'[0-9]').hasMatch(value);
|
|
49
|
+
final symbols = RegExp(r'[^A-Za-z0-9]').hasMatch(value);
|
|
50
|
+
final variety = <bool>[lower, upper, digits, symbols]
|
|
51
|
+
.where((item) => item)
|
|
52
|
+
.length;
|
|
53
|
+
final normalized = value.toLowerCase();
|
|
54
|
+
final userHints = <String>{
|
|
55
|
+
username.trim().toLowerCase(),
|
|
56
|
+
email.trim().toLowerCase(),
|
|
57
|
+
email.trim().toLowerCase().split('@').first,
|
|
58
|
+
}.where((item) => item.length >= 3);
|
|
59
|
+
final containsUserInfo = userHints.any(normalized.contains);
|
|
60
|
+
final obviousPattern =
|
|
61
|
+
RegExp(r'(.)\1\1').hasMatch(value) ||
|
|
62
|
+
normalized.contains('password') ||
|
|
63
|
+
normalized.contains('123456') ||
|
|
64
|
+
normalized.contains('qwerty') ||
|
|
65
|
+
normalized.contains('letmein') ||
|
|
66
|
+
normalized.contains('welcome') ||
|
|
67
|
+
normalized.contains('admin') ||
|
|
68
|
+
normalized.contains('neoagent') ||
|
|
69
|
+
hasSequentialPattern(normalized);
|
|
70
|
+
|
|
71
|
+
var score = 0;
|
|
72
|
+
if (value.length >= 8) score += 1;
|
|
73
|
+
if (value.length >= 12) score += 1;
|
|
74
|
+
if (variety >= 3) score += 1;
|
|
75
|
+
if (variety == 4 || value.length >= 16) score += 1;
|
|
76
|
+
if (containsUserInfo || obviousPattern) score -= 1;
|
|
77
|
+
|
|
78
|
+
return PasswordStrengthEvaluation(
|
|
79
|
+
score: score.clamp(0, 4),
|
|
80
|
+
containsUserInfo: containsUserInfo,
|
|
81
|
+
obviousPattern: obviousPattern,
|
|
82
|
+
hasMinimumLength: value.length >= 8,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -15,6 +15,9 @@ class StreamRenderer extends StatefulWidget {
|
|
|
15
15
|
this.onSwipe,
|
|
16
16
|
this.onType,
|
|
17
17
|
this.onHover,
|
|
18
|
+
this.onFirstFrame,
|
|
19
|
+
this.onFrameTimeout,
|
|
20
|
+
this.firstFrameTimeout = const Duration(seconds: 8),
|
|
18
21
|
this.fit = BoxFit.contain,
|
|
19
22
|
this.alignment = Alignment.center,
|
|
20
23
|
});
|
|
@@ -27,6 +30,9 @@ class StreamRenderer extends StatefulWidget {
|
|
|
27
30
|
final void Function(double x1, double y1, double x2, double y2)? onSwipe;
|
|
28
31
|
final void Function(String text)? onType;
|
|
29
32
|
final void Function(double x, double y)? onHover;
|
|
33
|
+
final VoidCallback? onFirstFrame;
|
|
34
|
+
final VoidCallback? onFrameTimeout;
|
|
35
|
+
final Duration firstFrameTimeout;
|
|
30
36
|
final BoxFit fit;
|
|
31
37
|
final Alignment alignment;
|
|
32
38
|
|
|
@@ -43,6 +49,8 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
43
49
|
Offset? _dragEnd;
|
|
44
50
|
|
|
45
51
|
Timer? _hoverThrottleTimer;
|
|
52
|
+
Timer? _firstFrameTimer;
|
|
53
|
+
bool _firstFrameTimedOut = false;
|
|
46
54
|
Offset? _pendingHoverOffset;
|
|
47
55
|
DateTime _lastHoverTime = DateTime.fromMillisecondsSinceEpoch(0);
|
|
48
56
|
|
|
@@ -54,6 +62,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
54
62
|
'deviceId': widget.deviceId,
|
|
55
63
|
'platform': widget.platform,
|
|
56
64
|
});
|
|
65
|
+
_startFirstFrameTimer();
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
@override
|
|
@@ -65,7 +74,9 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
65
74
|
return;
|
|
66
75
|
}
|
|
67
76
|
_frameSize = null;
|
|
77
|
+
_frame = null;
|
|
68
78
|
_detachImageListener();
|
|
79
|
+
_firstFrameTimer?.cancel();
|
|
69
80
|
oldWidget.socket.off('stream:frame', _onFrame);
|
|
70
81
|
oldWidget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
71
82
|
'deviceId': oldWidget.deviceId,
|
|
@@ -76,6 +87,24 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
76
87
|
'deviceId': widget.deviceId,
|
|
77
88
|
'platform': widget.platform,
|
|
78
89
|
});
|
|
90
|
+
_startFirstFrameTimer();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void _startFirstFrameTimer() {
|
|
94
|
+
_firstFrameTimer?.cancel();
|
|
95
|
+
_firstFrameTimedOut = false;
|
|
96
|
+
if (widget.onFrameTimeout == null ||
|
|
97
|
+
widget.firstFrameTimeout <= Duration.zero ||
|
|
98
|
+
_frame != null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
_firstFrameTimer = Timer(widget.firstFrameTimeout, () {
|
|
102
|
+
if (!mounted || _frame != null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
_firstFrameTimedOut = true;
|
|
106
|
+
widget.onFrameTimeout?.call();
|
|
107
|
+
});
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
void _onFrame(dynamic data) {
|
|
@@ -102,6 +131,14 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
102
131
|
_ => null,
|
|
103
132
|
};
|
|
104
133
|
if (frame == null || frame.isEmpty || !mounted) return;
|
|
134
|
+
final hadFrame = _frame != null;
|
|
135
|
+
if (!hadFrame) {
|
|
136
|
+
_firstFrameTimer?.cancel();
|
|
137
|
+
_firstFrameTimer = null;
|
|
138
|
+
if (!_firstFrameTimedOut) {
|
|
139
|
+
widget.onFirstFrame?.call();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
105
142
|
if (_frameSize == null) {
|
|
106
143
|
_resolveFrameSize(frame);
|
|
107
144
|
}
|
|
@@ -146,7 +183,8 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
146
183
|
return MouseRegion(
|
|
147
184
|
onHover: widget.onHover == null
|
|
148
185
|
? null
|
|
149
|
-
: (event) =>
|
|
186
|
+
: (event) =>
|
|
187
|
+
_handleHoverEvent(event.localPosition, constraints.biggest),
|
|
150
188
|
child: GestureDetector(
|
|
151
189
|
behavior: HitTestBehavior.opaque,
|
|
152
190
|
onTapDown: widget.onTap == null
|
|
@@ -197,7 +235,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
197
235
|
final now = DateTime.now();
|
|
198
236
|
final elapsed = now.difference(_lastHoverTime);
|
|
199
237
|
const throttleDuration = Duration(milliseconds: 70);
|
|
200
|
-
|
|
238
|
+
|
|
201
239
|
if (elapsed >= throttleDuration) {
|
|
202
240
|
_sendPendingHover(boxSize);
|
|
203
241
|
} else {
|
|
@@ -213,7 +251,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
213
251
|
if (offset == null) return;
|
|
214
252
|
_pendingHoverOffset = null;
|
|
215
253
|
_lastHoverTime = DateTime.now();
|
|
216
|
-
|
|
254
|
+
|
|
217
255
|
final point = _mapToRemote(offset, boxSize);
|
|
218
256
|
if (point != null) {
|
|
219
257
|
widget.onHover?.call(point.dx, point.dy);
|
|
@@ -275,6 +313,7 @@ class _StreamRendererState extends State<StreamRenderer> {
|
|
|
275
313
|
@override
|
|
276
314
|
void dispose() {
|
|
277
315
|
_hoverThrottleTimer?.cancel();
|
|
316
|
+
_firstFrameTimer?.cancel();
|
|
278
317
|
widget.socket.emit('stream:unsubscribe', <String, Object?>{
|
|
279
318
|
'deviceId': widget.deviceId,
|
|
280
319
|
'platform': widget.platform,
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import 'package:flutter/widgets.dart';
|
|
2
2
|
|
|
3
|
+
/// Resolves the active [NeoAgentPalette] for the given [brightness].
|
|
4
|
+
NeoAgentPalette paletteFor(Brightness brightness) =>
|
|
5
|
+
brightness == Brightness.light ? lightPalette : darkPalette;
|
|
6
|
+
|
|
7
|
+
/// Resolves the active [NeoAgentPalette] from the ambient theme brightness.
|
|
8
|
+
NeoAgentPalette paletteOf(BuildContext context) =>
|
|
9
|
+
paletteFor(MediaQuery.platformBrightnessOf(context));
|
|
10
|
+
|
|
11
|
+
/// Canonical color tokens for the NeoAgent "Control Surface" design system.
|
|
12
|
+
///
|
|
13
|
+
/// Visual language: warm-organic "agent OS" — olive/sage greens paired with
|
|
14
|
+
/// a gold accent, a paper-light day theme and a deep-olive night theme.
|
|
15
|
+
/// Every screen reads its colors from these tokens (via the theme getters in
|
|
16
|
+
/// `main_theme.dart`), so adjusting a value here re-skins the whole app.
|
|
3
17
|
class NeoAgentPalette {
|
|
4
18
|
const NeoAgentPalette({
|
|
5
19
|
required this.bgPrimary,
|
|
@@ -21,61 +35,89 @@ class NeoAgentPalette {
|
|
|
21
35
|
required this.info,
|
|
22
36
|
});
|
|
23
37
|
|
|
38
|
+
/// Page background ("paper" by day, "deep olive" by night).
|
|
24
39
|
final Color bgPrimary;
|
|
40
|
+
|
|
41
|
+
/// Slightly recessed background used for rails and inset wells.
|
|
25
42
|
final Color bgSecondary;
|
|
43
|
+
|
|
44
|
+
/// Tertiary surface for nested wells, track fills and pressed states.
|
|
26
45
|
final Color bgTertiary;
|
|
46
|
+
|
|
47
|
+
/// Raised surface for cards, sheets and bubbles.
|
|
27
48
|
final Color bgCard;
|
|
49
|
+
|
|
50
|
+
/// Primary ink — headings and body text.
|
|
28
51
|
final Color textPrimary;
|
|
52
|
+
|
|
53
|
+
/// Secondary ink — supporting copy and inactive labels.
|
|
29
54
|
final Color textSecondary;
|
|
55
|
+
|
|
56
|
+
/// Muted ink — captions, placeholders and disabled glyphs.
|
|
30
57
|
final Color textMuted;
|
|
58
|
+
|
|
59
|
+
/// Gold accent — primary actions, active nav, focus rings.
|
|
31
60
|
final Color accent;
|
|
61
|
+
|
|
62
|
+
/// Readable gold ("gold-ink") for eyebrows, links and accent text on paper.
|
|
32
63
|
final Color accentHover;
|
|
64
|
+
|
|
65
|
+
/// Sage green companion accent — calls, traces and secondary affordances.
|
|
33
66
|
final Color accentAlt;
|
|
67
|
+
|
|
68
|
+
/// Translucent gold wash for soft fills and selection backgrounds.
|
|
34
69
|
final Color accentMuted;
|
|
70
|
+
|
|
71
|
+
/// Hairline divider color.
|
|
35
72
|
final Color border;
|
|
73
|
+
|
|
74
|
+
/// Stronger hairline for inputs and interactive outlines.
|
|
36
75
|
final Color borderLight;
|
|
76
|
+
|
|
37
77
|
final Color success;
|
|
38
78
|
final Color warning;
|
|
39
79
|
final Color danger;
|
|
40
80
|
final Color info;
|
|
41
81
|
}
|
|
42
82
|
|
|
83
|
+
/// Deep-olive night theme.
|
|
43
84
|
const NeoAgentPalette darkPalette = NeoAgentPalette(
|
|
44
|
-
bgPrimary: Color(
|
|
45
|
-
bgSecondary: Color(
|
|
46
|
-
bgTertiary: Color(
|
|
47
|
-
bgCard: Color(
|
|
48
|
-
textPrimary: Color(
|
|
49
|
-
textSecondary: Color(
|
|
50
|
-
textMuted: Color(
|
|
51
|
-
accent: Color(
|
|
52
|
-
accentHover: Color(
|
|
53
|
-
accentAlt: Color(
|
|
54
|
-
accentMuted: Color(
|
|
55
|
-
border: Color(
|
|
56
|
-
borderLight: Color(
|
|
57
|
-
success: Color(
|
|
58
|
-
warning: Color(
|
|
59
|
-
danger: Color(
|
|
60
|
-
info: Color(
|
|
85
|
+
bgPrimary: Color(0xFF0E1511),
|
|
86
|
+
bgSecondary: Color(0xFF0A0F0C),
|
|
87
|
+
bgTertiary: Color(0xFF252D28),
|
|
88
|
+
bgCard: Color(0xFF171F1A),
|
|
89
|
+
textPrimary: Color(0xFFECEFE5),
|
|
90
|
+
textSecondary: Color(0xFFAEB7A6),
|
|
91
|
+
textMuted: Color(0xFF7E8877),
|
|
92
|
+
accent: Color(0xFFE1B052),
|
|
93
|
+
accentHover: Color(0xFFEAC272),
|
|
94
|
+
accentAlt: Color(0xFF84BA87),
|
|
95
|
+
accentMuted: Color(0x29E1B052),
|
|
96
|
+
border: Color(0x1AE0F0E0),
|
|
97
|
+
borderLight: Color(0x2BE0F0E0),
|
|
98
|
+
success: Color(0xFF74C07C),
|
|
99
|
+
warning: Color(0xFFD9A24B),
|
|
100
|
+
danger: Color(0xFFDE8A78),
|
|
101
|
+
info: Color(0xFF6FB0A4),
|
|
61
102
|
);
|
|
62
103
|
|
|
104
|
+
/// Paper-light day theme.
|
|
63
105
|
const NeoAgentPalette lightPalette = NeoAgentPalette(
|
|
64
|
-
bgPrimary: Color(
|
|
65
|
-
bgSecondary: Color(
|
|
66
|
-
bgTertiary: Color(
|
|
67
|
-
bgCard: Color(
|
|
68
|
-
textPrimary: Color(
|
|
69
|
-
textSecondary: Color(
|
|
70
|
-
textMuted: Color(
|
|
71
|
-
accent: Color(
|
|
72
|
-
accentHover: Color(
|
|
73
|
-
accentAlt: Color(
|
|
74
|
-
accentMuted: Color(
|
|
75
|
-
border: Color(
|
|
76
|
-
borderLight: Color(
|
|
77
|
-
success: Color(
|
|
78
|
-
warning: Color(
|
|
79
|
-
danger: Color(
|
|
80
|
-
info: Color(
|
|
106
|
+
bgPrimary: Color(0xFFF4F1E8),
|
|
107
|
+
bgSecondary: Color(0xFFEDE9DC),
|
|
108
|
+
bgTertiary: Color(0xFFF1EDE1),
|
|
109
|
+
bgCard: Color(0xFFFDFCF8),
|
|
110
|
+
textPrimary: Color(0xFF1C2117),
|
|
111
|
+
textSecondary: Color(0xFF49503F),
|
|
112
|
+
textMuted: Color(0xFF7E8470),
|
|
113
|
+
accent: Color(0xFFB07D2B),
|
|
114
|
+
accentHover: Color(0xFF8A5F1C),
|
|
115
|
+
accentAlt: Color(0xFF5E6B4C),
|
|
116
|
+
accentMuted: Color(0x24B07D2B),
|
|
117
|
+
border: Color(0x171C2117),
|
|
118
|
+
borderLight: Color(0x291C2117),
|
|
119
|
+
success: Color(0xFF527C4F),
|
|
120
|
+
warning: Color(0xFF9A7B33),
|
|
121
|
+
danger: Color(0xFFAE473C),
|
|
122
|
+
info: Color(0xFF2F7D6E),
|
|
81
123
|
);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/flutter_app/pubspec.lock
CHANGED
|
@@ -418,10 +418,10 @@ packages:
|
|
|
418
418
|
dependency: "direct main"
|
|
419
419
|
description:
|
|
420
420
|
name: google_fonts
|
|
421
|
-
sha256:
|
|
421
|
+
sha256: "4e9391085e524954a51e3625b7c9c7e9851dc3f376603208bb45c24b9a66255d"
|
|
422
422
|
url: "https://pub.dev"
|
|
423
423
|
source: hosted
|
|
424
|
-
version: "
|
|
424
|
+
version: "8.1.0"
|
|
425
425
|
hotkey_manager:
|
|
426
426
|
dependency: "direct main"
|
|
427
427
|
description:
|
package/flutter_app/pubspec.yaml
CHANGED
|
@@ -12,7 +12,7 @@ dependencies:
|
|
|
12
12
|
cupertino_icons: ^1.0.8
|
|
13
13
|
flutter_markdown: ^0.7.4+1
|
|
14
14
|
file_picker: ^8.0.7
|
|
15
|
-
google_fonts: ^
|
|
15
|
+
google_fonts: ^8.1.0
|
|
16
16
|
image: ^4.5.4
|
|
17
17
|
http: ^1.5.0
|
|
18
18
|
mixpanel_flutter: ^2.6.1
|
|
@@ -55,5 +55,11 @@ flutter:
|
|
|
55
55
|
assets:
|
|
56
56
|
- web/icons/Icon-192.png
|
|
57
57
|
- assets/branding/app_icon_256.png
|
|
58
|
+
- assets/branding/app_icon_512.png
|
|
59
|
+
- assets/branding/app_icon_1024.png
|
|
60
|
+
- assets/branding/app_icon_light_256.png
|
|
61
|
+
- assets/branding/app_icon_light_512.png
|
|
62
|
+
- assets/branding/app_icon_light_1024.png
|
|
58
63
|
- assets/branding/tray_icon_template.png
|
|
64
|
+
- assets/branding/tray_icon_light_template.png
|
|
59
65
|
- assets/branding/onboarding_intro.mp4
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 100 100">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="tile" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#705331"></stop>
|
|
5
|
+
<stop offset="100%" stop-color="#22564c"></stop>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<radialGradient id="shine" cx="26%" cy="14%" r="72%">
|
|
8
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.22"></stop>
|
|
9
|
+
<stop offset="100%" stop-color="#ffffff" stop-opacity="0"></stop>
|
|
10
|
+
</radialGradient>
|
|
11
|
+
<radialGradient id="core" cx="38%" cy="32%" r="75%">
|
|
12
|
+
<stop offset="0%" stop-color="#fffdf6"></stop>
|
|
13
|
+
<stop offset="48%" stop-color="#eee3cc"></stop>
|
|
14
|
+
<stop offset="100%" stop-color="#b5a888"></stop>
|
|
15
|
+
</radialGradient>
|
|
16
|
+
<radialGradient id="node" cx="36%" cy="30%" r="80%">
|
|
17
|
+
<stop offset="0%" stop-color="#f8dca8"></stop>
|
|
18
|
+
<stop offset="52%" stop-color="#d3a85f"></stop>
|
|
19
|
+
<stop offset="100%" stop-color="#9b6f2f"></stop>
|
|
20
|
+
</radialGradient>
|
|
21
|
+
<linearGradient id="tube" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
22
|
+
<stop offset="0%" stop-color="#fffaf0"></stop>
|
|
23
|
+
<stop offset="52%" stop-color="#efe7d6"></stop>
|
|
24
|
+
<stop offset="100%" stop-color="#cabf9f"></stop>
|
|
25
|
+
</linearGradient>
|
|
26
|
+
<filter id="sh" x="-30%" y="-30%" width="160%" height="160%">
|
|
27
|
+
<feDropShadow dx="0" dy="1.1" stdDeviation="1.1" flood-color="#000000" flood-opacity="0.45"></feDropShadow>
|
|
28
|
+
</filter>
|
|
29
|
+
<clipPath id="tc"><rect x="0" y="0" width="100" height="100" rx="22.5"></rect></clipPath>
|
|
30
|
+
</defs>
|
|
31
|
+
<g clip-path="url(#tc)">
|
|
32
|
+
<rect x="0" y="0" width="100" height="100" fill="url(#tile)"></rect>
|
|
33
|
+
<rect x="0" y="0" width="100" height="100" fill="url(#shine)"></rect>
|
|
34
|
+
<g fill="none" stroke="url(#tube)" stroke-linecap="round" filter="url(#sh)">
|
|
35
|
+
<circle cx="50" cy="50" r="17" stroke-width="5" stroke-dasharray="78 29" transform="rotate(-35 50 50)" stroke-opacity="0.92"></circle>
|
|
36
|
+
<circle cx="50" cy="50" r="30" stroke-width="5" stroke-dasharray="150 39" transform="rotate(70 50 50)" stroke-opacity="0.8"></circle>
|
|
37
|
+
</g>
|
|
38
|
+
<circle cx="50" cy="50" r="7" fill="url(#core)"></circle>
|
|
39
|
+
<circle cx="50" cy="20" r="5.6" fill="url(#node)"></circle>
|
|
40
|
+
<circle cx="48" cy="18.4" r="1.7" fill="#fff7e6" fill-opacity="0.8"></circle>
|
|
41
|
+
</g>
|
|
42
|
+
<rect x="0.6" y="0.6" width="98.8" height="98.8" rx="22" fill="none" stroke="rgba(255,255,255,0.16)" stroke-width="1.2"></rect>
|
|
43
|
+
</svg>
|