neoagent 3.1.1-beta.3 → 3.1.1-beta.4
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/extensions/chrome-browser/manifest.json +1 -1
- package/extensions/chrome-browser/protocol.mjs +36 -0
- package/flutter_app/lib/main_app_shell.dart +9 -3
- package/flutter_app/lib/main_controller.dart +32 -0
- package/flutter_app/lib/main_settings.dart +301 -1
- package/flutter_app/lib/main_shared.dart +7 -2
- package/flutter_app/lib/main_timeline.dart +82 -11
- package/flutter_app/lib/src/backend_client.dart +30 -0
- package/landing/index.html +3 -0
- package/landing/status.html +178 -0
- package/package.json +1 -1
- package/server/http/routes.js +2 -0
- package/server/http/static.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +69698 -69237
- package/server/routes/public_status.js +12 -0
- package/server/routes/settings.js +14 -0
- package/server/routes/social_reach.js +88 -0
- package/server/services/ai/tools.js +59 -0
- package/server/services/browser/extension/protocol.js +1 -0
- package/server/services/manager.js +14 -0
- package/server/services/public_status.js +103 -0
- package/server/services/runtime/settings.js +6 -0
- package/server/services/social_reach/channels/base.js +56 -0
- package/server/services/social_reach/channels/github.js +75 -0
- package/server/services/social_reach/channels/index.js +34 -0
- package/server/services/social_reach/channels/linkedin.js +34 -0
- package/server/services/social_reach/channels/rss.js +58 -0
- package/server/services/social_reach/channels/unsupported.js +49 -0
- package/server/services/social_reach/channels/v2ex.js +85 -0
- package/server/services/social_reach/channels/web.js +40 -0
- package/server/services/social_reach/channels/xueqiu.js +101 -0
- package/server/services/social_reach/channels/youtube.js +42 -0
- package/server/services/social_reach/index.js +7 -0
- package/server/services/social_reach/platforms.js +162 -0
- package/server/services/social_reach/service.js +163 -0
- package/server/services/social_reach/store.js +89 -0
- package/server/services/social_reach/utils.js +95 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"version_name": "2.4.1-beta.8",
|
|
6
6
|
"description": "Connect this Chrome browser to NeoAgent for browser automation.",
|
|
7
7
|
"minimum_chrome_version": "118",
|
|
8
|
-
"permissions": ["alarms", "debugger", "storage", "tabs"],
|
|
8
|
+
"permissions": ["alarms", "cookies", "debugger", "storage", "tabs"],
|
|
9
9
|
"host_permissions": ["http://*/*", "https://*/*"],
|
|
10
10
|
"icons": {
|
|
11
11
|
"16": "icons/icon16.png",
|
|
@@ -12,6 +12,7 @@ export const COMMANDS = Object.freeze({
|
|
|
12
12
|
SCREENSHOT: 'screenshot',
|
|
13
13
|
CLOSE: 'close',
|
|
14
14
|
GET_PAGE_INFO: 'getPageInfo',
|
|
15
|
+
GET_COOKIES: 'getCookies',
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
function chromeCall(chromeApi, namespace, method, ...args) {
|
|
@@ -221,6 +222,41 @@ export function createBrowserProtocol(chromeApi) {
|
|
|
221
222
|
|
|
222
223
|
async function run(command, payload = {}) {
|
|
223
224
|
switch (command) {
|
|
225
|
+
case COMMANDS.GET_COOKIES: {
|
|
226
|
+
const domains = Array.isArray(payload.domains)
|
|
227
|
+
? payload.domains.map((item) => String(item || '').replace(/^\./, '').toLowerCase()).filter(Boolean)
|
|
228
|
+
: [];
|
|
229
|
+
if (domains.length === 0) throw new Error('At least one cookie domain is required.');
|
|
230
|
+
const all = [];
|
|
231
|
+
for (const domain of domains) {
|
|
232
|
+
const cookies = await chromeCall(chromeApi, 'cookies', 'getAll', { domain });
|
|
233
|
+
all.push(...(Array.isArray(cookies) ? cookies : []));
|
|
234
|
+
}
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
const filtered = all.filter((cookie) => {
|
|
237
|
+
const cookieDomain = String(cookie?.domain || '').replace(/^\./, '').toLowerCase();
|
|
238
|
+
const allowed = domains.some((domain) => cookieDomain === domain || cookieDomain.endsWith(`.${domain}`));
|
|
239
|
+
if (!allowed) return false;
|
|
240
|
+
const key = `${cookie.name}\n${cookie.domain}\n${cookie.path}`;
|
|
241
|
+
if (seen.has(key)) return false;
|
|
242
|
+
seen.add(key);
|
|
243
|
+
return true;
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
platform: String(payload.platform || ''),
|
|
247
|
+
domains,
|
|
248
|
+
cookies: filtered.map((cookie) => ({
|
|
249
|
+
name: cookie.name,
|
|
250
|
+
value: cookie.value,
|
|
251
|
+
domain: cookie.domain,
|
|
252
|
+
path: cookie.path,
|
|
253
|
+
secure: cookie.secure === true,
|
|
254
|
+
httpOnly: cookie.httpOnly === true,
|
|
255
|
+
sameSite: cookie.sameSite || null,
|
|
256
|
+
expirationDate: cookie.expirationDate || null,
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
224
260
|
case COMMANDS.LAUNCH:
|
|
225
261
|
await attach();
|
|
226
262
|
return pageSnapshot({ screenshot: false });
|
|
@@ -357,8 +357,9 @@ class _LocalInstallWidgetState extends State<_LocalInstallWidget> {
|
|
|
357
357
|
if (!mounted) return;
|
|
358
358
|
setState(() {
|
|
359
359
|
_phase = exit == 0 ? _LocalInstallPhase.done : _LocalInstallPhase.failed;
|
|
360
|
-
if (exit != 0)
|
|
360
|
+
if (exit != 0) {
|
|
361
361
|
_errorMsg = 'Installation failed (exit $exit). Check the log above.';
|
|
362
|
+
}
|
|
362
363
|
});
|
|
363
364
|
}
|
|
364
365
|
|
|
@@ -471,8 +472,9 @@ class _LocalInstallWidgetState extends State<_LocalInstallWidget> {
|
|
|
471
472
|
final proc = _proc;
|
|
472
473
|
proc?.kill(ProcessSignal.sigterm);
|
|
473
474
|
await proc?.exitCode;
|
|
474
|
-
if (mounted)
|
|
475
|
+
if (mounted) {
|
|
475
476
|
setState(() => _phase = _LocalInstallPhase.ready);
|
|
477
|
+
}
|
|
476
478
|
},
|
|
477
479
|
child: const Text('Cancel'),
|
|
478
480
|
),
|
|
@@ -1607,7 +1609,11 @@ class _HomeViewState extends State<HomeView> {
|
|
|
1607
1609
|
titleSpacing: 0,
|
|
1608
1610
|
leadingWidth: 44,
|
|
1609
1611
|
centerTitle: false,
|
|
1610
|
-
title: Text(
|
|
1612
|
+
title: Text(
|
|
1613
|
+
controller.selectedSection.navigationTitle,
|
|
1614
|
+
maxLines: 1,
|
|
1615
|
+
overflow: TextOverflow.ellipsis,
|
|
1616
|
+
),
|
|
1611
1617
|
titleTextStyle: Theme.of(
|
|
1612
1618
|
context,
|
|
1613
1619
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
|
@@ -182,6 +182,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
182
182
|
List<AiWidgetItem> widgets = const <AiWidgetItem>[];
|
|
183
183
|
List<McpServerItem> mcpServers = const <McpServerItem>[];
|
|
184
184
|
Map<String, dynamic> browserRuntime = const <String, dynamic>{};
|
|
185
|
+
Map<String, dynamic> socialReachStatus = const <String, dynamic>{};
|
|
185
186
|
Map<String, dynamic> browserExtensionStatus = const <String, dynamic>{};
|
|
186
187
|
List<Map<String, dynamic>> browserExtensionTokens =
|
|
187
188
|
const <Map<String, dynamic>>[];
|
|
@@ -2316,6 +2317,9 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2316
2317
|
final browserFuture = _backendClient
|
|
2317
2318
|
.fetchBrowserStatus(backendUrl)
|
|
2318
2319
|
.catchError((_) => const <String, dynamic>{});
|
|
2320
|
+
final socialReachFuture = _backendClient
|
|
2321
|
+
.fetchSocialReachStatus(backendUrl)
|
|
2322
|
+
.catchError((_) => const <String, dynamic>{});
|
|
2319
2323
|
final browserExtensionFuture = _backendClient
|
|
2320
2324
|
.fetchBrowserExtensionStatus(backendUrl)
|
|
2321
2325
|
.catchError((_) => const <String, dynamic>{});
|
|
@@ -2371,6 +2375,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2371
2375
|
final mcpResponse = await mcpFuture;
|
|
2372
2376
|
final recordingsResponse = await recordingsFuture;
|
|
2373
2377
|
final browserResponse = await browserFuture;
|
|
2378
|
+
final socialReachResponse = await socialReachFuture;
|
|
2374
2379
|
final browserExtensionResponse = await browserExtensionFuture;
|
|
2375
2380
|
final androidResponse = await androidFuture;
|
|
2376
2381
|
final desktopResponse = await desktopFuture;
|
|
@@ -2468,6 +2473,7 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
2468
2473
|
RecordingSessionItem.fromJson,
|
|
2469
2474
|
);
|
|
2470
2475
|
browserRuntime = Map<String, dynamic>.from(browserResponse);
|
|
2476
|
+
socialReachStatus = Map<String, dynamic>.from(socialReachResponse);
|
|
2471
2477
|
browserExtensionStatus = Map<String, dynamic>.from(
|
|
2472
2478
|
browserExtensionResponse,
|
|
2473
2479
|
);
|
|
@@ -5892,6 +5898,32 @@ class NeoAgentController extends ChangeNotifier {
|
|
|
5892
5898
|
}
|
|
5893
5899
|
}
|
|
5894
5900
|
|
|
5901
|
+
Future<Map<String, dynamic>> refreshSocialReachStatus() async {
|
|
5902
|
+
final response = await _backendClient.fetchSocialReachStatus(backendUrl);
|
|
5903
|
+
socialReachStatus = Map<String, dynamic>.from(response);
|
|
5904
|
+
notifyListeners();
|
|
5905
|
+
return socialReachStatus;
|
|
5906
|
+
}
|
|
5907
|
+
|
|
5908
|
+
Future<Map<String, dynamic>> importSocialReachCookies(String platform) async {
|
|
5909
|
+
final response = await _backendClient.importSocialReachCookies(
|
|
5910
|
+
backendUrl,
|
|
5911
|
+
platform,
|
|
5912
|
+
tokenId: selectedBrowserExtensionTokenId,
|
|
5913
|
+
);
|
|
5914
|
+
await refreshSocialReachStatus();
|
|
5915
|
+
return response;
|
|
5916
|
+
}
|
|
5917
|
+
|
|
5918
|
+
Future<Map<String, dynamic>> clearSocialReachCookies(String platform) async {
|
|
5919
|
+
final response = await _backendClient.clearSocialReachCookies(
|
|
5920
|
+
backendUrl,
|
|
5921
|
+
platform,
|
|
5922
|
+
);
|
|
5923
|
+
await refreshSocialReachStatus();
|
|
5924
|
+
return response;
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5895
5927
|
Future<void> disconnectMessagingPlatform(String platform) async {
|
|
5896
5928
|
final busyKey = '$platform:disconnect';
|
|
5897
5929
|
if (_busyMessagingPlatformKeys.contains(busyKey)) {
|
|
@@ -117,6 +117,22 @@ const _modelsSettingsSection = _SettingsSection('models', <String>[
|
|
|
117
117
|
'smart selector',
|
|
118
118
|
]);
|
|
119
119
|
|
|
120
|
+
const _socialReachSettingsSection = _SettingsSection('social reach', <String>[
|
|
121
|
+
'social',
|
|
122
|
+
'reach',
|
|
123
|
+
'web',
|
|
124
|
+
'rss',
|
|
125
|
+
'github',
|
|
126
|
+
'youtube',
|
|
127
|
+
'linkedin',
|
|
128
|
+
'xueqiu',
|
|
129
|
+
'twitter',
|
|
130
|
+
'reddit',
|
|
131
|
+
'instagram',
|
|
132
|
+
'facebook',
|
|
133
|
+
'cookies',
|
|
134
|
+
]);
|
|
135
|
+
|
|
120
136
|
const _voiceRecordingSettingsSection = _SettingsSection(
|
|
121
137
|
'voice recording',
|
|
122
138
|
<String>[
|
|
@@ -164,6 +180,7 @@ const _securitySettingsSection = _SettingsSection('security', <String>[
|
|
|
164
180
|
const List<_SettingsSection> _settingsSearchSections = <_SettingsSection>[
|
|
165
181
|
_overviewSettingsSection,
|
|
166
182
|
_workspaceSettingsSection,
|
|
183
|
+
_socialReachSettingsSection,
|
|
167
184
|
_modelsSettingsSection,
|
|
168
185
|
_voiceRecordingSettingsSection,
|
|
169
186
|
_desktopSettingsSection,
|
|
@@ -201,6 +218,9 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
201
218
|
Map<String, dynamic>? _extensionTestResult;
|
|
202
219
|
bool _desktopTestRunning = false;
|
|
203
220
|
Map<String, dynamic>? _desktopTestResult;
|
|
221
|
+
bool _socialReachRefreshing = false;
|
|
222
|
+
String? _socialReachBusyPlatform;
|
|
223
|
+
Map<String, dynamic>? _socialReachActionResult;
|
|
204
224
|
|
|
205
225
|
@override
|
|
206
226
|
void initState() {
|
|
@@ -399,6 +419,13 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
399
419
|
_buildWorkspaceSection(controller),
|
|
400
420
|
const SizedBox(height: 16),
|
|
401
421
|
],
|
|
422
|
+
if (_matchesSettingsSection(
|
|
423
|
+
searchQuery,
|
|
424
|
+
_socialReachSettingsSection,
|
|
425
|
+
)) ...<Widget>[
|
|
426
|
+
_buildSocialReachSection(controller),
|
|
427
|
+
const SizedBox(height: 16),
|
|
428
|
+
],
|
|
402
429
|
if (_matchesSettingsSection(
|
|
403
430
|
searchQuery,
|
|
404
431
|
_modelsSettingsSection,
|
|
@@ -920,6 +947,278 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
920
947
|
);
|
|
921
948
|
}
|
|
922
949
|
|
|
950
|
+
List<Map<String, dynamic>> _socialReachPlatforms(
|
|
951
|
+
NeoAgentController controller,
|
|
952
|
+
) {
|
|
953
|
+
final raw = controller.socialReachStatus['platforms'];
|
|
954
|
+
if (raw is! List) return const <Map<String, dynamic>>[];
|
|
955
|
+
return raw
|
|
956
|
+
.whereType<Map>()
|
|
957
|
+
.map((item) => Map<String, dynamic>.from(item))
|
|
958
|
+
.toList();
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
Color _socialReachStatusColor(Map<String, dynamic> platform) {
|
|
962
|
+
final status = platform['status']?.toString().toLowerCase() ?? '';
|
|
963
|
+
if (platform['ready'] == true || status == 'ok') return _success;
|
|
964
|
+
if (status == 'warn') return _warning;
|
|
965
|
+
if (status == 'error') return _danger;
|
|
966
|
+
return _textSecondary;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
Widget _buildSocialReachSection(NeoAgentController controller) {
|
|
970
|
+
final platforms = _socialReachPlatforms(controller);
|
|
971
|
+
final ready = platforms
|
|
972
|
+
.where((item) => item['ready'] == true || item['status'] == 'ok')
|
|
973
|
+
.length;
|
|
974
|
+
final unsupported = platforms
|
|
975
|
+
.where((item) => item['setupKind'] == 'unsupported_node_only')
|
|
976
|
+
.length;
|
|
977
|
+
return Card(
|
|
978
|
+
child: Padding(
|
|
979
|
+
padding: const EdgeInsets.all(20),
|
|
980
|
+
child: Column(
|
|
981
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
982
|
+
children: <Widget>[
|
|
983
|
+
Row(
|
|
984
|
+
children: <Widget>[
|
|
985
|
+
const Expanded(child: _SectionTitle('Social Reach')),
|
|
986
|
+
IconButton(
|
|
987
|
+
tooltip: 'Refresh',
|
|
988
|
+
onPressed: _socialReachRefreshing
|
|
989
|
+
? null
|
|
990
|
+
: () async {
|
|
991
|
+
setState(() {
|
|
992
|
+
_socialReachRefreshing = true;
|
|
993
|
+
_socialReachActionResult = null;
|
|
994
|
+
});
|
|
995
|
+
try {
|
|
996
|
+
await controller.refreshSocialReachStatus();
|
|
997
|
+
} catch (e) {
|
|
998
|
+
if (mounted) {
|
|
999
|
+
setState(
|
|
1000
|
+
() => _socialReachActionResult =
|
|
1001
|
+
<String, dynamic>{'error': e.toString()},
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
} finally {
|
|
1005
|
+
if (mounted) {
|
|
1006
|
+
setState(() => _socialReachRefreshing = false);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
icon: _socialReachRefreshing
|
|
1011
|
+
? const SizedBox.square(
|
|
1012
|
+
dimension: 18,
|
|
1013
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
1014
|
+
)
|
|
1015
|
+
: const Icon(Icons.refresh_rounded),
|
|
1016
|
+
),
|
|
1017
|
+
],
|
|
1018
|
+
),
|
|
1019
|
+
const SizedBox(height: 10),
|
|
1020
|
+
Text(
|
|
1021
|
+
'Node-native web and social content access for agents.',
|
|
1022
|
+
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
1023
|
+
),
|
|
1024
|
+
const SizedBox(height: 12),
|
|
1025
|
+
Wrap(
|
|
1026
|
+
spacing: 8,
|
|
1027
|
+
runSpacing: 8,
|
|
1028
|
+
children: <Widget>[
|
|
1029
|
+
_MetaPill(
|
|
1030
|
+
icon: Icons.check_circle_outline,
|
|
1031
|
+
label: '$ready ready',
|
|
1032
|
+
color: _success,
|
|
1033
|
+
),
|
|
1034
|
+
_MetaPill(
|
|
1035
|
+
icon: Icons.code_outlined,
|
|
1036
|
+
label: 'Node-only mode',
|
|
1037
|
+
color: _info,
|
|
1038
|
+
),
|
|
1039
|
+
_MetaPill(
|
|
1040
|
+
icon: Icons.block_outlined,
|
|
1041
|
+
label: '$unsupported unavailable',
|
|
1042
|
+
color: _warning,
|
|
1043
|
+
),
|
|
1044
|
+
],
|
|
1045
|
+
),
|
|
1046
|
+
if (_socialReachActionResult != null) ...<Widget>[
|
|
1047
|
+
const SizedBox(height: 12),
|
|
1048
|
+
Container(
|
|
1049
|
+
width: double.infinity,
|
|
1050
|
+
padding: const EdgeInsets.all(10),
|
|
1051
|
+
decoration: BoxDecoration(
|
|
1052
|
+
color:
|
|
1053
|
+
(_socialReachActionResult!['error'] == null
|
|
1054
|
+
? _success
|
|
1055
|
+
: _danger)
|
|
1056
|
+
.withValues(alpha: 0.10),
|
|
1057
|
+
borderRadius: BorderRadius.circular(8),
|
|
1058
|
+
border: Border.all(
|
|
1059
|
+
color:
|
|
1060
|
+
(_socialReachActionResult!['error'] == null
|
|
1061
|
+
? _success
|
|
1062
|
+
: _danger)
|
|
1063
|
+
.withValues(alpha: 0.30),
|
|
1064
|
+
),
|
|
1065
|
+
),
|
|
1066
|
+
child: Text(
|
|
1067
|
+
_socialReachActionResult!['error']?.toString() ??
|
|
1068
|
+
'Social Reach updated.',
|
|
1069
|
+
style: TextStyle(
|
|
1070
|
+
color: _socialReachActionResult!['error'] == null
|
|
1071
|
+
? _success
|
|
1072
|
+
: _danger,
|
|
1073
|
+
),
|
|
1074
|
+
),
|
|
1075
|
+
),
|
|
1076
|
+
],
|
|
1077
|
+
const SizedBox(height: 16),
|
|
1078
|
+
if (platforms.isEmpty)
|
|
1079
|
+
Text(
|
|
1080
|
+
'Status is not loaded yet.',
|
|
1081
|
+
style: TextStyle(color: _textSecondary),
|
|
1082
|
+
)
|
|
1083
|
+
else
|
|
1084
|
+
...platforms.map(
|
|
1085
|
+
(platform) => Padding(
|
|
1086
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
1087
|
+
child: _buildSocialReachPlatformRow(controller, platform),
|
|
1088
|
+
),
|
|
1089
|
+
),
|
|
1090
|
+
],
|
|
1091
|
+
),
|
|
1092
|
+
),
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
Widget _buildSocialReachPlatformRow(
|
|
1097
|
+
NeoAgentController controller,
|
|
1098
|
+
Map<String, dynamic> platform,
|
|
1099
|
+
) {
|
|
1100
|
+
final id = platform['platform']?.toString() ?? '';
|
|
1101
|
+
final label = platform['label']?.toString() ?? id;
|
|
1102
|
+
final setupKind = platform['setupKind']?.toString() ?? '';
|
|
1103
|
+
final status = platform['status']?.toString() ?? 'off';
|
|
1104
|
+
final message = platform['message']?.toString() ?? '';
|
|
1105
|
+
final cookie = platform['cookie'] is Map
|
|
1106
|
+
? Map<String, dynamic>.from(platform['cookie'] as Map)
|
|
1107
|
+
: const <String, dynamic>{};
|
|
1108
|
+
final busy = _socialReachBusyPlatform == id;
|
|
1109
|
+
final canImport = setupKind == 'cookies';
|
|
1110
|
+
return Container(
|
|
1111
|
+
padding: const EdgeInsets.all(14),
|
|
1112
|
+
decoration: BoxDecoration(
|
|
1113
|
+
color: _bgSecondary,
|
|
1114
|
+
borderRadius: BorderRadius.circular(8),
|
|
1115
|
+
border: Border.all(color: _border),
|
|
1116
|
+
),
|
|
1117
|
+
child: Column(
|
|
1118
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1119
|
+
children: <Widget>[
|
|
1120
|
+
Row(
|
|
1121
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
1122
|
+
children: <Widget>[
|
|
1123
|
+
Expanded(
|
|
1124
|
+
child: Text(
|
|
1125
|
+
label,
|
|
1126
|
+
style: TextStyle(
|
|
1127
|
+
color: _textPrimary,
|
|
1128
|
+
fontWeight: FontWeight.w800,
|
|
1129
|
+
),
|
|
1130
|
+
),
|
|
1131
|
+
),
|
|
1132
|
+
_StatusPill(
|
|
1133
|
+
label: status,
|
|
1134
|
+
color: _socialReachStatusColor(platform),
|
|
1135
|
+
),
|
|
1136
|
+
],
|
|
1137
|
+
),
|
|
1138
|
+
const SizedBox(height: 6),
|
|
1139
|
+
Text(message, style: TextStyle(color: _textSecondary, height: 1.35)),
|
|
1140
|
+
if (cookie.isNotEmpty) ...<Widget>[
|
|
1141
|
+
const SizedBox(height: 8),
|
|
1142
|
+
Text(
|
|
1143
|
+
cookie['configured'] == true
|
|
1144
|
+
? '${cookie['count'] ?? 0} cookies imported'
|
|
1145
|
+
: 'Cookies not configured',
|
|
1146
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
1147
|
+
),
|
|
1148
|
+
],
|
|
1149
|
+
if (canImport) ...<Widget>[
|
|
1150
|
+
const SizedBox(height: 10),
|
|
1151
|
+
Wrap(
|
|
1152
|
+
spacing: 8,
|
|
1153
|
+
runSpacing: 8,
|
|
1154
|
+
children: <Widget>[
|
|
1155
|
+
FilledButton.icon(
|
|
1156
|
+
onPressed: busy
|
|
1157
|
+
? null
|
|
1158
|
+
: () => _runSocialReachAction(
|
|
1159
|
+
controller,
|
|
1160
|
+
id,
|
|
1161
|
+
() => controller.importSocialReachCookies(id),
|
|
1162
|
+
),
|
|
1163
|
+
icon: busy
|
|
1164
|
+
? const SizedBox.square(
|
|
1165
|
+
dimension: 16,
|
|
1166
|
+
child: CircularProgressIndicator(
|
|
1167
|
+
strokeWidth: 2,
|
|
1168
|
+
color: Colors.white,
|
|
1169
|
+
),
|
|
1170
|
+
)
|
|
1171
|
+
: const Icon(Icons.extension_outlined, size: 18),
|
|
1172
|
+
label: const Text('Import from extension'),
|
|
1173
|
+
),
|
|
1174
|
+
OutlinedButton.icon(
|
|
1175
|
+
onPressed: busy
|
|
1176
|
+
? null
|
|
1177
|
+
: () => _runSocialReachAction(
|
|
1178
|
+
controller,
|
|
1179
|
+
id,
|
|
1180
|
+
() => controller.clearSocialReachCookies(id),
|
|
1181
|
+
),
|
|
1182
|
+
icon: const Icon(Icons.delete_outline, size: 18),
|
|
1183
|
+
label: const Text('Clear'),
|
|
1184
|
+
),
|
|
1185
|
+
],
|
|
1186
|
+
),
|
|
1187
|
+
],
|
|
1188
|
+
],
|
|
1189
|
+
),
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
Future<void> _runSocialReachAction(
|
|
1194
|
+
NeoAgentController controller,
|
|
1195
|
+
String platform,
|
|
1196
|
+
Future<Map<String, dynamic>> Function() action,
|
|
1197
|
+
) async {
|
|
1198
|
+
setState(() {
|
|
1199
|
+
_socialReachBusyPlatform = platform;
|
|
1200
|
+
_socialReachActionResult = null;
|
|
1201
|
+
});
|
|
1202
|
+
try {
|
|
1203
|
+
final result = await action();
|
|
1204
|
+
if (mounted) {
|
|
1205
|
+
setState(() => _socialReachActionResult = result);
|
|
1206
|
+
}
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
if (mounted) {
|
|
1209
|
+
setState(
|
|
1210
|
+
() => _socialReachActionResult = <String, dynamic>{
|
|
1211
|
+
'error': e.toString(),
|
|
1212
|
+
},
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
} finally {
|
|
1216
|
+
if (mounted) {
|
|
1217
|
+
setState(() => _socialReachBusyPlatform = null);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
923
1222
|
Widget _buildModelsSection({
|
|
924
1223
|
required BuildContext context,
|
|
925
1224
|
required NeoAgentController controller,
|
|
@@ -1075,11 +1374,12 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
1075
1374
|
selectedIds: _enabledModels,
|
|
1076
1375
|
),
|
|
1077
1376
|
);
|
|
1078
|
-
if (result != null)
|
|
1377
|
+
if (result != null) {
|
|
1079
1378
|
setState(() {
|
|
1080
1379
|
_enabledModels = result;
|
|
1081
1380
|
_hasUnsavedChanges = true;
|
|
1082
1381
|
});
|
|
1382
|
+
}
|
|
1083
1383
|
},
|
|
1084
1384
|
),
|
|
1085
1385
|
],
|
|
@@ -588,7 +588,7 @@ class _PageTitle extends StatelessWidget {
|
|
|
588
588
|
Widget build(BuildContext context) {
|
|
589
589
|
final compact = MediaQuery.sizeOf(context).width < 760;
|
|
590
590
|
final titleStyle = compact
|
|
591
|
-
? _displayTitleStyle(
|
|
591
|
+
? _displayTitleStyle(22)
|
|
592
592
|
: _displayTitleStyle(32);
|
|
593
593
|
final subtitleStyle = TextStyle(
|
|
594
594
|
color: _textSecondary,
|
|
@@ -603,7 +603,12 @@ class _PageTitle extends StatelessWidget {
|
|
|
603
603
|
children: <Widget>[
|
|
604
604
|
Text('CONTROL SURFACE', style: _sectionEyebrowStyle()),
|
|
605
605
|
const SizedBox(height: 6),
|
|
606
|
-
Text(
|
|
606
|
+
Text(
|
|
607
|
+
title,
|
|
608
|
+
maxLines: 1,
|
|
609
|
+
overflow: TextOverflow.ellipsis,
|
|
610
|
+
style: titleStyle,
|
|
611
|
+
),
|
|
607
612
|
const SizedBox(height: 8),
|
|
608
613
|
ConstrainedBox(
|
|
609
614
|
constraints: const BoxConstraints(maxWidth: 720),
|
|
@@ -51,6 +51,67 @@ class _TimelinePanelState extends State<TimelinePanel> {
|
|
|
51
51
|
return items.first;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
Future<void> _showMobileEventDetails(
|
|
55
|
+
List<TimelineEventItem> items,
|
|
56
|
+
TimelineEventItem initialEvent,
|
|
57
|
+
) async {
|
|
58
|
+
_selectEvent(initialEvent);
|
|
59
|
+
var selectedIndex = items.indexWhere((item) => item.id == initialEvent.id);
|
|
60
|
+
if (selectedIndex < 0) {
|
|
61
|
+
selectedIndex = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await showModalBottomSheet<void>(
|
|
65
|
+
context: context,
|
|
66
|
+
isScrollControlled: true,
|
|
67
|
+
useSafeArea: true,
|
|
68
|
+
backgroundColor: Colors.transparent,
|
|
69
|
+
builder: (context) {
|
|
70
|
+
return StatefulBuilder(
|
|
71
|
+
builder: (context, setSheetState) {
|
|
72
|
+
final selectedEvent = items[selectedIndex];
|
|
73
|
+
void selectOffset(int offset) {
|
|
74
|
+
final nextIndex = (selectedIndex + offset).clamp(
|
|
75
|
+
0,
|
|
76
|
+
items.length - 1,
|
|
77
|
+
);
|
|
78
|
+
if (nextIndex == selectedIndex) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setSheetState(() {
|
|
82
|
+
selectedIndex = nextIndex;
|
|
83
|
+
});
|
|
84
|
+
_selectEvent(items[nextIndex]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return FractionallySizedBox(
|
|
88
|
+
heightFactor: 0.9,
|
|
89
|
+
child: Padding(
|
|
90
|
+
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
|
91
|
+
child: _TimelineDetailPane(
|
|
92
|
+
items: items,
|
|
93
|
+
selectedEvent: selectedEvent,
|
|
94
|
+
selectedIndex: selectedIndex,
|
|
95
|
+
onSelectPrevious: selectedIndex > 0
|
|
96
|
+
? () => selectOffset(-1)
|
|
97
|
+
: null,
|
|
98
|
+
onSelectNext: selectedIndex < items.length - 1
|
|
99
|
+
? () => selectOffset(1)
|
|
100
|
+
: null,
|
|
101
|
+
onOpenRun: selectedEvent.runId.isNotEmpty
|
|
102
|
+
? () => unawaited(
|
|
103
|
+
widget.controller.openRunDetails(selectedEvent.runId),
|
|
104
|
+
)
|
|
105
|
+
: null,
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
54
115
|
@override
|
|
55
116
|
Widget build(BuildContext context) {
|
|
56
117
|
final items = _sortedTimelineEvents(widget.controller.timelineItems);
|
|
@@ -87,10 +148,15 @@ class _TimelinePanelState extends State<TimelinePanel> {
|
|
|
87
148
|
: LayoutBuilder(
|
|
88
149
|
builder: (context, constraints) {
|
|
89
150
|
final isWide = constraints.maxWidth >= 1180;
|
|
151
|
+
final isCompact = constraints.maxWidth < 760;
|
|
90
152
|
final feedPane = _TimelineFeedPane(
|
|
91
153
|
groups: groups,
|
|
92
154
|
selectedEventId: selectedEvent?.id,
|
|
93
|
-
onSelectEvent:
|
|
155
|
+
onSelectEvent: isCompact
|
|
156
|
+
? (item) => unawaited(
|
|
157
|
+
_showMobileEventDetails(items, item),
|
|
158
|
+
)
|
|
159
|
+
: _selectEvent,
|
|
94
160
|
);
|
|
95
161
|
final detailPane = _TimelineDetailPane(
|
|
96
162
|
items: items,
|
|
@@ -126,6 +192,10 @@ class _TimelinePanelState extends State<TimelinePanel> {
|
|
|
126
192
|
);
|
|
127
193
|
}
|
|
128
194
|
|
|
195
|
+
if (isCompact) {
|
|
196
|
+
return feedPane;
|
|
197
|
+
}
|
|
198
|
+
|
|
129
199
|
return Column(
|
|
130
200
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
131
201
|
children: <Widget>[
|
|
@@ -156,13 +226,14 @@ class _TimelineHeroHeader extends StatelessWidget {
|
|
|
156
226
|
|
|
157
227
|
@override
|
|
158
228
|
Widget build(BuildContext context) {
|
|
229
|
+
final compact = MediaQuery.sizeOf(context).width < 760;
|
|
159
230
|
final focusedDay =
|
|
160
231
|
selectedEvent?.occurredAt ??
|
|
161
232
|
(items.isEmpty ? null : items.first.occurredAt);
|
|
162
233
|
|
|
163
234
|
return Container(
|
|
164
235
|
width: double.infinity,
|
|
165
|
-
padding:
|
|
236
|
+
padding: EdgeInsets.all(compact ? 16 : 22),
|
|
166
237
|
decoration: BoxDecoration(
|
|
167
238
|
gradient: LinearGradient(
|
|
168
239
|
colors: <Color>[
|
|
@@ -172,7 +243,7 @@ class _TimelineHeroHeader extends StatelessWidget {
|
|
|
172
243
|
begin: Alignment.topLeft,
|
|
173
244
|
end: Alignment.bottomRight,
|
|
174
245
|
),
|
|
175
|
-
borderRadius: BorderRadius.circular(28),
|
|
246
|
+
borderRadius: BorderRadius.circular(compact ? 20 : 28),
|
|
176
247
|
border: Border.all(color: _borderLight),
|
|
177
248
|
boxShadow: <BoxShadow>[
|
|
178
249
|
BoxShadow(
|
|
@@ -195,27 +266,27 @@ class _TimelineHeroHeader extends StatelessWidget {
|
|
|
195
266
|
'ACTIVITY FEED',
|
|
196
267
|
style: TextStyle(
|
|
197
268
|
color: _accentHover,
|
|
198
|
-
fontSize: 13,
|
|
269
|
+
fontSize: compact ? 11 : 13,
|
|
199
270
|
fontWeight: FontWeight.w700,
|
|
200
|
-
letterSpacing: 4.2,
|
|
271
|
+
letterSpacing: compact ? 2.4 : 4.2,
|
|
201
272
|
),
|
|
202
273
|
),
|
|
203
|
-
|
|
204
|
-
|
|
274
|
+
SizedBox(height: compact ? 8 : 14),
|
|
275
|
+
Text(
|
|
205
276
|
'Timeline',
|
|
206
277
|
style: TextStyle(
|
|
207
|
-
fontSize: 40,
|
|
278
|
+
fontSize: compact ? 28 : 40,
|
|
208
279
|
fontWeight: FontWeight.w800,
|
|
209
280
|
height: 1,
|
|
210
281
|
),
|
|
211
282
|
),
|
|
212
|
-
|
|
283
|
+
SizedBox(height: compact ? 8 : 14),
|
|
213
284
|
Text(
|
|
214
285
|
'Emails, AI actions, recordings, tasks and run activity in one chronological feed.',
|
|
215
286
|
style: TextStyle(
|
|
216
287
|
color: _textSecondary,
|
|
217
|
-
fontSize: 16.5,
|
|
218
|
-
height: 1.35,
|
|
288
|
+
fontSize: compact ? 13.5 : 16.5,
|
|
289
|
+
height: compact ? 1.28 : 1.35,
|
|
219
290
|
),
|
|
220
291
|
),
|
|
221
292
|
],
|