neoagent 2.4.1-beta.8 → 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 +45 -7
- 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 -2
- package/extensions/chrome-browser/popup.html +5 -1
- package/extensions/chrome-browser/popup.js +15 -2
- 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 +3 -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 +338 -46
- package/flutter_app/lib/main_devices.dart +851 -122
- 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 +764 -887
- 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 +79 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +56 -7
- package/flutter_app/lib/src/desktop_companion_io.dart +77 -1
- package/flutter_app/lib/src/desktop_native_bridge.dart +13 -0
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/stream_renderer.dart +205 -35
- 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/AppDelegate.swift +44 -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 +88944 -85720
- 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/browser.js +14 -0
- package/server/routes/browser_extension.js +21 -4
- package/server/routes/desktop.js +10 -0
- 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 +14 -2
- package/server/routes/stream.js +12 -3
- 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 +80 -15
- package/server/services/android/controller.js +36 -1
- package/server/services/browser/controller.js +18 -0
- package/server/services/browser/extension/manifest.js +33 -0
- package/server/services/browser/extension/provider.js +12 -6
- package/server/services/browser/extension/registry.js +229 -18
- package/server/services/desktop/protocol.js +1 -0
- package/server/services/desktop/provider.js +4 -0
- 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 -47
- 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/runtime/manager.js +7 -3
- package/server/services/runtime/settings.js +17 -0
- package/server/services/streaming/android-stream.js +296 -32
- package/server/services/streaming/stream-hub.js +1 -1
- 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
|
@@ -11,6 +11,8 @@ EdgeInsets _pagePadding(BuildContext context) {
|
|
|
11
11
|
return const EdgeInsets.fromLTRB(20, 20, 20, 28);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
final ValueNotifier<bool> _partyModeEnabled = ValueNotifier<bool>(false);
|
|
15
|
+
|
|
14
16
|
class _AmbientBackdrop extends StatefulWidget {
|
|
15
17
|
const _AmbientBackdrop({required this.child});
|
|
16
18
|
|
|
@@ -41,81 +43,266 @@ class _AmbientBackdropState extends State<_AmbientBackdrop>
|
|
|
41
43
|
|
|
42
44
|
@override
|
|
43
45
|
Widget build(BuildContext context) {
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
color: _accentAlt.withValues(alpha: 0.85),
|
|
66
|
-
),
|
|
67
|
-
),
|
|
68
|
-
Positioned(
|
|
69
|
-
bottom: -140 + (t * 16),
|
|
70
|
-
left: 100 - (t * 24),
|
|
71
|
-
child: _BlurOrb(
|
|
72
|
-
size: 360,
|
|
73
|
-
color: _accent.withValues(alpha: 0.45),
|
|
74
|
-
),
|
|
75
|
-
),
|
|
76
|
-
Positioned.fill(
|
|
77
|
-
child: IgnorePointer(
|
|
78
|
-
child: DecoratedBox(
|
|
79
|
-
decoration: BoxDecoration(
|
|
80
|
-
gradient: LinearGradient(
|
|
81
|
-
colors: <Color>[
|
|
82
|
-
Colors.white.withValues(alpha: 0.05),
|
|
83
|
-
Colors.transparent,
|
|
84
|
-
Colors.black.withValues(alpha: 0.12),
|
|
85
|
-
],
|
|
86
|
-
stops: const <double>[0, 0.32, 1],
|
|
87
|
-
begin: Alignment.topCenter,
|
|
88
|
-
end: Alignment.bottomCenter,
|
|
46
|
+
return ValueListenableBuilder<bool>(
|
|
47
|
+
valueListenable: _partyModeEnabled,
|
|
48
|
+
builder: (context, partyMode, _) {
|
|
49
|
+
return DecoratedBox(
|
|
50
|
+
decoration: BoxDecoration(gradient: _appBackgroundGradient),
|
|
51
|
+
child: AnimatedBuilder(
|
|
52
|
+
animation: _controller,
|
|
53
|
+
builder: (context, _) {
|
|
54
|
+
final t = Curves.easeInOut.transform(_controller.value);
|
|
55
|
+
return Stack(
|
|
56
|
+
children: <Widget>[
|
|
57
|
+
Positioned.fill(
|
|
58
|
+
child: IgnorePointer(
|
|
59
|
+
child: CustomPaint(
|
|
60
|
+
painter: _AuroraFieldPainter(
|
|
61
|
+
progress: t,
|
|
62
|
+
partyMode: partyMode,
|
|
63
|
+
primary: _accent,
|
|
64
|
+
secondary: _accentAlt,
|
|
65
|
+
base: _bgPrimary,
|
|
66
|
+
),
|
|
89
67
|
),
|
|
90
68
|
),
|
|
91
69
|
),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
radius: 0.95,
|
|
101
|
-
colors: <Color>[
|
|
102
|
-
_glassHighlight.withValues(alpha: 0.14),
|
|
103
|
-
Colors.transparent,
|
|
104
|
-
],
|
|
70
|
+
Positioned.fill(
|
|
71
|
+
child: IgnorePointer(
|
|
72
|
+
child: CustomPaint(
|
|
73
|
+
painter: _ArcadeGridPainter(
|
|
74
|
+
progress: t,
|
|
75
|
+
color: _accentAlt,
|
|
76
|
+
partyMode: partyMode,
|
|
77
|
+
),
|
|
105
78
|
),
|
|
106
79
|
),
|
|
107
80
|
),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
81
|
+
Positioned.fill(
|
|
82
|
+
child: IgnorePointer(
|
|
83
|
+
child: DecoratedBox(
|
|
84
|
+
decoration: BoxDecoration(
|
|
85
|
+
gradient: LinearGradient(
|
|
86
|
+
colors: <Color>[
|
|
87
|
+
Colors.white.withValues(alpha: 0.05),
|
|
88
|
+
Colors.transparent,
|
|
89
|
+
Colors.black.withValues(alpha: 0.12),
|
|
90
|
+
],
|
|
91
|
+
stops: const <double>[0, 0.32, 1],
|
|
92
|
+
begin: Alignment.topCenter,
|
|
93
|
+
end: Alignment.bottomCenter,
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
Positioned.fill(
|
|
100
|
+
child: IgnorePointer(
|
|
101
|
+
child: CustomPaint(
|
|
102
|
+
painter: _ConfettiBitsPainter(
|
|
103
|
+
progress: t,
|
|
104
|
+
active: partyMode,
|
|
105
|
+
primary: _accent,
|
|
106
|
+
secondary: _accentAlt,
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
),
|
|
111
|
+
widget.child,
|
|
112
|
+
],
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class _ControlSurfaceBackdrop extends StatelessWidget {
|
|
123
|
+
const _ControlSurfaceBackdrop({required this.child});
|
|
124
|
+
|
|
125
|
+
final Widget child;
|
|
126
|
+
|
|
127
|
+
@override
|
|
128
|
+
Widget build(BuildContext context) {
|
|
129
|
+
return DecoratedBox(
|
|
130
|
+
decoration: BoxDecoration(
|
|
131
|
+
gradient: LinearGradient(
|
|
132
|
+
colors: <Color>[
|
|
133
|
+
_bgPrimary,
|
|
134
|
+
Color.lerp(_bgPrimary, _accentAlt, 0.025)!,
|
|
135
|
+
Color.lerp(_bgPrimary, _accent, 0.02)!,
|
|
136
|
+
],
|
|
137
|
+
stops: const <double>[0, 0.58, 1],
|
|
138
|
+
begin: const Alignment(-0.95, -1),
|
|
139
|
+
end: const Alignment(0.96, 1),
|
|
140
|
+
),
|
|
114
141
|
),
|
|
142
|
+
child: child,
|
|
115
143
|
);
|
|
116
144
|
}
|
|
117
145
|
}
|
|
118
146
|
|
|
147
|
+
class _AuroraFieldPainter extends CustomPainter {
|
|
148
|
+
const _AuroraFieldPainter({
|
|
149
|
+
required this.progress,
|
|
150
|
+
required this.partyMode,
|
|
151
|
+
required this.primary,
|
|
152
|
+
required this.secondary,
|
|
153
|
+
required this.base,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
final double progress;
|
|
157
|
+
final bool partyMode;
|
|
158
|
+
final Color primary;
|
|
159
|
+
final Color secondary;
|
|
160
|
+
final Color base;
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
void paint(Canvas canvas, Size size) {
|
|
164
|
+
final rect = Offset.zero & size;
|
|
165
|
+
final energy = partyMode ? 1.45 : 1.0;
|
|
166
|
+
final wash = Paint()
|
|
167
|
+
..shader = LinearGradient(
|
|
168
|
+
colors: <Color>[
|
|
169
|
+
primary.withValues(alpha: 0.18 * energy),
|
|
170
|
+
secondary.withValues(alpha: 0.12 * energy),
|
|
171
|
+
base.withValues(alpha: 0),
|
|
172
|
+
],
|
|
173
|
+
stops: const <double>[0, 0.42, 1],
|
|
174
|
+
begin: Alignment(-0.95 + progress * 0.35, -1),
|
|
175
|
+
end: Alignment(0.85 - progress * 0.25, 1),
|
|
176
|
+
).createShader(rect);
|
|
177
|
+
canvas.drawRect(rect, wash);
|
|
178
|
+
|
|
179
|
+
for (var lane = 0; lane < 4; lane++) {
|
|
180
|
+
final yBase = size.height * (0.16 + lane * 0.18);
|
|
181
|
+
final path = Path()..moveTo(-size.width * 0.12, yBase);
|
|
182
|
+
for (var i = 0; i <= 8; i++) {
|
|
183
|
+
final x = size.width * (i / 8);
|
|
184
|
+
final phase = progress * math.pi * 2 + lane * 0.9 + i * 0.28;
|
|
185
|
+
final y = yBase + math.sin(phase) * (28 + lane * 9) * energy;
|
|
186
|
+
path.lineTo(x, y);
|
|
187
|
+
}
|
|
188
|
+
path.lineTo(size.width * 1.12, yBase + size.height * 0.34);
|
|
189
|
+
path.lineTo(-size.width * 0.12, yBase + size.height * 0.28);
|
|
190
|
+
path.close();
|
|
191
|
+
|
|
192
|
+
final lanePaint = Paint()
|
|
193
|
+
..shader = LinearGradient(
|
|
194
|
+
colors: <Color>[
|
|
195
|
+
(lane.isEven ? primary : secondary).withValues(
|
|
196
|
+
alpha: (0.07 + lane * 0.012) * energy,
|
|
197
|
+
),
|
|
198
|
+
Colors.transparent,
|
|
199
|
+
],
|
|
200
|
+
begin: Alignment.topLeft,
|
|
201
|
+
end: Alignment.bottomRight,
|
|
202
|
+
).createShader(rect)
|
|
203
|
+
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 42);
|
|
204
|
+
canvas.drawPath(path, lanePaint);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@override
|
|
209
|
+
bool shouldRepaint(covariant _AuroraFieldPainter oldDelegate) {
|
|
210
|
+
return oldDelegate.progress != progress ||
|
|
211
|
+
oldDelegate.partyMode != partyMode ||
|
|
212
|
+
oldDelegate.primary != primary ||
|
|
213
|
+
oldDelegate.secondary != secondary ||
|
|
214
|
+
oldDelegate.base != base;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class _ArcadeGridPainter extends CustomPainter {
|
|
219
|
+
const _ArcadeGridPainter({
|
|
220
|
+
required this.progress,
|
|
221
|
+
required this.color,
|
|
222
|
+
required this.partyMode,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
final double progress;
|
|
226
|
+
final Color color;
|
|
227
|
+
final bool partyMode;
|
|
228
|
+
|
|
229
|
+
@override
|
|
230
|
+
void paint(Canvas canvas, Size size) {
|
|
231
|
+
final paint = Paint()
|
|
232
|
+
..color = color.withValues(alpha: partyMode ? 0.13 : 0.055)
|
|
233
|
+
..strokeWidth = 1;
|
|
234
|
+
const step = 48.0;
|
|
235
|
+
final drift = progress * step;
|
|
236
|
+
for (double x = -step + drift; x < size.width + step; x += step) {
|
|
237
|
+
canvas.drawLine(
|
|
238
|
+
Offset(x, 0),
|
|
239
|
+
Offset(x - size.width * 0.08, size.height),
|
|
240
|
+
paint,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
for (double y = size.height * 0.58; y < size.height + step; y += step) {
|
|
244
|
+
canvas.drawLine(
|
|
245
|
+
Offset(0, y),
|
|
246
|
+
Offset(size.width, y - progress * 12),
|
|
247
|
+
paint,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@override
|
|
253
|
+
bool shouldRepaint(covariant _ArcadeGridPainter oldDelegate) {
|
|
254
|
+
return oldDelegate.progress != progress ||
|
|
255
|
+
oldDelegate.color != color ||
|
|
256
|
+
oldDelegate.partyMode != partyMode;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
class _ConfettiBitsPainter extends CustomPainter {
|
|
261
|
+
const _ConfettiBitsPainter({
|
|
262
|
+
required this.progress,
|
|
263
|
+
required this.active,
|
|
264
|
+
required this.primary,
|
|
265
|
+
required this.secondary,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
final double progress;
|
|
269
|
+
final bool active;
|
|
270
|
+
final Color primary;
|
|
271
|
+
final Color secondary;
|
|
272
|
+
|
|
273
|
+
@override
|
|
274
|
+
void paint(Canvas canvas, Size size) {
|
|
275
|
+
if (!active) return;
|
|
276
|
+
final paint = Paint()..style = PaintingStyle.fill;
|
|
277
|
+
for (var i = 0; i < 26; i++) {
|
|
278
|
+
final seed = i * 37.0;
|
|
279
|
+
final x = ((seed * 17 + progress * size.width * 0.35) % size.width);
|
|
280
|
+
final y = ((seed * 29 + progress * size.height * 0.9) % size.height);
|
|
281
|
+
final color = i.isEven ? primary : secondary;
|
|
282
|
+
paint.color = color.withValues(alpha: 0.16 + (i % 4) * 0.035);
|
|
283
|
+
canvas.save();
|
|
284
|
+
canvas.translate(x, y);
|
|
285
|
+
canvas.rotate(progress * math.pi * 2 + i);
|
|
286
|
+
canvas.drawRRect(
|
|
287
|
+
RRect.fromRectAndRadius(
|
|
288
|
+
Rect.fromCenter(center: Offset.zero, width: 5 + (i % 3), height: 2.5),
|
|
289
|
+
const Radius.circular(2),
|
|
290
|
+
),
|
|
291
|
+
paint,
|
|
292
|
+
);
|
|
293
|
+
canvas.restore();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@override
|
|
298
|
+
bool shouldRepaint(covariant _ConfettiBitsPainter oldDelegate) {
|
|
299
|
+
return oldDelegate.progress != progress ||
|
|
300
|
+
oldDelegate.active != active ||
|
|
301
|
+
oldDelegate.primary != primary ||
|
|
302
|
+
oldDelegate.secondary != secondary;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
119
306
|
class _EntranceMotion extends StatefulWidget {
|
|
120
307
|
const _EntranceMotion({required this.child});
|
|
121
308
|
|
|
@@ -231,7 +418,6 @@ List<AppSection> _mainSections(NeoAgentController controller) {
|
|
|
231
418
|
return <AppSection>[
|
|
232
419
|
AppSection.chat,
|
|
233
420
|
AppSection.recordings,
|
|
234
|
-
AppSection.runs,
|
|
235
421
|
AppSection.devices,
|
|
236
422
|
AppSection.tasks,
|
|
237
423
|
AppSection.widgets,
|
|
@@ -239,6 +425,7 @@ List<AppSection> _mainSections(NeoAgentController controller) {
|
|
|
239
425
|
AppSection.memory,
|
|
240
426
|
if (controller.showHealthSection) AppSection.health,
|
|
241
427
|
AppSection.settings,
|
|
428
|
+
AppSection.runs,
|
|
242
429
|
AppSection.agents,
|
|
243
430
|
AppSection.messaging,
|
|
244
431
|
];
|
|
@@ -263,11 +450,19 @@ List<Widget> _buildSidebarItems(
|
|
|
263
450
|
continue;
|
|
264
451
|
}
|
|
265
452
|
|
|
453
|
+
final defaultSection = sections.first;
|
|
454
|
+
final parentSection = group == SidebarGroup.settings
|
|
455
|
+
? AppSection.settings
|
|
456
|
+
: defaultSection;
|
|
457
|
+
final childSections = group == SidebarGroup.settings
|
|
458
|
+
? sections.where((section) => section != parentSection).toList()
|
|
459
|
+
: sections;
|
|
460
|
+
final hasChildren =
|
|
461
|
+
childSections.length > 1 ||
|
|
462
|
+
(group == SidebarGroup.settings && childSections.isNotEmpty);
|
|
266
463
|
final active =
|
|
267
464
|
selectedSidebarSection &&
|
|
268
|
-
controller.selectedSection.sidebarSection
|
|
269
|
-
final defaultSection = sections.first;
|
|
270
|
-
final hasChildren = sections.length > 1;
|
|
465
|
+
controller.selectedSection.sidebarSection == parentSection;
|
|
271
466
|
final expanded = expandedGroup == group;
|
|
272
467
|
|
|
273
468
|
widgets.add(
|
|
@@ -279,12 +474,14 @@ List<Widget> _buildSidebarItems(
|
|
|
279
474
|
? Icon(
|
|
280
475
|
expanded ? Icons.expand_less : Icons.expand_more,
|
|
281
476
|
size: 16,
|
|
282
|
-
color: active ?
|
|
477
|
+
color: active ? _textSecondary : _textMuted,
|
|
283
478
|
)
|
|
284
479
|
: null,
|
|
285
480
|
onTap: hasChildren
|
|
286
|
-
?
|
|
287
|
-
|
|
481
|
+
? group == SidebarGroup.settings
|
|
482
|
+
? () => onSelect(parentSection)
|
|
483
|
+
: () => onToggleGroup(group)
|
|
484
|
+
: () => onSelect(parentSection),
|
|
288
485
|
),
|
|
289
486
|
);
|
|
290
487
|
|
|
@@ -292,19 +489,23 @@ List<Widget> _buildSidebarItems(
|
|
|
292
489
|
continue;
|
|
293
490
|
}
|
|
294
491
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
492
|
+
widgets.add(
|
|
493
|
+
_SidebarSubnav(
|
|
494
|
+
children: childSections
|
|
495
|
+
.map(
|
|
496
|
+
(section) => _SidebarButton(
|
|
497
|
+
label: section.label,
|
|
498
|
+
icon: section.icon,
|
|
499
|
+
active: controller.selectedSection.sidebarSection == section,
|
|
500
|
+
iconSize: 16,
|
|
501
|
+
fontSize: 13,
|
|
502
|
+
compact: true,
|
|
503
|
+
onTap: () => onSelect(section),
|
|
504
|
+
),
|
|
505
|
+
)
|
|
506
|
+
.toList(growable: false),
|
|
507
|
+
),
|
|
508
|
+
);
|
|
308
509
|
}
|
|
309
510
|
return widgets;
|
|
310
511
|
}
|
|
@@ -438,6 +639,53 @@ class _PageTitle extends StatelessWidget {
|
|
|
438
639
|
}
|
|
439
640
|
}
|
|
440
641
|
|
|
642
|
+
IconData _runPhaseIcon(String phase) {
|
|
643
|
+
switch (phase.toLowerCase()) {
|
|
644
|
+
case 'thinking':
|
|
645
|
+
return Icons.psychology_outlined;
|
|
646
|
+
case 'analyzing':
|
|
647
|
+
return Icons.analytics_outlined;
|
|
648
|
+
case 'planning':
|
|
649
|
+
return Icons.list_alt_outlined;
|
|
650
|
+
case 'verifying':
|
|
651
|
+
return Icons.fact_check_outlined;
|
|
652
|
+
case 'streaming':
|
|
653
|
+
case 'responding':
|
|
654
|
+
return Icons.chat_outlined;
|
|
655
|
+
case 'awaiting approval':
|
|
656
|
+
return Icons.lock_clock_outlined;
|
|
657
|
+
case 'incorporating steering':
|
|
658
|
+
return Icons.tune_outlined;
|
|
659
|
+
case 'completed':
|
|
660
|
+
return Icons.check_circle_outline_rounded;
|
|
661
|
+
case 'stopped':
|
|
662
|
+
case 'stopping':
|
|
663
|
+
return Icons.stop_circle_outlined;
|
|
664
|
+
default:
|
|
665
|
+
return Icons.sync_outlined;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
Color _runPhaseColor(String phase) {
|
|
670
|
+
switch (phase.toLowerCase()) {
|
|
671
|
+
case 'thinking':
|
|
672
|
+
case 'analyzing':
|
|
673
|
+
case 'planning':
|
|
674
|
+
return _info;
|
|
675
|
+
case 'verifying':
|
|
676
|
+
return _accentAlt;
|
|
677
|
+
case 'awaiting approval':
|
|
678
|
+
return _warning;
|
|
679
|
+
case 'completed':
|
|
680
|
+
return _success;
|
|
681
|
+
case 'stopped':
|
|
682
|
+
case 'stopping':
|
|
683
|
+
return _textSecondary;
|
|
684
|
+
default:
|
|
685
|
+
return _accent;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
441
689
|
class _RunStatusPanel extends StatelessWidget {
|
|
442
690
|
const _RunStatusPanel({required this.run, required this.tools});
|
|
443
691
|
|
|
@@ -449,6 +697,9 @@ class _RunStatusPanel extends StatelessWidget {
|
|
|
449
697
|
final runningCount = tools.where((tool) => tool.status == 'running').length;
|
|
450
698
|
final helperCount = tools.where((tool) => tool.isHelperRelated).length;
|
|
451
699
|
final webCount = tools.where((tool) => tool.isWebRelated).length;
|
|
700
|
+
final phase = run?.phase ?? '';
|
|
701
|
+
final phaseColor = _runPhaseColor(phase);
|
|
702
|
+
final isDeepRun = (run?.iteration ?? 0) >= 8;
|
|
452
703
|
|
|
453
704
|
return Card(
|
|
454
705
|
child: Padding(
|
|
@@ -458,6 +709,30 @@ class _RunStatusPanel extends StatelessWidget {
|
|
|
458
709
|
children: <Widget>[
|
|
459
710
|
Row(
|
|
460
711
|
children: <Widget>[
|
|
712
|
+
if (phase.isNotEmpty) ...<Widget>[
|
|
713
|
+
_PulseHalo(
|
|
714
|
+
color: phaseColor,
|
|
715
|
+
animate: phase.toLowerCase() != 'completed' &&
|
|
716
|
+
phase.toLowerCase() != 'stopped',
|
|
717
|
+
child: Container(
|
|
718
|
+
width: 30,
|
|
719
|
+
height: 30,
|
|
720
|
+
decoration: BoxDecoration(
|
|
721
|
+
color: phaseColor.withValues(alpha: 0.14),
|
|
722
|
+
shape: BoxShape.circle,
|
|
723
|
+
border: Border.all(
|
|
724
|
+
color: phaseColor.withValues(alpha: 0.28),
|
|
725
|
+
),
|
|
726
|
+
),
|
|
727
|
+
child: Icon(
|
|
728
|
+
_runPhaseIcon(phase),
|
|
729
|
+
size: 16,
|
|
730
|
+
color: phaseColor,
|
|
731
|
+
),
|
|
732
|
+
),
|
|
733
|
+
),
|
|
734
|
+
const SizedBox(width: 12),
|
|
735
|
+
],
|
|
461
736
|
Expanded(
|
|
462
737
|
child: Column(
|
|
463
738
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
@@ -469,7 +744,7 @@ class _RunStatusPanel extends StatelessWidget {
|
|
|
469
744
|
fontWeight: FontWeight.w700,
|
|
470
745
|
),
|
|
471
746
|
),
|
|
472
|
-
const SizedBox(height:
|
|
747
|
+
const SizedBox(height: 4),
|
|
473
748
|
Text(
|
|
474
749
|
run == null
|
|
475
750
|
? 'Waiting for run events...'
|
|
@@ -478,7 +753,12 @@ class _RunStatusPanel extends StatelessWidget {
|
|
|
478
753
|
if (run!.pendingSteeringCount > 0)
|
|
479
754
|
'${run!.pendingSteeringCount} steering ${run!.pendingSteeringCount == 1 ? 'update' : 'updates'} queued',
|
|
480
755
|
].join(' · '),
|
|
481
|
-
style: TextStyle(
|
|
756
|
+
style: TextStyle(
|
|
757
|
+
color: phase.isNotEmpty
|
|
758
|
+
? phaseColor.withValues(alpha: 0.8)
|
|
759
|
+
: _textSecondary,
|
|
760
|
+
fontSize: 12.5,
|
|
761
|
+
),
|
|
482
762
|
),
|
|
483
763
|
],
|
|
484
764
|
),
|
|
@@ -513,6 +793,12 @@ class _RunStatusPanel extends StatelessWidget {
|
|
|
513
793
|
label: '$helperCount helpers',
|
|
514
794
|
icon: Icons.account_tree_outlined,
|
|
515
795
|
),
|
|
796
|
+
if (isDeepRun)
|
|
797
|
+
_MetaPill(
|
|
798
|
+
label: 'deep run · step ${run!.iteration}',
|
|
799
|
+
icon: Icons.warning_amber_outlined,
|
|
800
|
+
color: _warning,
|
|
801
|
+
),
|
|
516
802
|
],
|
|
517
803
|
),
|
|
518
804
|
const SizedBox(height: 14),
|
|
@@ -548,6 +834,7 @@ class _ToolEventTimelineRow extends StatelessWidget {
|
|
|
548
834
|
@override
|
|
549
835
|
Widget build(BuildContext context) {
|
|
550
836
|
Color color;
|
|
837
|
+
final running = tool.status == 'running';
|
|
551
838
|
switch (tool.status) {
|
|
552
839
|
case 'running':
|
|
553
840
|
color = _warning;
|
|
@@ -565,14 +852,26 @@ class _ToolEventTimelineRow extends StatelessWidget {
|
|
|
565
852
|
width: 28,
|
|
566
853
|
child: Column(
|
|
567
854
|
children: <Widget>[
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
855
|
+
_PulseHalo(
|
|
856
|
+
color: color,
|
|
857
|
+
animate: running,
|
|
858
|
+
child: Container(
|
|
859
|
+
width: 28,
|
|
860
|
+
height: 28,
|
|
861
|
+
decoration: BoxDecoration(
|
|
862
|
+
gradient: LinearGradient(
|
|
863
|
+
colors: <Color>[
|
|
864
|
+
color.withValues(alpha: running ? 0.26 : 0.16),
|
|
865
|
+
color.withValues(alpha: 0.08),
|
|
866
|
+
],
|
|
867
|
+
begin: Alignment.topLeft,
|
|
868
|
+
end: Alignment.bottomRight,
|
|
869
|
+
),
|
|
870
|
+
shape: BoxShape.circle,
|
|
871
|
+
border: Border.all(color: color.withValues(alpha: 0.24)),
|
|
872
|
+
),
|
|
873
|
+
child: Icon(tool.laneIcon, size: 16, color: color),
|
|
574
874
|
),
|
|
575
|
-
child: Icon(tool.laneIcon, size: 16, color: color),
|
|
576
875
|
),
|
|
577
876
|
if (!isLast)
|
|
578
877
|
Container(
|
|
@@ -592,9 +891,18 @@ class _ToolEventTimelineRow extends StatelessWidget {
|
|
|
592
891
|
child: Container(
|
|
593
892
|
padding: const EdgeInsets.all(12),
|
|
594
893
|
decoration: BoxDecoration(
|
|
595
|
-
|
|
894
|
+
gradient: LinearGradient(
|
|
895
|
+
colors: <Color>[
|
|
896
|
+
_bgSecondary,
|
|
897
|
+
if (running) color.withValues(alpha: 0.055) else _bgCard,
|
|
898
|
+
],
|
|
899
|
+
begin: Alignment.topLeft,
|
|
900
|
+
end: Alignment.bottomRight,
|
|
901
|
+
),
|
|
596
902
|
borderRadius: BorderRadius.circular(14),
|
|
597
|
-
border: Border.all(
|
|
903
|
+
border: Border.all(
|
|
904
|
+
color: running ? color.withValues(alpha: 0.25) : _border,
|
|
905
|
+
),
|
|
598
906
|
),
|
|
599
907
|
child: Column(
|
|
600
908
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
@@ -770,36 +1078,127 @@ class _DotStatus extends StatelessWidget {
|
|
|
770
1078
|
|
|
771
1079
|
@override
|
|
772
1080
|
Widget build(BuildContext context) {
|
|
773
|
-
return
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1081
|
+
return _PulseHalo(
|
|
1082
|
+
color: color,
|
|
1083
|
+
child: _GlassSurface(
|
|
1084
|
+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
1085
|
+
borderRadius: BorderRadius.circular(999),
|
|
1086
|
+
blurSigma: 16,
|
|
1087
|
+
fillColor: _bgSecondary.withValues(alpha: 0.3),
|
|
1088
|
+
borderColor: color.withValues(alpha: 0.24),
|
|
1089
|
+
child: Row(
|
|
1090
|
+
mainAxisSize: MainAxisSize.min,
|
|
1091
|
+
children: <Widget>[
|
|
1092
|
+
Container(
|
|
1093
|
+
width: 8,
|
|
1094
|
+
height: 8,
|
|
1095
|
+
decoration: BoxDecoration(
|
|
1096
|
+
color: color,
|
|
1097
|
+
shape: BoxShape.circle,
|
|
1098
|
+
boxShadow: <BoxShadow>[
|
|
1099
|
+
BoxShadow(
|
|
1100
|
+
color: color.withValues(alpha: 0.42),
|
|
1101
|
+
blurRadius: 9,
|
|
1102
|
+
spreadRadius: 1,
|
|
1103
|
+
),
|
|
1104
|
+
],
|
|
1105
|
+
),
|
|
1106
|
+
),
|
|
1107
|
+
const SizedBox(width: 8),
|
|
1108
|
+
Text(label),
|
|
1109
|
+
],
|
|
1110
|
+
),
|
|
790
1111
|
),
|
|
791
1112
|
);
|
|
792
1113
|
}
|
|
793
1114
|
}
|
|
794
1115
|
|
|
795
|
-
class
|
|
1116
|
+
class _PulseHalo extends StatefulWidget {
|
|
1117
|
+
const _PulseHalo({
|
|
1118
|
+
required this.child,
|
|
1119
|
+
required this.color,
|
|
1120
|
+
this.animate = true,
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
final Widget child;
|
|
1124
|
+
final Color color;
|
|
1125
|
+
final bool animate;
|
|
1126
|
+
|
|
1127
|
+
@override
|
|
1128
|
+
State<_PulseHalo> createState() => _PulseHaloState();
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
class _PulseHaloState extends State<_PulseHalo>
|
|
1132
|
+
with SingleTickerProviderStateMixin {
|
|
1133
|
+
late final AnimationController _controller;
|
|
1134
|
+
|
|
1135
|
+
@override
|
|
1136
|
+
void initState() {
|
|
1137
|
+
super.initState();
|
|
1138
|
+
_controller = AnimationController(
|
|
1139
|
+
vsync: this,
|
|
1140
|
+
duration: const Duration(milliseconds: 1600),
|
|
1141
|
+
);
|
|
1142
|
+
if (widget.animate) {
|
|
1143
|
+
_controller.repeat(reverse: true);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
@override
|
|
1148
|
+
void didUpdateWidget(_PulseHalo oldWidget) {
|
|
1149
|
+
super.didUpdateWidget(oldWidget);
|
|
1150
|
+
if (widget.animate != oldWidget.animate) {
|
|
1151
|
+
if (widget.animate) {
|
|
1152
|
+
_controller.repeat(reverse: true);
|
|
1153
|
+
} else {
|
|
1154
|
+
_controller.stop();
|
|
1155
|
+
_controller.value = 0.0;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
@override
|
|
1161
|
+
void dispose() {
|
|
1162
|
+
_controller.dispose();
|
|
1163
|
+
super.dispose();
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
@override
|
|
1167
|
+
Widget build(BuildContext context) {
|
|
1168
|
+
if (!widget.animate) {
|
|
1169
|
+
return widget.child;
|
|
1170
|
+
}
|
|
1171
|
+
return AnimatedBuilder(
|
|
1172
|
+
animation: _controller,
|
|
1173
|
+
builder: (context, child) {
|
|
1174
|
+
final t = Curves.easeInOut.transform(_controller.value);
|
|
1175
|
+
return DecoratedBox(
|
|
1176
|
+
decoration: BoxDecoration(
|
|
1177
|
+
borderRadius: BorderRadius.circular(999),
|
|
1178
|
+
boxShadow: <BoxShadow>[
|
|
1179
|
+
BoxShadow(
|
|
1180
|
+
color: widget.color.withValues(alpha: 0.04 + t * 0.08),
|
|
1181
|
+
blurRadius: 12 + t * 10,
|
|
1182
|
+
spreadRadius: t * 1.5,
|
|
1183
|
+
),
|
|
1184
|
+
],
|
|
1185
|
+
),
|
|
1186
|
+
child: child,
|
|
1187
|
+
);
|
|
1188
|
+
},
|
|
1189
|
+
child: widget.child,
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
class _SidebarButton extends StatefulWidget {
|
|
796
1195
|
const _SidebarButton({
|
|
797
1196
|
required this.label,
|
|
798
1197
|
required this.icon,
|
|
799
1198
|
this.active = false,
|
|
800
|
-
this.indent = 0,
|
|
801
1199
|
this.iconSize = 18,
|
|
802
1200
|
this.fontSize = 13,
|
|
1201
|
+
this.compact = false,
|
|
803
1202
|
this.trailing,
|
|
804
1203
|
required this.onTap,
|
|
805
1204
|
});
|
|
@@ -807,89 +1206,108 @@ class _SidebarButton extends StatelessWidget {
|
|
|
807
1206
|
final String label;
|
|
808
1207
|
final IconData icon;
|
|
809
1208
|
final bool active;
|
|
810
|
-
final double indent;
|
|
811
1209
|
final double iconSize;
|
|
812
1210
|
final double fontSize;
|
|
1211
|
+
final bool compact;
|
|
813
1212
|
final Widget? trailing;
|
|
814
1213
|
final VoidCallback? onTap;
|
|
815
1214
|
|
|
1215
|
+
@override
|
|
1216
|
+
State<_SidebarButton> createState() => _SidebarButtonState();
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
class _SidebarButtonState extends State<_SidebarButton> {
|
|
1220
|
+
bool _hovering = false;
|
|
1221
|
+
|
|
816
1222
|
@override
|
|
817
1223
|
Widget build(BuildContext context) {
|
|
1224
|
+
final active = widget.active;
|
|
1225
|
+
final BoxDecoration decoration = active
|
|
1226
|
+
? BoxDecoration(
|
|
1227
|
+
color: _bgCard,
|
|
1228
|
+
borderRadius: BorderRadius.circular(9),
|
|
1229
|
+
border: Border.all(color: _border),
|
|
1230
|
+
boxShadow: <BoxShadow>[
|
|
1231
|
+
BoxShadow(
|
|
1232
|
+
color: Colors.black.withValues(alpha: 0.05),
|
|
1233
|
+
blurRadius: 10,
|
|
1234
|
+
offset: const Offset(0, 2),
|
|
1235
|
+
),
|
|
1236
|
+
],
|
|
1237
|
+
)
|
|
1238
|
+
: BoxDecoration(
|
|
1239
|
+
color: _hovering
|
|
1240
|
+
? _bgTertiary.withValues(alpha: 0.72)
|
|
1241
|
+
: Colors.transparent,
|
|
1242
|
+
borderRadius: BorderRadius.circular(9),
|
|
1243
|
+
);
|
|
818
1244
|
return Padding(
|
|
819
|
-
padding: const EdgeInsets.only(bottom:
|
|
820
|
-
child:
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
:
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
:
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
width: double.infinity,
|
|
849
|
-
padding: EdgeInsets.fromLTRB(12 + indent, 12, 12, 12),
|
|
850
|
-
child: Row(
|
|
851
|
-
children: <Widget>[
|
|
852
|
-
if (active)
|
|
853
|
-
Container(
|
|
854
|
-
width: 6,
|
|
855
|
-
height: 26,
|
|
856
|
-
margin: const EdgeInsets.only(right: 10),
|
|
1245
|
+
padding: const EdgeInsets.only(bottom: 2),
|
|
1246
|
+
child: MouseRegion(
|
|
1247
|
+
onEnter: (_) => setState(() => _hovering = true),
|
|
1248
|
+
onExit: (_) => setState(() => _hovering = false),
|
|
1249
|
+
child: Material(
|
|
1250
|
+
color: Colors.transparent,
|
|
1251
|
+
child: InkWell(
|
|
1252
|
+
borderRadius: BorderRadius.circular(9),
|
|
1253
|
+
onTap: widget.onTap,
|
|
1254
|
+
child: AnimatedContainer(
|
|
1255
|
+
duration: const Duration(milliseconds: 180),
|
|
1256
|
+
curve: Curves.easeOutCubic,
|
|
1257
|
+
width: double.infinity,
|
|
1258
|
+
padding: EdgeInsets.fromLTRB(
|
|
1259
|
+
12,
|
|
1260
|
+
widget.compact ? 8 : 10,
|
|
1261
|
+
12,
|
|
1262
|
+
widget.compact ? 8 : 10,
|
|
1263
|
+
),
|
|
1264
|
+
decoration: decoration,
|
|
1265
|
+
child: Stack(
|
|
1266
|
+
alignment: Alignment.centerLeft,
|
|
1267
|
+
children: <Widget>[
|
|
1268
|
+
if (active)
|
|
1269
|
+
Positioned(
|
|
1270
|
+
left: -12,
|
|
1271
|
+
child: Container(
|
|
1272
|
+
width: 3,
|
|
1273
|
+
height: 18,
|
|
857
1274
|
decoration: BoxDecoration(
|
|
1275
|
+
borderRadius: BorderRadius.circular(999),
|
|
858
1276
|
gradient: LinearGradient(
|
|
859
|
-
colors: <Color>[
|
|
860
|
-
_accentHover,
|
|
861
|
-
_accentAlt.withValues(alpha: 0.9),
|
|
862
|
-
],
|
|
1277
|
+
colors: <Color>[_accentAlt, _accent],
|
|
863
1278
|
begin: Alignment.topCenter,
|
|
864
1279
|
end: Alignment.bottomCenter,
|
|
865
1280
|
),
|
|
866
|
-
borderRadius: BorderRadius.circular(999),
|
|
867
1281
|
),
|
|
868
1282
|
),
|
|
869
|
-
Icon(
|
|
870
|
-
icon,
|
|
871
|
-
size: iconSize,
|
|
872
|
-
color: active ? _accentHover : _textSecondary,
|
|
873
1283
|
),
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1284
|
+
Row(
|
|
1285
|
+
children: <Widget>[
|
|
1286
|
+
Icon(
|
|
1287
|
+
widget.icon,
|
|
1288
|
+
size: widget.iconSize,
|
|
1289
|
+
color: active ? _accent : _textMuted,
|
|
1290
|
+
),
|
|
1291
|
+
const SizedBox(width: 11),
|
|
1292
|
+
Expanded(
|
|
1293
|
+
child: Text(
|
|
1294
|
+
widget.label,
|
|
1295
|
+
maxLines: 1,
|
|
1296
|
+
overflow: TextOverflow.ellipsis,
|
|
1297
|
+
style: GoogleFonts.geist(
|
|
1298
|
+
fontSize: widget.fontSize,
|
|
1299
|
+
fontWeight: FontWeight.w600,
|
|
1300
|
+
color: active ? _textPrimary : _textSecondary,
|
|
1301
|
+
),
|
|
884
1302
|
),
|
|
885
1303
|
),
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1304
|
+
if (widget.trailing != null) ...<Widget>[
|
|
1305
|
+
const SizedBox(width: 8),
|
|
1306
|
+
widget.trailing!,
|
|
1307
|
+
],
|
|
890
1308
|
],
|
|
891
|
-
|
|
892
|
-
|
|
1309
|
+
),
|
|
1310
|
+
],
|
|
893
1311
|
),
|
|
894
1312
|
),
|
|
895
1313
|
),
|
|
@@ -899,6 +1317,24 @@ class _SidebarButton extends StatelessWidget {
|
|
|
899
1317
|
}
|
|
900
1318
|
}
|
|
901
1319
|
|
|
1320
|
+
class _SidebarSubnav extends StatelessWidget {
|
|
1321
|
+
const _SidebarSubnav({required this.children});
|
|
1322
|
+
|
|
1323
|
+
final List<Widget> children;
|
|
1324
|
+
|
|
1325
|
+
@override
|
|
1326
|
+
Widget build(BuildContext context) {
|
|
1327
|
+
return Container(
|
|
1328
|
+
margin: const EdgeInsets.fromLTRB(14, 2, 0, 2),
|
|
1329
|
+
padding: const EdgeInsets.only(left: 12),
|
|
1330
|
+
decoration: BoxDecoration(
|
|
1331
|
+
border: Border(left: BorderSide(color: _border)),
|
|
1332
|
+
),
|
|
1333
|
+
child: Column(children: children),
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
902
1338
|
class _SidebarIconButton extends StatelessWidget {
|
|
903
1339
|
const _SidebarIconButton({
|
|
904
1340
|
required this.tooltip,
|
|
@@ -914,21 +1350,21 @@ class _SidebarIconButton extends StatelessWidget {
|
|
|
914
1350
|
Widget build(BuildContext context) {
|
|
915
1351
|
return Tooltip(
|
|
916
1352
|
message: tooltip,
|
|
917
|
-
child:
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
child: Icon(icon, size: 17, color: _textSecondary),
|
|
1353
|
+
child: Material(
|
|
1354
|
+
color: Colors.transparent,
|
|
1355
|
+
borderRadius: BorderRadius.circular(9),
|
|
1356
|
+
child: InkWell(
|
|
1357
|
+
borderRadius: BorderRadius.circular(9),
|
|
1358
|
+
onTap: onTap,
|
|
1359
|
+
child: Container(
|
|
1360
|
+
width: 32,
|
|
1361
|
+
height: 32,
|
|
1362
|
+
decoration: BoxDecoration(
|
|
1363
|
+
borderRadius: BorderRadius.circular(9),
|
|
1364
|
+
color: _bgSecondary.withValues(alpha: 0.01),
|
|
1365
|
+
border: Border.all(color: Colors.transparent),
|
|
931
1366
|
),
|
|
1367
|
+
child: Icon(icon, size: 17, color: _textSecondary),
|
|
932
1368
|
),
|
|
933
1369
|
),
|
|
934
1370
|
),
|
|
@@ -936,63 +1372,87 @@ class _SidebarIconButton extends StatelessWidget {
|
|
|
936
1372
|
}
|
|
937
1373
|
}
|
|
938
1374
|
|
|
939
|
-
class
|
|
940
|
-
const
|
|
1375
|
+
class _LogoBadge extends StatefulWidget {
|
|
1376
|
+
const _LogoBadge({required this.size});
|
|
941
1377
|
|
|
942
1378
|
final double size;
|
|
943
|
-
final Color color;
|
|
944
1379
|
|
|
945
1380
|
@override
|
|
946
|
-
|
|
947
|
-
return IgnorePointer(
|
|
948
|
-
child: Container(
|
|
949
|
-
width: size,
|
|
950
|
-
height: size,
|
|
951
|
-
decoration: BoxDecoration(
|
|
952
|
-
shape: BoxShape.circle,
|
|
953
|
-
boxShadow: <BoxShadow>[
|
|
954
|
-
BoxShadow(
|
|
955
|
-
color: color.withValues(alpha: 0.18),
|
|
956
|
-
blurRadius: 120,
|
|
957
|
-
spreadRadius: 30,
|
|
958
|
-
),
|
|
959
|
-
],
|
|
960
|
-
),
|
|
961
|
-
),
|
|
962
|
-
);
|
|
963
|
-
}
|
|
1381
|
+
State<_LogoBadge> createState() => _LogoBadgeState();
|
|
964
1382
|
}
|
|
965
1383
|
|
|
966
|
-
class
|
|
967
|
-
|
|
1384
|
+
class _LogoBadgeState extends State<_LogoBadge> {
|
|
1385
|
+
int _tapCount = 0;
|
|
1386
|
+
Timer? _tapResetTimer;
|
|
1387
|
+
Timer? _partyResetTimer;
|
|
968
1388
|
|
|
969
|
-
|
|
1389
|
+
@override
|
|
1390
|
+
void dispose() {
|
|
1391
|
+
_tapResetTimer?.cancel();
|
|
1392
|
+
if (_partyResetTimer != null && _partyResetTimer!.isActive) {
|
|
1393
|
+
_partyResetTimer?.cancel();
|
|
1394
|
+
_partyModeEnabled.value = false;
|
|
1395
|
+
}
|
|
1396
|
+
super.dispose();
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
void _handleTap() {
|
|
1400
|
+
_tapResetTimer?.cancel();
|
|
1401
|
+
_tapCount += 1;
|
|
1402
|
+
if (_tapCount >= 5) {
|
|
1403
|
+
_tapCount = 0;
|
|
1404
|
+
_partyModeEnabled.value = true;
|
|
1405
|
+
_partyResetTimer?.cancel();
|
|
1406
|
+
_partyResetTimer = Timer(const Duration(seconds: 7), () {
|
|
1407
|
+
_partyModeEnabled.value = false;
|
|
1408
|
+
});
|
|
1409
|
+
} else {
|
|
1410
|
+
_tapResetTimer = Timer(const Duration(milliseconds: 1250), () {
|
|
1411
|
+
_tapCount = 0;
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
970
1415
|
|
|
971
1416
|
@override
|
|
972
1417
|
Widget build(BuildContext context) {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1418
|
+
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
1419
|
+
return ValueListenableBuilder<bool>(
|
|
1420
|
+
valueListenable: _partyModeEnabled,
|
|
1421
|
+
builder: (context, partyMode, _) {
|
|
1422
|
+
return GestureDetector(
|
|
1423
|
+
onTap: _handleTap,
|
|
1424
|
+
child: AnimatedScale(
|
|
1425
|
+
duration: const Duration(milliseconds: 240),
|
|
1426
|
+
curve: Curves.easeOutBack,
|
|
1427
|
+
scale: partyMode ? 1.08 : 1,
|
|
1428
|
+
child: AnimatedContainer(
|
|
1429
|
+
duration: const Duration(milliseconds: 260),
|
|
1430
|
+
width: widget.size,
|
|
1431
|
+
height: widget.size,
|
|
1432
|
+
decoration: BoxDecoration(
|
|
1433
|
+
borderRadius: BorderRadius.circular(widget.size * 0.24),
|
|
1434
|
+
boxShadow: <BoxShadow>[
|
|
1435
|
+
BoxShadow(
|
|
1436
|
+
color: (partyMode ? _accentAlt : _accent).withValues(
|
|
1437
|
+
alpha: partyMode ? 0.34 : 0.16,
|
|
1438
|
+
),
|
|
1439
|
+
blurRadius: partyMode ? 28 : 14,
|
|
1440
|
+
spreadRadius: partyMode ? 1 : 0,
|
|
1441
|
+
),
|
|
1442
|
+
],
|
|
1443
|
+
),
|
|
1444
|
+
child: Image.asset(
|
|
1445
|
+
isDark
|
|
1446
|
+
? 'assets/branding/app_icon_1024.png'
|
|
1447
|
+
: 'assets/branding/app_icon_light_1024.png',
|
|
1448
|
+
width: widget.size,
|
|
1449
|
+
height: widget.size,
|
|
1450
|
+
filterQuality: FilterQuality.high,
|
|
1451
|
+
),
|
|
1452
|
+
),
|
|
988
1453
|
),
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
),
|
|
992
|
-
child: Padding(
|
|
993
|
-
padding: EdgeInsets.all(size * 0.18),
|
|
994
|
-
child: CustomPaint(painter: _NeoAgentLogoPainter()),
|
|
995
|
-
),
|
|
1454
|
+
);
|
|
1455
|
+
},
|
|
996
1456
|
);
|
|
997
1457
|
}
|
|
998
1458
|
}
|
|
@@ -1014,12 +1474,13 @@ class _BrandLockup extends StatelessWidget {
|
|
|
1014
1474
|
|
|
1015
1475
|
@override
|
|
1016
1476
|
Widget build(BuildContext context) {
|
|
1477
|
+
final titleColor = Theme.of(context).colorScheme.onSurface;
|
|
1017
1478
|
final title = Text(
|
|
1018
|
-
'
|
|
1019
|
-
style: GoogleFonts.
|
|
1479
|
+
'NeoAgent',
|
|
1480
|
+
style: GoogleFonts.geist(
|
|
1020
1481
|
fontSize: titleFontSize,
|
|
1021
1482
|
fontWeight: FontWeight.w700,
|
|
1022
|
-
color:
|
|
1483
|
+
color: titleColor,
|
|
1023
1484
|
letterSpacing: -0.4,
|
|
1024
1485
|
),
|
|
1025
1486
|
);
|
|
@@ -1047,44 +1508,6 @@ class _BrandLockup extends StatelessWidget {
|
|
|
1047
1508
|
}
|
|
1048
1509
|
}
|
|
1049
1510
|
|
|
1050
|
-
class _NeoAgentLogoPainter extends CustomPainter {
|
|
1051
|
-
@override
|
|
1052
|
-
void paint(Canvas canvas, Size size) {
|
|
1053
|
-
final fillPaint = Paint()
|
|
1054
|
-
..color = Colors.white
|
|
1055
|
-
..style = PaintingStyle.fill;
|
|
1056
|
-
final strokePaint = Paint()
|
|
1057
|
-
..color = Colors.white
|
|
1058
|
-
..style = PaintingStyle.stroke
|
|
1059
|
-
..strokeWidth = size.width * 0.08
|
|
1060
|
-
..strokeCap = StrokeCap.round
|
|
1061
|
-
..strokeJoin = StrokeJoin.round;
|
|
1062
|
-
|
|
1063
|
-
final top = Path()
|
|
1064
|
-
..moveTo(size.width * 0.5, size.height * 0.08)
|
|
1065
|
-
..lineTo(size.width * 0.1, size.height * 0.3)
|
|
1066
|
-
..lineTo(size.width * 0.5, size.height * 0.52)
|
|
1067
|
-
..lineTo(size.width * 0.9, size.height * 0.3)
|
|
1068
|
-
..close();
|
|
1069
|
-
canvas.drawPath(top, fillPaint);
|
|
1070
|
-
|
|
1071
|
-
final middle = Path()
|
|
1072
|
-
..moveTo(size.width * 0.1, size.height * 0.52)
|
|
1073
|
-
..lineTo(size.width * 0.5, size.height * 0.74)
|
|
1074
|
-
..lineTo(size.width * 0.9, size.height * 0.52);
|
|
1075
|
-
canvas.drawPath(middle, strokePaint);
|
|
1076
|
-
|
|
1077
|
-
final bottom = Path()
|
|
1078
|
-
..moveTo(size.width * 0.1, size.height * 0.72)
|
|
1079
|
-
..lineTo(size.width * 0.5, size.height * 0.94)
|
|
1080
|
-
..lineTo(size.width * 0.9, size.height * 0.72);
|
|
1081
|
-
canvas.drawPath(bottom, strokePaint);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
@override
|
|
1085
|
-
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
1511
|
class _EmptyState extends StatelessWidget {
|
|
1089
1512
|
const _EmptyState({required this.title, required this.subtitle});
|
|
1090
1513
|
|
|
@@ -1096,7 +1519,27 @@ class _EmptyState extends StatelessWidget {
|
|
|
1096
1519
|
return Column(
|
|
1097
1520
|
mainAxisSize: MainAxisSize.min,
|
|
1098
1521
|
children: <Widget>[
|
|
1099
|
-
|
|
1522
|
+
Stack(
|
|
1523
|
+
alignment: Alignment.center,
|
|
1524
|
+
children: <Widget>[
|
|
1525
|
+
Container(
|
|
1526
|
+
width: 74,
|
|
1527
|
+
height: 74,
|
|
1528
|
+
decoration: BoxDecoration(
|
|
1529
|
+
shape: BoxShape.circle,
|
|
1530
|
+
gradient: SweepGradient(
|
|
1531
|
+
colors: <Color>[
|
|
1532
|
+
_accent.withValues(alpha: 0.18),
|
|
1533
|
+
_accentAlt.withValues(alpha: 0.28),
|
|
1534
|
+
Colors.transparent,
|
|
1535
|
+
_accent.withValues(alpha: 0.18),
|
|
1536
|
+
],
|
|
1537
|
+
),
|
|
1538
|
+
),
|
|
1539
|
+
),
|
|
1540
|
+
const _LogoBadge(size: 52),
|
|
1541
|
+
],
|
|
1542
|
+
),
|
|
1100
1543
|
const SizedBox(height: 12),
|
|
1101
1544
|
Text(
|
|
1102
1545
|
title,
|
|
@@ -1121,10 +1564,15 @@ class _EmptyState extends StatelessWidget {
|
|
|
1121
1564
|
}
|
|
1122
1565
|
|
|
1123
1566
|
class _ChatBubble extends StatelessWidget {
|
|
1124
|
-
const _ChatBubble({
|
|
1567
|
+
const _ChatBubble({
|
|
1568
|
+
required this.entry,
|
|
1569
|
+
this.onLoadRunDetail,
|
|
1570
|
+
this.onSendMessage,
|
|
1571
|
+
});
|
|
1125
1572
|
|
|
1126
1573
|
final ChatEntry entry;
|
|
1127
1574
|
final Future<RunDetailSnapshot> Function(String runId)? onLoadRunDetail;
|
|
1575
|
+
final void Function(String)? onSendMessage;
|
|
1128
1576
|
|
|
1129
1577
|
@override
|
|
1130
1578
|
Widget build(BuildContext context) {
|
|
@@ -1155,162 +1603,189 @@ class _ChatBubble extends StatelessWidget {
|
|
|
1155
1603
|
const SizedBox(width: 12),
|
|
1156
1604
|
],
|
|
1157
1605
|
Flexible(
|
|
1158
|
-
child:
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
bottomRight: Radius.circular(isUser ? 4 : 14),
|
|
1606
|
+
child: ConstrainedBox(
|
|
1607
|
+
constraints: const BoxConstraints(maxWidth: 672),
|
|
1608
|
+
child: AnimatedContainer(
|
|
1609
|
+
duration: const Duration(milliseconds: 220),
|
|
1610
|
+
curve: Curves.easeOutCubic,
|
|
1611
|
+
padding: const EdgeInsets.symmetric(
|
|
1612
|
+
horizontal: 20,
|
|
1613
|
+
vertical: 16,
|
|
1167
1614
|
),
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
?
|
|
1182
|
-
:
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1615
|
+
decoration: BoxDecoration(
|
|
1616
|
+
gradient: isUser
|
|
1617
|
+
? LinearGradient(
|
|
1618
|
+
colors: <Color>[
|
|
1619
|
+
_accentAlt,
|
|
1620
|
+
Color.lerp(_accent, _accentAlt, 0.28)!,
|
|
1621
|
+
],
|
|
1622
|
+
begin: Alignment.topLeft,
|
|
1623
|
+
end: Alignment.bottomRight,
|
|
1624
|
+
)
|
|
1625
|
+
: null,
|
|
1626
|
+
color: isUser ? null : _bgCard,
|
|
1627
|
+
borderRadius: BorderRadius.only(
|
|
1628
|
+
topLeft: Radius.circular(isUser ? 21 : 8),
|
|
1629
|
+
topRight: Radius.circular(isUser ? 8 : 21),
|
|
1630
|
+
bottomLeft: const Radius.circular(21),
|
|
1631
|
+
bottomRight: const Radius.circular(21),
|
|
1632
|
+
),
|
|
1633
|
+
border: Border.all(
|
|
1634
|
+
color: isUser ? Colors.transparent : _border,
|
|
1635
|
+
),
|
|
1636
|
+
boxShadow: <BoxShadow>[
|
|
1637
|
+
BoxShadow(
|
|
1638
|
+
color: Colors.black.withValues(alpha: 0.05),
|
|
1639
|
+
blurRadius: 10,
|
|
1640
|
+
offset: const Offset(0, 2),
|
|
1191
1641
|
),
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
backgroundColor: _bgPrimary,
|
|
1206
|
-
color: isUser ? Colors.white : _textPrimary,
|
|
1207
|
-
),
|
|
1208
|
-
blockquoteDecoration: BoxDecoration(
|
|
1209
|
-
borderRadius: BorderRadius.circular(14),
|
|
1210
|
-
color: const Color(0x22000000),
|
|
1211
|
-
),
|
|
1642
|
+
],
|
|
1643
|
+
),
|
|
1644
|
+
child: Column(
|
|
1645
|
+
crossAxisAlignment: isUser
|
|
1646
|
+
? CrossAxisAlignment.end
|
|
1647
|
+
: CrossAxisAlignment.start,
|
|
1648
|
+
children: <Widget>[
|
|
1649
|
+
if (!isUser && entry.platformTag != null)
|
|
1650
|
+
Padding(
|
|
1651
|
+
padding: const EdgeInsets.only(bottom: 8),
|
|
1652
|
+
child: _StatusPill(
|
|
1653
|
+
label: entry.platformTag!,
|
|
1654
|
+
color: entry.platform == 'live' ? _info : _warning,
|
|
1212
1655
|
),
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
.
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
)
|
|
1233
|
-
? Icons.audiotrack_outlined
|
|
1234
|
-
: Icons.attach_file_rounded;
|
|
1235
|
-
return Container(
|
|
1236
|
-
padding: const EdgeInsets.symmetric(
|
|
1237
|
-
horizontal: 10,
|
|
1238
|
-
vertical: 7,
|
|
1239
|
-
),
|
|
1240
|
-
decoration: BoxDecoration(
|
|
1241
|
-
color: isUser
|
|
1242
|
-
? const Color(0x1FFFFFFF)
|
|
1243
|
-
: _bgSecondary,
|
|
1244
|
-
borderRadius: BorderRadius.circular(999),
|
|
1245
|
-
border: Border.all(
|
|
1246
|
-
color: isUser
|
|
1247
|
-
? const Color(0x40FFFFFF)
|
|
1248
|
-
: _border,
|
|
1656
|
+
),
|
|
1657
|
+
MarkdownBody(
|
|
1658
|
+
data: entry.content,
|
|
1659
|
+
selectable: false,
|
|
1660
|
+
styleSheet:
|
|
1661
|
+
MarkdownStyleSheet.fromTheme(
|
|
1662
|
+
Theme.of(context),
|
|
1663
|
+
).copyWith(
|
|
1664
|
+
p: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
1665
|
+
color: isUser ? Colors.white : _textPrimary,
|
|
1666
|
+
fontSize: 14.5,
|
|
1667
|
+
height: 1.62,
|
|
1668
|
+
),
|
|
1669
|
+
code: Theme.of(context).textTheme.bodyMedium
|
|
1670
|
+
?.copyWith(
|
|
1671
|
+
fontFamily:
|
|
1672
|
+
GoogleFonts.geistMono().fontFamily,
|
|
1673
|
+
backgroundColor: _bgPrimary,
|
|
1674
|
+
color: isUser ? Colors.white : _textPrimary,
|
|
1249
1675
|
),
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
icon,
|
|
1256
|
-
size: 14,
|
|
1257
|
-
color: isUser
|
|
1258
|
-
? Colors.white
|
|
1259
|
-
: _textSecondary,
|
|
1260
|
-
),
|
|
1261
|
-
const SizedBox(width: 6),
|
|
1262
|
-
ConstrainedBox(
|
|
1263
|
-
constraints: const BoxConstraints(
|
|
1264
|
-
maxWidth: 180,
|
|
1265
|
-
),
|
|
1266
|
-
child: Text(
|
|
1267
|
-
attachment.name,
|
|
1268
|
-
maxLines: 1,
|
|
1269
|
-
overflow: TextOverflow.ellipsis,
|
|
1270
|
-
style: TextStyle(
|
|
1271
|
-
color: isUser
|
|
1272
|
-
? Colors.white
|
|
1273
|
-
: _textPrimary,
|
|
1274
|
-
fontSize: 12,
|
|
1275
|
-
),
|
|
1276
|
-
),
|
|
1277
|
-
),
|
|
1278
|
-
],
|
|
1279
|
-
),
|
|
1280
|
-
);
|
|
1281
|
-
})
|
|
1282
|
-
.toList(growable: false),
|
|
1676
|
+
blockquoteDecoration: BoxDecoration(
|
|
1677
|
+
borderRadius: BorderRadius.circular(14),
|
|
1678
|
+
color: const Color(0x22000000),
|
|
1679
|
+
),
|
|
1680
|
+
),
|
|
1283
1681
|
),
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1682
|
+
if (sharedAttachments.isNotEmpty) ...<Widget>[
|
|
1683
|
+
const SizedBox(height: 10),
|
|
1684
|
+
_SharedAttachmentChipWrap(
|
|
1685
|
+
attachments: sharedAttachments,
|
|
1686
|
+
isUser: isUser,
|
|
1687
|
+
),
|
|
1688
|
+
],
|
|
1689
|
+
if (!isUser &&
|
|
1690
|
+
entry.runId?.trim().isNotEmpty == true) ...<Widget>[
|
|
1691
|
+
const SizedBox(height: 12),
|
|
1692
|
+
_MessageRunPreview(
|
|
1693
|
+
runId: entry.runId!.trim(),
|
|
1694
|
+
onLoadRunDetail: onLoadRunDetail,
|
|
1695
|
+
),
|
|
1696
|
+
],
|
|
1697
|
+
if (!isUser &&
|
|
1698
|
+
entry.richPayload != null &&
|
|
1699
|
+
onSendMessage != null) ...<Widget>[
|
|
1700
|
+
const SizedBox(height: 12),
|
|
1701
|
+
_QuickReplyBar(
|
|
1702
|
+
payload: entry.richPayload!,
|
|
1703
|
+
onSelected: onSendMessage!,
|
|
1704
|
+
),
|
|
1705
|
+
],
|
|
1706
|
+
const SizedBox(height: 10),
|
|
1707
|
+
Text(
|
|
1708
|
+
entry.createdAtLabel,
|
|
1709
|
+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
1710
|
+
color: isUser
|
|
1711
|
+
? const Color(0xCCFFFFFF)
|
|
1712
|
+
: _textSecondary,
|
|
1713
|
+
),
|
|
1291
1714
|
),
|
|
1292
1715
|
],
|
|
1293
|
-
|
|
1294
|
-
Text(
|
|
1295
|
-
entry.createdAtLabel,
|
|
1296
|
-
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
1297
|
-
color: isUser ? const Color(0xCCFFFFFF) : _textSecondary,
|
|
1298
|
-
),
|
|
1299
|
-
),
|
|
1300
|
-
],
|
|
1716
|
+
),
|
|
1301
1717
|
),
|
|
1302
1718
|
),
|
|
1303
1719
|
),
|
|
1304
|
-
if (isUser) ...<Widget>[
|
|
1305
|
-
const SizedBox(width: 12),
|
|
1306
|
-
const _MessageAvatar(assistant: false),
|
|
1307
|
-
],
|
|
1308
1720
|
],
|
|
1309
1721
|
),
|
|
1310
1722
|
);
|
|
1311
1723
|
}
|
|
1312
1724
|
}
|
|
1313
1725
|
|
|
1726
|
+
class _SharedAttachmentChipWrap extends StatelessWidget {
|
|
1727
|
+
const _SharedAttachmentChipWrap({
|
|
1728
|
+
required this.attachments,
|
|
1729
|
+
required this.isUser,
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
final List<SharedChatAttachment> attachments;
|
|
1733
|
+
final bool isUser;
|
|
1734
|
+
|
|
1735
|
+
@override
|
|
1736
|
+
Widget build(BuildContext context) {
|
|
1737
|
+
return Wrap(
|
|
1738
|
+
spacing: 8,
|
|
1739
|
+
runSpacing: 8,
|
|
1740
|
+
children: attachments
|
|
1741
|
+
.map((attachment) {
|
|
1742
|
+
final mimeType = attachment.mimeType.toLowerCase();
|
|
1743
|
+
final icon = mimeType.startsWith('video/')
|
|
1744
|
+
? Icons.videocam_outlined
|
|
1745
|
+
: mimeType.startsWith('image/')
|
|
1746
|
+
? Icons.image_outlined
|
|
1747
|
+
: mimeType.startsWith('audio/')
|
|
1748
|
+
? Icons.audiotrack_outlined
|
|
1749
|
+
: Icons.attach_file_rounded;
|
|
1750
|
+
return Container(
|
|
1751
|
+
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
|
|
1752
|
+
decoration: BoxDecoration(
|
|
1753
|
+
color: isUser ? const Color(0x1FFFFFFF) : _bgSecondary,
|
|
1754
|
+
borderRadius: BorderRadius.circular(999),
|
|
1755
|
+
border: Border.all(
|
|
1756
|
+
color: isUser ? const Color(0x40FFFFFF) : _border,
|
|
1757
|
+
),
|
|
1758
|
+
),
|
|
1759
|
+
child: Row(
|
|
1760
|
+
mainAxisSize: MainAxisSize.min,
|
|
1761
|
+
children: <Widget>[
|
|
1762
|
+
Icon(
|
|
1763
|
+
icon,
|
|
1764
|
+
size: 14,
|
|
1765
|
+
color: isUser ? Colors.white : _textSecondary,
|
|
1766
|
+
),
|
|
1767
|
+
const SizedBox(width: 6),
|
|
1768
|
+
ConstrainedBox(
|
|
1769
|
+
constraints: const BoxConstraints(maxWidth: 180),
|
|
1770
|
+
child: Text(
|
|
1771
|
+
attachment.name,
|
|
1772
|
+
maxLines: 1,
|
|
1773
|
+
overflow: TextOverflow.ellipsis,
|
|
1774
|
+
style: TextStyle(
|
|
1775
|
+
color: isUser ? Colors.white : _textPrimary,
|
|
1776
|
+
fontSize: 12,
|
|
1777
|
+
),
|
|
1778
|
+
),
|
|
1779
|
+
),
|
|
1780
|
+
],
|
|
1781
|
+
),
|
|
1782
|
+
);
|
|
1783
|
+
})
|
|
1784
|
+
.toList(growable: false),
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1314
1789
|
class _MessageRunPreview extends StatefulWidget {
|
|
1315
1790
|
const _MessageRunPreview({
|
|
1316
1791
|
required this.runId,
|
|
@@ -1557,29 +2032,22 @@ class _MessageAvatar extends StatelessWidget {
|
|
|
1557
2032
|
@override
|
|
1558
2033
|
Widget build(BuildContext context) {
|
|
1559
2034
|
return Container(
|
|
1560
|
-
width: 30,
|
|
1561
|
-
height: 30,
|
|
2035
|
+
width: assistant ? 34 : 30,
|
|
2036
|
+
height: assistant ? 34 : 30,
|
|
1562
2037
|
decoration: BoxDecoration(
|
|
1563
|
-
borderRadius: BorderRadius.circular(
|
|
1564
|
-
gradient: assistant
|
|
1565
|
-
? LinearGradient(colors: <Color>[_accent, _accentAlt])
|
|
1566
|
-
: null,
|
|
2038
|
+
borderRadius: BorderRadius.circular(assistant ? 10 : 999),
|
|
1567
2039
|
color: assistant ? null : _bgTertiary,
|
|
1568
|
-
|
|
1569
|
-
? <BoxShadow>[
|
|
1570
|
-
BoxShadow(
|
|
1571
|
-
color: _accentAlt.withValues(alpha: 0.35),
|
|
1572
|
-
blurRadius: 10,
|
|
1573
|
-
offset: const Offset(0, 2),
|
|
1574
|
-
),
|
|
1575
|
-
]
|
|
1576
|
-
: null,
|
|
1577
|
-
),
|
|
1578
|
-
child: Icon(
|
|
1579
|
-
assistant ? Icons.auto_awesome : Icons.person,
|
|
1580
|
-
size: 16,
|
|
1581
|
-
color: assistant ? Colors.white : _textSecondary,
|
|
2040
|
+
border: assistant ? null : Border.all(color: _borderLight),
|
|
1582
2041
|
),
|
|
2042
|
+
clipBehavior: Clip.antiAlias,
|
|
2043
|
+
child: assistant
|
|
2044
|
+
? Image.asset(
|
|
2045
|
+
MediaQuery.platformBrightnessOf(context) == Brightness.dark
|
|
2046
|
+
? 'assets/branding/app_icon_1024.png'
|
|
2047
|
+
: 'assets/branding/app_icon_light_1024.png',
|
|
2048
|
+
filterQuality: FilterQuality.high,
|
|
2049
|
+
)
|
|
2050
|
+
: Icon(Icons.person, size: 16, color: _textSecondary),
|
|
1583
2051
|
);
|
|
1584
2052
|
}
|
|
1585
2053
|
}
|
|
@@ -1597,8 +2065,22 @@ class _StatusPill extends StatelessWidget {
|
|
|
1597
2065
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
1598
2066
|
decoration: BoxDecoration(
|
|
1599
2067
|
borderRadius: BorderRadius.circular(999),
|
|
1600
|
-
|
|
1601
|
-
|
|
2068
|
+
gradient: LinearGradient(
|
|
2069
|
+
colors: <Color>[
|
|
2070
|
+
color.withValues(alpha: 0.2),
|
|
2071
|
+
color.withValues(alpha: 0.08),
|
|
2072
|
+
],
|
|
2073
|
+
begin: Alignment.topLeft,
|
|
2074
|
+
end: Alignment.bottomRight,
|
|
2075
|
+
),
|
|
2076
|
+
border: Border.all(color: color.withValues(alpha: 0.28)),
|
|
2077
|
+
boxShadow: <BoxShadow>[
|
|
2078
|
+
BoxShadow(
|
|
2079
|
+
color: color.withValues(alpha: 0.08),
|
|
2080
|
+
blurRadius: 12,
|
|
2081
|
+
offset: const Offset(0, 4),
|
|
2082
|
+
),
|
|
2083
|
+
],
|
|
1602
2084
|
),
|
|
1603
2085
|
child: Text(
|
|
1604
2086
|
label,
|
|
@@ -3252,6 +3734,16 @@ int _asInt(dynamic value) {
|
|
|
3252
3734
|
return int.tryParse(value?.toString() ?? '') ?? 0;
|
|
3253
3735
|
}
|
|
3254
3736
|
|
|
3737
|
+
double _asDouble(dynamic value, {double fallback = 0}) {
|
|
3738
|
+
if (value is double) {
|
|
3739
|
+
return value;
|
|
3740
|
+
}
|
|
3741
|
+
if (value is int) {
|
|
3742
|
+
return value.toDouble();
|
|
3743
|
+
}
|
|
3744
|
+
return double.tryParse(value?.toString() ?? '') ?? fallback;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3255
3747
|
DateTime _parseTimestamp(String? raw) {
|
|
3256
3748
|
if (raw == null || raw.isEmpty) {
|
|
3257
3749
|
return DateTime.now();
|
|
@@ -3364,6 +3856,42 @@ String _summarizeToolResult(dynamic raw) {
|
|
|
3364
3856
|
return text.length > 140 ? '${text.substring(0, 140)}…' : text;
|
|
3365
3857
|
}
|
|
3366
3858
|
|
|
3859
|
+
class _QuickReplyBar extends StatelessWidget {
|
|
3860
|
+
const _QuickReplyBar({required this.payload, required this.onSelected});
|
|
3861
|
+
|
|
3862
|
+
final ChatRichPayload payload;
|
|
3863
|
+
final void Function(String value) onSelected;
|
|
3864
|
+
|
|
3865
|
+
@override
|
|
3866
|
+
Widget build(BuildContext context) {
|
|
3867
|
+
return Wrap(
|
|
3868
|
+
spacing: 8,
|
|
3869
|
+
runSpacing: 8,
|
|
3870
|
+
children: payload.options.map((option) {
|
|
3871
|
+
return GestureDetector(
|
|
3872
|
+
onTap: () => onSelected(option.value),
|
|
3873
|
+
child: Container(
|
|
3874
|
+
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
|
3875
|
+
decoration: BoxDecoration(
|
|
3876
|
+
color: _accentMuted,
|
|
3877
|
+
borderRadius: BorderRadius.circular(999),
|
|
3878
|
+
border: Border.all(color: _accent.withValues(alpha: 0.4)),
|
|
3879
|
+
),
|
|
3880
|
+
child: Text(
|
|
3881
|
+
option.label,
|
|
3882
|
+
style: TextStyle(
|
|
3883
|
+
fontSize: 13,
|
|
3884
|
+
color: _accent,
|
|
3885
|
+
fontWeight: FontWeight.w600,
|
|
3886
|
+
),
|
|
3887
|
+
),
|
|
3888
|
+
),
|
|
3889
|
+
);
|
|
3890
|
+
}).toList(growable: false),
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3367
3895
|
String _titleCase(String value) {
|
|
3368
3896
|
final normalized = value.trim();
|
|
3369
3897
|
if (normalized.isEmpty) {
|