neoagent 2.3.1-beta.63 → 2.3.1-beta.65
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/docs/capabilities.md +1 -1
- package/docs/configuration.md +2 -2
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_account_settings.dart +50 -22
- package/flutter_app/lib/main_admin.dart +24 -10
- package/flutter_app/lib/main_app_shell.dart +10 -9
- package/flutter_app/lib/main_chat.dart +631 -318
- package/flutter_app/lib/main_controller.dart +29 -8
- package/flutter_app/lib/main_devices.dart +4 -6
- package/flutter_app/lib/main_integrations.dart +15 -7
- package/flutter_app/lib/main_models.dart +207 -8
- package/flutter_app/lib/main_navigation.dart +36 -18
- package/flutter_app/lib/main_operations.dart +162 -91
- package/flutter_app/lib/main_settings.dart +273 -78
- package/flutter_app/lib/main_shared.dart +8 -6
- package/flutter_app/lib/main_unified.dart +388 -0
- package/package.json +1 -1
- package/server/db/database.js +52 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.json +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 +79074 -78010
- package/server/routes/agents.js +2 -1
- package/server/routes/browser.js +1 -14
- package/server/routes/memory.js +75 -3
- package/server/routes/settings.js +1 -5
- package/server/routes/widgets.js +4 -4
- package/server/services/ai/capabilityHealth.js +1 -10
- package/server/services/ai/deliverables/artifact_helpers.js +190 -0
- package/server/services/ai/deliverables/contracts.js +113 -0
- package/server/services/ai/deliverables/deliverables.test.js +76 -0
- package/server/services/ai/deliverables/index.js +20 -0
- package/server/services/ai/deliverables/selector.js +94 -0
- package/server/services/ai/deliverables/validator.js +63 -0
- package/server/services/ai/deliverables/workflows.js +195 -0
- package/server/services/ai/engine.js +259 -1
- package/server/services/ai/runEvents.js +100 -0
- package/server/services/ai/systemPrompt.js +6 -0
- package/server/services/ai/tools.js +1 -5
- package/server/services/manager.js +5 -56
- package/server/services/memory/manager.js +242 -26
- package/server/services/runtime/manager.js +2 -6
- package/server/services/runtime/settings.js +6 -12
- package/server/services/websocket.js +3 -1
- package/server/services/widgets/focus_widget.js +134 -0
- package/server/services/widgets/service.js +130 -2
- package/server/utils/deployment.js +4 -3
|
@@ -43,6 +43,23 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
43
43
|
..selection = TextSelection.collapsed(offset: draft.length);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
void _scrollToBottom() {
|
|
47
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
48
|
+
if (!mounted || !_scrollController.hasClients) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
52
|
+
unawaited(
|
|
53
|
+
WidgetsBinding.instance.endOfFrame.then((_) {
|
|
54
|
+
if (!mounted || !_scrollController.hasClients) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
46
63
|
@override
|
|
47
64
|
Widget build(BuildContext context) {
|
|
48
65
|
final controller = widget.controller;
|
|
@@ -53,15 +70,7 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
53
70
|
_lastMessageCount = messages.length;
|
|
54
71
|
_lastToolCount = controller.toolEvents.length;
|
|
55
72
|
_lastStream = controller.streamingAssistant;
|
|
56
|
-
|
|
57
|
-
if (_scrollController.hasClients) {
|
|
58
|
-
_scrollController.animateTo(
|
|
59
|
-
_scrollController.position.maxScrollExtent,
|
|
60
|
-
duration: const Duration(milliseconds: 220),
|
|
61
|
-
curve: Curves.easeOut,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
73
|
+
_scrollToBottom();
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
return Column(
|
|
@@ -259,6 +268,87 @@ class _ChatPanelState extends State<ChatPanel> {
|
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
270
|
|
|
271
|
+
class _TypingIndicatorBubble extends StatefulWidget {
|
|
272
|
+
const _TypingIndicatorBubble();
|
|
273
|
+
|
|
274
|
+
@override
|
|
275
|
+
State<_TypingIndicatorBubble> createState() => _TypingIndicatorBubbleState();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
class _TypingIndicatorBubbleState extends State<_TypingIndicatorBubble>
|
|
279
|
+
with SingleTickerProviderStateMixin {
|
|
280
|
+
late final AnimationController _controller;
|
|
281
|
+
|
|
282
|
+
@override
|
|
283
|
+
void initState() {
|
|
284
|
+
super.initState();
|
|
285
|
+
_controller = AnimationController(
|
|
286
|
+
vsync: this,
|
|
287
|
+
duration: const Duration(milliseconds: 1200),
|
|
288
|
+
)..repeat();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@override
|
|
292
|
+
void dispose() {
|
|
293
|
+
_controller.dispose();
|
|
294
|
+
super.dispose();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@override
|
|
298
|
+
Widget build(BuildContext context) {
|
|
299
|
+
return Row(
|
|
300
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
301
|
+
children: <Widget>[
|
|
302
|
+
const _MessageAvatar(assistant: true),
|
|
303
|
+
const SizedBox(width: 12),
|
|
304
|
+
Flexible(
|
|
305
|
+
child: Container(
|
|
306
|
+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
307
|
+
decoration: BoxDecoration(
|
|
308
|
+
color: _bgCard,
|
|
309
|
+
borderRadius: BorderRadius.circular(14),
|
|
310
|
+
border: Border.all(color: _border),
|
|
311
|
+
),
|
|
312
|
+
child: AnimatedBuilder(
|
|
313
|
+
animation: _controller,
|
|
314
|
+
builder: (context, _) {
|
|
315
|
+
return Row(
|
|
316
|
+
mainAxisSize: MainAxisSize.min,
|
|
317
|
+
children: List<Widget>.generate(3, (index) {
|
|
318
|
+
final phase = ((_controller.value * 3) - index).clamp(
|
|
319
|
+
0.0,
|
|
320
|
+
1.0,
|
|
321
|
+
);
|
|
322
|
+
final offset = Curves.easeOut.transform(
|
|
323
|
+
phase > 0.5 ? 1 - phase : phase,
|
|
324
|
+
);
|
|
325
|
+
return Padding(
|
|
326
|
+
padding: EdgeInsets.only(
|
|
327
|
+
right: index == 2 ? 0 : 6,
|
|
328
|
+
top: (1 - offset) * 6,
|
|
329
|
+
),
|
|
330
|
+
child: Container(
|
|
331
|
+
width: 8,
|
|
332
|
+
height: 8,
|
|
333
|
+
decoration: BoxDecoration(
|
|
334
|
+
color: _textSecondary.withValues(
|
|
335
|
+
alpha: 0.45 + (offset * 0.5),
|
|
336
|
+
),
|
|
337
|
+
shape: BoxShape.circle,
|
|
338
|
+
),
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
),
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
],
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
262
352
|
class MessagingPanel extends StatefulWidget {
|
|
263
353
|
const MessagingPanel({super.key, required this.controller});
|
|
264
354
|
|
|
@@ -343,9 +433,7 @@ class _MessagingPanelState extends State<MessagingPanel> {
|
|
|
343
433
|
const (
|
|
344
434
|
'Hardware Bridges',
|
|
345
435
|
'Local device bridges and TCP-connected integrations.',
|
|
346
|
-
[
|
|
347
|
-
'meshtastic',
|
|
348
|
-
],
|
|
436
|
+
['meshtastic'],
|
|
349
437
|
),
|
|
350
438
|
const ('Voice', 'Telephony integrations.', ['telnyx']),
|
|
351
439
|
];
|
|
@@ -440,7 +528,11 @@ class _MessagingPanelState extends State<MessagingPanel> {
|
|
|
440
528
|
accessCatalog: controller
|
|
441
529
|
.currentMessagingAccessCatalog(platform.id),
|
|
442
530
|
controller: controller,
|
|
443
|
-
onConnect: () => openMessagingConfig(
|
|
531
|
+
onConnect: () => openMessagingConfig(
|
|
532
|
+
context,
|
|
533
|
+
controller,
|
|
534
|
+
platform,
|
|
535
|
+
),
|
|
444
536
|
onDisconnect: () => controller
|
|
445
537
|
.disconnectMessagingPlatform(platform.id),
|
|
446
538
|
onLogout: () => controller
|
|
@@ -1017,9 +1109,10 @@ class _MessagingActivityItem extends StatelessWidget {
|
|
|
1017
1109
|
}
|
|
1018
1110
|
|
|
1019
1111
|
class RunsPanel extends StatefulWidget {
|
|
1020
|
-
const RunsPanel({super.key, required this.controller});
|
|
1112
|
+
const RunsPanel({super.key, required this.controller, this.embedded = false});
|
|
1021
1113
|
|
|
1022
1114
|
final NeoAgentController controller;
|
|
1115
|
+
final bool embedded;
|
|
1023
1116
|
|
|
1024
1117
|
@override
|
|
1025
1118
|
State<RunsPanel> createState() => _RunsPanelState();
|
|
@@ -1210,18 +1303,31 @@ class _RunsPanelState extends State<RunsPanel> {
|
|
|
1210
1303
|
final detail = _detail?.run.id == selected?.id ? _detail : null;
|
|
1211
1304
|
|
|
1212
1305
|
return ListView(
|
|
1213
|
-
padding: _pagePadding(context),
|
|
1306
|
+
padding: widget.embedded ? EdgeInsets.zero : _pagePadding(context),
|
|
1214
1307
|
children: <Widget>[
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1308
|
+
if (!widget.embedded)
|
|
1309
|
+
_PageTitle(
|
|
1310
|
+
title: 'Runs',
|
|
1311
|
+
subtitle:
|
|
1312
|
+
'Inspect recent runs, failures, tool steps, and final responses.',
|
|
1313
|
+
trailing: OutlinedButton.icon(
|
|
1314
|
+
onPressed: _refreshRuns,
|
|
1315
|
+
icon: Icon(Icons.refresh),
|
|
1316
|
+
label: Text('Refresh'),
|
|
1317
|
+
),
|
|
1318
|
+
)
|
|
1319
|
+
else
|
|
1320
|
+
Align(
|
|
1321
|
+
alignment: Alignment.centerRight,
|
|
1322
|
+
child: Padding(
|
|
1323
|
+
padding: const EdgeInsets.only(bottom: 12),
|
|
1324
|
+
child: OutlinedButton.icon(
|
|
1325
|
+
onPressed: _refreshRuns,
|
|
1326
|
+
icon: const Icon(Icons.refresh),
|
|
1327
|
+
label: const Text('Refresh'),
|
|
1328
|
+
),
|
|
1329
|
+
),
|
|
1223
1330
|
),
|
|
1224
|
-
),
|
|
1225
1331
|
if (controller.errorMessage != null) ...<Widget>[
|
|
1226
1332
|
_InlineError(message: controller.errorMessage!),
|
|
1227
1333
|
const SizedBox(height: 16),
|
|
@@ -2477,6 +2583,13 @@ class _RunHistoryRow extends StatelessWidget {
|
|
|
2477
2583
|
'${run.modelLabel} • ${run.totalTokensLabel} tokens',
|
|
2478
2584
|
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
2479
2585
|
),
|
|
2586
|
+
if (run.deliverableType.trim().isNotEmpty) ...<Widget>[
|
|
2587
|
+
const SizedBox(height: 4),
|
|
2588
|
+
Text(
|
|
2589
|
+
'Deliverable • ${run.deliverableType.replaceAll('_', ' ')}',
|
|
2590
|
+
style: TextStyle(color: _accent, fontSize: 12),
|
|
2591
|
+
),
|
|
2592
|
+
],
|
|
2480
2593
|
if (run.error.trim().isNotEmpty) ...<Widget>[
|
|
2481
2594
|
const SizedBox(height: 8),
|
|
2482
2595
|
Text(
|
|
@@ -2595,6 +2708,12 @@ class _RunDetailWorkspace extends StatelessWidget {
|
|
|
2595
2708
|
helper: 'Subagents or helpers',
|
|
2596
2709
|
color: _accentHover,
|
|
2597
2710
|
),
|
|
2711
|
+
_RunMetricCard(
|
|
2712
|
+
title: 'Trace events',
|
|
2713
|
+
value: '${snapshot.events.length}',
|
|
2714
|
+
helper: 'Structured run timeline',
|
|
2715
|
+
color: _warning,
|
|
2716
|
+
),
|
|
2598
2717
|
],
|
|
2599
2718
|
),
|
|
2600
2719
|
const SizedBox(height: 16),
|
|
@@ -2602,8 +2721,14 @@ class _RunDetailWorkspace extends StatelessWidget {
|
|
|
2602
2721
|
response: snapshot.response,
|
|
2603
2722
|
onCopy: () => onCopyResponse(snapshot.response),
|
|
2604
2723
|
),
|
|
2724
|
+
if (snapshot.run.deliverableType.trim().isNotEmpty) ...<Widget>[
|
|
2725
|
+
const SizedBox(height: 16),
|
|
2726
|
+
_DeliverableSummaryCard(run: snapshot.run),
|
|
2727
|
+
],
|
|
2605
2728
|
const SizedBox(height: 16),
|
|
2606
2729
|
_RunTimelineCard(steps: snapshot.steps, loading: loading),
|
|
2730
|
+
const SizedBox(height: 16),
|
|
2731
|
+
_RunEventTimelineCard(events: snapshot.events, loading: loading),
|
|
2607
2732
|
] else
|
|
2608
2733
|
const _EmptyCard(
|
|
2609
2734
|
title: 'No detail available',
|
|
@@ -2663,6 +2788,11 @@ class _RunHeroCard extends StatelessWidget {
|
|
|
2663
2788
|
label: run.modelLabel,
|
|
2664
2789
|
icon: Icons.memory_outlined,
|
|
2665
2790
|
),
|
|
2791
|
+
if (run.deliverableType.trim().isNotEmpty)
|
|
2792
|
+
_MetaPill(
|
|
2793
|
+
label: run.deliverableType.replaceAll('_', ' '),
|
|
2794
|
+
icon: Icons.inventory_2_outlined,
|
|
2795
|
+
),
|
|
2666
2796
|
],
|
|
2667
2797
|
),
|
|
2668
2798
|
const SizedBox(height: 16),
|
|
@@ -2788,6 +2918,89 @@ class _RunResponseCard extends StatelessWidget {
|
|
|
2788
2918
|
}
|
|
2789
2919
|
}
|
|
2790
2920
|
|
|
2921
|
+
class _DeliverableSummaryCard extends StatelessWidget {
|
|
2922
|
+
const _DeliverableSummaryCard({required this.run});
|
|
2923
|
+
|
|
2924
|
+
final RunSummary run;
|
|
2925
|
+
|
|
2926
|
+
@override
|
|
2927
|
+
Widget build(BuildContext context) {
|
|
2928
|
+
final artifacts = run.deliverableArtifacts;
|
|
2929
|
+
return Card(
|
|
2930
|
+
child: Padding(
|
|
2931
|
+
padding: const EdgeInsets.all(18),
|
|
2932
|
+
child: Column(
|
|
2933
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2934
|
+
children: <Widget>[
|
|
2935
|
+
_SectionTitle('Deliverable'),
|
|
2936
|
+
const SizedBox(height: 12),
|
|
2937
|
+
Text(
|
|
2938
|
+
run.deliverableSummary.ifEmpty(
|
|
2939
|
+
'Workflow: ${run.deliverableType.replaceAll('_', ' ')}',
|
|
2940
|
+
),
|
|
2941
|
+
style: TextStyle(color: _textPrimary, height: 1.45),
|
|
2942
|
+
),
|
|
2943
|
+
if (artifacts.isNotEmpty) ...<Widget>[
|
|
2944
|
+
const SizedBox(height: 14),
|
|
2945
|
+
...artifacts.map((artifact) {
|
|
2946
|
+
final meta = <String>[
|
|
2947
|
+
artifact.kind,
|
|
2948
|
+
if (artifact.mimeType.trim().isNotEmpty) artifact.mimeType,
|
|
2949
|
+
if (artifact.size > 0) '${artifact.size} bytes',
|
|
2950
|
+
].join(' • ');
|
|
2951
|
+
final location = artifact.uri.ifEmpty(artifact.path);
|
|
2952
|
+
return Padding(
|
|
2953
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
2954
|
+
child: Container(
|
|
2955
|
+
width: double.infinity,
|
|
2956
|
+
padding: const EdgeInsets.all(12),
|
|
2957
|
+
decoration: BoxDecoration(
|
|
2958
|
+
color: _bgSecondary,
|
|
2959
|
+
borderRadius: BorderRadius.circular(14),
|
|
2960
|
+
border: Border.all(color: _border),
|
|
2961
|
+
),
|
|
2962
|
+
child: Column(
|
|
2963
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2964
|
+
children: <Widget>[
|
|
2965
|
+
Text(
|
|
2966
|
+
artifact.displayLabel,
|
|
2967
|
+
style: TextStyle(fontWeight: FontWeight.w700),
|
|
2968
|
+
),
|
|
2969
|
+
if (meta.trim().isNotEmpty) ...<Widget>[
|
|
2970
|
+
const SizedBox(height: 4),
|
|
2971
|
+
Text(
|
|
2972
|
+
meta,
|
|
2973
|
+
style: TextStyle(
|
|
2974
|
+
color: _textSecondary,
|
|
2975
|
+
fontSize: 12,
|
|
2976
|
+
),
|
|
2977
|
+
),
|
|
2978
|
+
],
|
|
2979
|
+
if (location.trim().isNotEmpty) ...<Widget>[
|
|
2980
|
+
const SizedBox(height: 6),
|
|
2981
|
+
SelectableText(
|
|
2982
|
+
location,
|
|
2983
|
+
style: TextStyle(
|
|
2984
|
+
color: _textSecondary,
|
|
2985
|
+
fontSize: 12,
|
|
2986
|
+
fontFamily:
|
|
2987
|
+
GoogleFonts.jetBrainsMono().fontFamily,
|
|
2988
|
+
),
|
|
2989
|
+
),
|
|
2990
|
+
],
|
|
2991
|
+
],
|
|
2992
|
+
),
|
|
2993
|
+
),
|
|
2994
|
+
);
|
|
2995
|
+
}),
|
|
2996
|
+
],
|
|
2997
|
+
],
|
|
2998
|
+
),
|
|
2999
|
+
),
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
|
|
2791
3004
|
class _RunTimelineCard extends StatelessWidget {
|
|
2792
3005
|
const _RunTimelineCard({required this.steps, required this.loading});
|
|
2793
3006
|
|
|
@@ -2832,6 +3045,105 @@ class _RunTimelineCard extends StatelessWidget {
|
|
|
2832
3045
|
}
|
|
2833
3046
|
}
|
|
2834
3047
|
|
|
3048
|
+
class _RunEventTimelineCard extends StatelessWidget {
|
|
3049
|
+
const _RunEventTimelineCard({required this.events, required this.loading});
|
|
3050
|
+
|
|
3051
|
+
final List<RunEventItem> events;
|
|
3052
|
+
final bool loading;
|
|
3053
|
+
|
|
3054
|
+
@override
|
|
3055
|
+
Widget build(BuildContext context) {
|
|
3056
|
+
return Card(
|
|
3057
|
+
child: Padding(
|
|
3058
|
+
padding: const EdgeInsets.all(18),
|
|
3059
|
+
child: Column(
|
|
3060
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3061
|
+
children: <Widget>[
|
|
3062
|
+
Row(
|
|
3063
|
+
children: <Widget>[
|
|
3064
|
+
Expanded(child: _SectionTitle('Execution Trace')),
|
|
3065
|
+
if (loading)
|
|
3066
|
+
const SizedBox.square(
|
|
3067
|
+
dimension: 16,
|
|
3068
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
3069
|
+
),
|
|
3070
|
+
],
|
|
3071
|
+
),
|
|
3072
|
+
const SizedBox(height: 12),
|
|
3073
|
+
if (events.isEmpty)
|
|
3074
|
+
Text(
|
|
3075
|
+
'No structured run events recorded yet.',
|
|
3076
|
+
style: TextStyle(color: _textSecondary),
|
|
3077
|
+
)
|
|
3078
|
+
else
|
|
3079
|
+
...events.map((event) {
|
|
3080
|
+
return Padding(
|
|
3081
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
3082
|
+
child: _RunEventCard(event: event),
|
|
3083
|
+
);
|
|
3084
|
+
}),
|
|
3085
|
+
],
|
|
3086
|
+
),
|
|
3087
|
+
),
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
class _RunEventCard extends StatelessWidget {
|
|
3093
|
+
const _RunEventCard({required this.event});
|
|
3094
|
+
|
|
3095
|
+
final RunEventItem event;
|
|
3096
|
+
|
|
3097
|
+
@override
|
|
3098
|
+
Widget build(BuildContext context) {
|
|
3099
|
+
final accent = event.isFailure ? _danger : _info;
|
|
3100
|
+
return Container(
|
|
3101
|
+
padding: const EdgeInsets.all(14),
|
|
3102
|
+
decoration: BoxDecoration(
|
|
3103
|
+
color: _bgSecondary,
|
|
3104
|
+
borderRadius: BorderRadius.circular(16),
|
|
3105
|
+
border: Border.all(color: accent.withValues(alpha: 0.24)),
|
|
3106
|
+
),
|
|
3107
|
+
child: Column(
|
|
3108
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
3109
|
+
children: <Widget>[
|
|
3110
|
+
Row(
|
|
3111
|
+
children: <Widget>[
|
|
3112
|
+
Container(
|
|
3113
|
+
width: 10,
|
|
3114
|
+
height: 10,
|
|
3115
|
+
decoration: BoxDecoration(
|
|
3116
|
+
color: accent,
|
|
3117
|
+
shape: BoxShape.circle,
|
|
3118
|
+
),
|
|
3119
|
+
),
|
|
3120
|
+
const SizedBox(width: 10),
|
|
3121
|
+
Expanded(
|
|
3122
|
+
child: Text(
|
|
3123
|
+
event.title,
|
|
3124
|
+
style: const TextStyle(fontWeight: FontWeight.w700),
|
|
3125
|
+
),
|
|
3126
|
+
),
|
|
3127
|
+
if (event.createdAtLabel.isNotEmpty)
|
|
3128
|
+
Text(
|
|
3129
|
+
event.createdAtLabel,
|
|
3130
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
3131
|
+
),
|
|
3132
|
+
],
|
|
3133
|
+
),
|
|
3134
|
+
if (event.detail.trim().isNotEmpty) ...<Widget>[
|
|
3135
|
+
const SizedBox(height: 8),
|
|
3136
|
+
Text(
|
|
3137
|
+
event.detail,
|
|
3138
|
+
style: TextStyle(color: _textSecondary, height: 1.4),
|
|
3139
|
+
),
|
|
3140
|
+
],
|
|
3141
|
+
],
|
|
3142
|
+
),
|
|
3143
|
+
);
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
2835
3147
|
class _RunStepCard extends StatelessWidget {
|
|
2836
3148
|
const _RunStepCard({required this.step});
|
|
2837
3149
|
|
|
@@ -2994,323 +3306,324 @@ class _RunDetailBlock extends StatelessWidget {
|
|
|
2994
3306
|
}
|
|
2995
3307
|
}
|
|
2996
3308
|
|
|
2997
|
-
Future<void> openMessagingConfig(
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
default:
|
|
3010
|
-
return _openGenericMessagingConfigHelper(context, controller, platform);
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
Future<bool> _connectMessagingPlatformHelper(BuildContext context, NeoAgentController controller, {
|
|
3015
|
-
required String platform,
|
|
3016
|
-
required String platformLabel,
|
|
3017
|
-
Map<String, dynamic>? config,
|
|
3018
|
-
Map<String, dynamic>? configSnapshot,
|
|
3019
|
-
}) async {
|
|
3020
|
-
try {
|
|
3021
|
-
await controller.connectMessagingPlatform(
|
|
3022
|
-
platform: platform,
|
|
3023
|
-
config: config,
|
|
3024
|
-
configSnapshot: configSnapshot,
|
|
3025
|
-
);
|
|
3026
|
-
return true;
|
|
3027
|
-
} catch (error) {
|
|
3028
|
-
if (!context.mounted) return false;
|
|
3029
|
-
final messenger = ScaffoldMessenger.maybeOf(context);
|
|
3030
|
-
messenger?.showSnackBar(
|
|
3031
|
-
SnackBar(
|
|
3032
|
-
content: Text(
|
|
3033
|
-
'Failed to connect $platformLabel: ${controller.friendlyErrorMessage(error)}',
|
|
3034
|
-
),
|
|
3035
|
-
),
|
|
3309
|
+
Future<void> openMessagingConfig(
|
|
3310
|
+
BuildContext context,
|
|
3311
|
+
NeoAgentController controller,
|
|
3312
|
+
MessagingPlatformDescriptor platform,
|
|
3313
|
+
) async {
|
|
3314
|
+
switch (platform.id) {
|
|
3315
|
+
case 'whatsapp':
|
|
3316
|
+
await _connectMessagingPlatformHelper(
|
|
3317
|
+
context,
|
|
3318
|
+
controller,
|
|
3319
|
+
platform: 'whatsapp',
|
|
3320
|
+
platformLabel: platform.label,
|
|
3036
3321
|
);
|
|
3037
|
-
return
|
|
3038
|
-
|
|
3322
|
+
return;
|
|
3323
|
+
case 'telnyx':
|
|
3324
|
+
return _openTelnyxConfigHelper(context, controller);
|
|
3325
|
+
default:
|
|
3326
|
+
return _openGenericMessagingConfigHelper(context, controller, platform);
|
|
3039
3327
|
}
|
|
3328
|
+
}
|
|
3040
3329
|
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3330
|
+
Future<bool> _connectMessagingPlatformHelper(
|
|
3331
|
+
BuildContext context,
|
|
3332
|
+
NeoAgentController controller, {
|
|
3333
|
+
required String platform,
|
|
3334
|
+
required String platformLabel,
|
|
3335
|
+
Map<String, dynamic>? config,
|
|
3336
|
+
Map<String, dynamic>? configSnapshot,
|
|
3337
|
+
}) async {
|
|
3338
|
+
try {
|
|
3339
|
+
await controller.connectMessagingPlatform(
|
|
3340
|
+
platform: platform,
|
|
3341
|
+
config: config,
|
|
3342
|
+
configSnapshot: configSnapshot,
|
|
3053
3343
|
);
|
|
3054
|
-
|
|
3055
|
-
|
|
3344
|
+
return true;
|
|
3345
|
+
} catch (error) {
|
|
3346
|
+
if (!context.mounted) return false;
|
|
3347
|
+
final messenger = ScaffoldMessenger.maybeOf(context);
|
|
3348
|
+
messenger?.showSnackBar(
|
|
3349
|
+
SnackBar(
|
|
3350
|
+
content: Text(
|
|
3351
|
+
'Failed to connect $platformLabel: ${controller.friendlyErrorMessage(error)}',
|
|
3352
|
+
),
|
|
3353
|
+
),
|
|
3056
3354
|
);
|
|
3355
|
+
return false;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3057
3358
|
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3359
|
+
Future<void> _openTelnyxConfigHelper(
|
|
3360
|
+
BuildContext context,
|
|
3361
|
+
NeoAgentController controller,
|
|
3362
|
+
) async {
|
|
3363
|
+
final saved = _jsonMap(
|
|
3364
|
+
_decodeMaybeJson(controller.settings['telnyx_config']),
|
|
3365
|
+
);
|
|
3366
|
+
final apiKey = TextEditingController(text: saved['apiKey']?.toString() ?? '');
|
|
3367
|
+
final phoneNumber = TextEditingController(
|
|
3368
|
+
text: saved['phoneNumber']?.toString() ?? '',
|
|
3369
|
+
);
|
|
3370
|
+
final connectionId = TextEditingController(
|
|
3371
|
+
text: saved['connectionId']?.toString() ?? '',
|
|
3372
|
+
);
|
|
3373
|
+
final webhookUrl = TextEditingController(
|
|
3374
|
+
text: saved['webhookUrl']?.toString() ?? controller.backendUrl,
|
|
3375
|
+
);
|
|
3376
|
+
|
|
3377
|
+
try {
|
|
3378
|
+
await showDialog<void>(
|
|
3379
|
+
context: context,
|
|
3380
|
+
builder: (context) {
|
|
3381
|
+
return StatefulBuilder(
|
|
3382
|
+
builder: (context, setLocalState) {
|
|
3383
|
+
return AlertDialog(
|
|
3384
|
+
backgroundColor: _bgCard,
|
|
3385
|
+
title: Text('Telnyx Voice'),
|
|
3386
|
+
content: SizedBox(
|
|
3387
|
+
width: 620,
|
|
3388
|
+
child: SingleChildScrollView(
|
|
3389
|
+
child: Column(
|
|
3390
|
+
mainAxisSize: MainAxisSize.min,
|
|
3391
|
+
children: <Widget>[
|
|
3392
|
+
TextField(
|
|
3393
|
+
controller: apiKey,
|
|
3394
|
+
obscureText: true,
|
|
3395
|
+
decoration: const InputDecoration(labelText: 'API Key'),
|
|
3396
|
+
),
|
|
3397
|
+
const SizedBox(height: 12),
|
|
3398
|
+
TextField(
|
|
3399
|
+
controller: phoneNumber,
|
|
3400
|
+
decoration: const InputDecoration(
|
|
3401
|
+
labelText: 'Phone Number',
|
|
3093
3402
|
),
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3403
|
+
),
|
|
3404
|
+
const SizedBox(height: 12),
|
|
3405
|
+
TextField(
|
|
3406
|
+
controller: connectionId,
|
|
3407
|
+
decoration: const InputDecoration(
|
|
3408
|
+
labelText: 'Connection ID',
|
|
3100
3409
|
),
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3410
|
+
),
|
|
3411
|
+
const SizedBox(height: 12),
|
|
3412
|
+
TextField(
|
|
3413
|
+
controller: webhookUrl,
|
|
3414
|
+
decoration: const InputDecoration(
|
|
3415
|
+
labelText: 'Webhook Base URL',
|
|
3105
3416
|
),
|
|
3106
|
-
|
|
3107
|
-
|
|
3417
|
+
),
|
|
3418
|
+
const SizedBox(height: 12),
|
|
3419
|
+
Text(
|
|
3420
|
+
'Voice STT/TTS providers and models are configured in global Settings > Voice.',
|
|
3421
|
+
style: TextStyle(color: _textSecondary),
|
|
3422
|
+
),
|
|
3423
|
+
],
|
|
3108
3424
|
),
|
|
3109
3425
|
),
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
),
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3426
|
+
),
|
|
3427
|
+
actions: <Widget>[
|
|
3428
|
+
TextButton(
|
|
3429
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
3430
|
+
child: Text('Cancel'),
|
|
3431
|
+
),
|
|
3432
|
+
FilledButton(
|
|
3433
|
+
onPressed: () async {
|
|
3434
|
+
final config = <String, dynamic>{
|
|
3435
|
+
'apiKey': apiKey.text.trim(),
|
|
3436
|
+
'phoneNumber': phoneNumber.text.trim(),
|
|
3437
|
+
'connectionId': connectionId.text.trim(),
|
|
3438
|
+
'webhookUrl': webhookUrl.text.trim(),
|
|
3439
|
+
};
|
|
3440
|
+
final connected = await _connectMessagingPlatformHelper(
|
|
3441
|
+
context,
|
|
3442
|
+
controller,
|
|
3443
|
+
platform: 'telnyx',
|
|
3444
|
+
platformLabel: 'Telnyx Voice',
|
|
3445
|
+
config: config,
|
|
3446
|
+
configSnapshot: <String, dynamic>{
|
|
3447
|
+
'telnyx_config': jsonEncode(config),
|
|
3448
|
+
},
|
|
3449
|
+
);
|
|
3450
|
+
if (connected && context.mounted) {
|
|
3451
|
+
Navigator.of(context).pop();
|
|
3452
|
+
}
|
|
3453
|
+
},
|
|
3454
|
+
child: Text('Connect'),
|
|
3455
|
+
),
|
|
3456
|
+
],
|
|
3457
|
+
);
|
|
3458
|
+
},
|
|
3459
|
+
);
|
|
3460
|
+
},
|
|
3461
|
+
);
|
|
3462
|
+
} finally {
|
|
3463
|
+
apiKey.dispose();
|
|
3464
|
+
phoneNumber.dispose();
|
|
3465
|
+
connectionId.dispose();
|
|
3466
|
+
webhookUrl.dispose();
|
|
3149
3467
|
}
|
|
3468
|
+
}
|
|
3150
3469
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3470
|
+
Future<void> _openGenericMessagingConfigHelper(
|
|
3471
|
+
BuildContext context,
|
|
3472
|
+
NeoAgentController controller,
|
|
3473
|
+
MessagingPlatformDescriptor platform,
|
|
3474
|
+
) async {
|
|
3475
|
+
final saved = _jsonMap(
|
|
3476
|
+
_decodeMaybeJson(controller.settings[platform.settingsKey]),
|
|
3477
|
+
);
|
|
3478
|
+
final textControllers = <String, TextEditingController>{};
|
|
3479
|
+
final boolValues = <String, bool>{};
|
|
3480
|
+
for (final field in platform.configFields) {
|
|
3481
|
+
final savedValue = field.settingsKey == null
|
|
3482
|
+
? saved[field.key]
|
|
3483
|
+
: controller.settings[field.storageKey];
|
|
3484
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3485
|
+
boolValues[field.key] =
|
|
3486
|
+
savedValue == true || savedValue?.toString() == 'true';
|
|
3487
|
+
} else {
|
|
3488
|
+
textControllers[field.key] = TextEditingController(
|
|
3489
|
+
text: savedValue?.toString() ?? field.defaultValue ?? '',
|
|
3490
|
+
);
|
|
3171
3491
|
}
|
|
3492
|
+
}
|
|
3172
3493
|
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
},
|
|
3206
|
-
);
|
|
3207
|
-
}
|
|
3208
|
-
final controller = textControllers[field.key]!;
|
|
3209
|
-
return Padding(
|
|
3210
|
-
padding: const EdgeInsets.only(bottom: 12),
|
|
3211
|
-
child: TextField(
|
|
3212
|
-
controller: controller,
|
|
3213
|
-
obscureText:
|
|
3214
|
-
field.obscure ||
|
|
3215
|
-
field.kind ==
|
|
3216
|
-
MessagingConfigFieldKind.password,
|
|
3217
|
-
minLines:
|
|
3218
|
-
field.kind ==
|
|
3219
|
-
MessagingConfigFieldKind.multiline
|
|
3220
|
-
? 4
|
|
3221
|
-
: 1,
|
|
3222
|
-
maxLines:
|
|
3223
|
-
field.kind ==
|
|
3224
|
-
MessagingConfigFieldKind.multiline
|
|
3225
|
-
? 8
|
|
3226
|
-
: 1,
|
|
3227
|
-
decoration: InputDecoration(
|
|
3228
|
-
labelText: field.label,
|
|
3229
|
-
),
|
|
3230
|
-
),
|
|
3494
|
+
try {
|
|
3495
|
+
await showDialog<void>(
|
|
3496
|
+
context: context,
|
|
3497
|
+
builder: (context) {
|
|
3498
|
+
return StatefulBuilder(
|
|
3499
|
+
builder: (context, setLocalState) {
|
|
3500
|
+
return AlertDialog(
|
|
3501
|
+
backgroundColor: _bgCard,
|
|
3502
|
+
title: Text(platform.label),
|
|
3503
|
+
content: SizedBox(
|
|
3504
|
+
width: 620,
|
|
3505
|
+
child: SingleChildScrollView(
|
|
3506
|
+
child: Column(
|
|
3507
|
+
mainAxisSize: MainAxisSize.min,
|
|
3508
|
+
children: <Widget>[
|
|
3509
|
+
if (platform.configFields.isEmpty)
|
|
3510
|
+
Text(
|
|
3511
|
+
'No extra settings are required.',
|
|
3512
|
+
style: TextStyle(color: _textSecondary),
|
|
3513
|
+
)
|
|
3514
|
+
else
|
|
3515
|
+
...platform.configFields.map((field) {
|
|
3516
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3517
|
+
return SwitchListTile(
|
|
3518
|
+
contentPadding: EdgeInsets.zero,
|
|
3519
|
+
title: Text(field.label),
|
|
3520
|
+
value: boolValues[field.key] ?? false,
|
|
3521
|
+
onChanged: (value) {
|
|
3522
|
+
setLocalState(() {
|
|
3523
|
+
boolValues[field.key] = value;
|
|
3524
|
+
});
|
|
3525
|
+
},
|
|
3231
3526
|
);
|
|
3232
|
-
}
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3527
|
+
}
|
|
3528
|
+
final controller = textControllers[field.key]!;
|
|
3529
|
+
return Padding(
|
|
3530
|
+
padding: const EdgeInsets.only(bottom: 12),
|
|
3531
|
+
child: TextField(
|
|
3532
|
+
controller: controller,
|
|
3533
|
+
obscureText:
|
|
3534
|
+
field.obscure ||
|
|
3535
|
+
field.kind ==
|
|
3536
|
+
MessagingConfigFieldKind.password,
|
|
3537
|
+
minLines:
|
|
3538
|
+
field.kind ==
|
|
3539
|
+
MessagingConfigFieldKind.multiline
|
|
3540
|
+
? 4
|
|
3541
|
+
: 1,
|
|
3542
|
+
maxLines:
|
|
3543
|
+
field.kind ==
|
|
3544
|
+
MessagingConfigFieldKind.multiline
|
|
3545
|
+
? 8
|
|
3546
|
+
: 1,
|
|
3547
|
+
decoration: InputDecoration(
|
|
3548
|
+
labelText: field.label,
|
|
3549
|
+
),
|
|
3248
3550
|
),
|
|
3249
|
-
)
|
|
3250
|
-
|
|
3251
|
-
|
|
3551
|
+
);
|
|
3552
|
+
}),
|
|
3553
|
+
const SizedBox(height: 8),
|
|
3554
|
+
if (platform.id == 'meshtastic')
|
|
3555
|
+
Text(
|
|
3556
|
+
'Meshtastic connects directly to the device TCP API on port 4403 by default. Normal chat is limited to the configured channel.',
|
|
3557
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
3558
|
+
)
|
|
3559
|
+
else
|
|
3560
|
+
SelectableText(
|
|
3561
|
+
'Inbound webhook: ${controller.backendUrl}/api/messaging/webhook/${platform.id}',
|
|
3562
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
3563
|
+
),
|
|
3564
|
+
],
|
|
3252
3565
|
),
|
|
3253
3566
|
),
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
),
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3567
|
+
),
|
|
3568
|
+
actions: <Widget>[
|
|
3569
|
+
TextButton(
|
|
3570
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
3571
|
+
child: Text('Cancel'),
|
|
3572
|
+
),
|
|
3573
|
+
FilledButton(
|
|
3574
|
+
onPressed: () async {
|
|
3575
|
+
final config = <String, dynamic>{};
|
|
3576
|
+
final snapshot = <String, dynamic>{};
|
|
3577
|
+
for (final field in platform.configFields) {
|
|
3578
|
+
if (field.kind == MessagingConfigFieldKind.boolean ||
|
|
3579
|
+
!field.includeInConfig) {
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
final controller = textControllers[field.key];
|
|
3583
|
+
final value = controller?.text.trim() ?? '';
|
|
3584
|
+
if (value.isNotEmpty) config[field.key] = value;
|
|
3585
|
+
}
|
|
3586
|
+
for (final field in platform.configFields) {
|
|
3587
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3588
|
+
final value = boolValues[field.key] ?? false;
|
|
3589
|
+
if (field.includeInConfig) {
|
|
3590
|
+
config[field.key] = value;
|
|
3591
|
+
}
|
|
3592
|
+
if (field.settingsKey != null) {
|
|
3593
|
+
snapshot[field.storageKey] = value;
|
|
3267
3594
|
}
|
|
3595
|
+
} else if (field.settingsKey != null) {
|
|
3268
3596
|
final controller = textControllers[field.key];
|
|
3269
3597
|
final value = controller?.text.trim() ?? '';
|
|
3270
|
-
if (value.isNotEmpty)
|
|
3271
|
-
|
|
3272
|
-
for (final field in platform.configFields) {
|
|
3273
|
-
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3274
|
-
final value = boolValues[field.key] ?? false;
|
|
3275
|
-
if (field.includeInConfig) {
|
|
3276
|
-
config[field.key] = value;
|
|
3277
|
-
}
|
|
3278
|
-
if (field.settingsKey != null) {
|
|
3279
|
-
snapshot[field.storageKey] = value;
|
|
3280
|
-
}
|
|
3281
|
-
} else if (field.settingsKey != null) {
|
|
3282
|
-
final controller = textControllers[field.key];
|
|
3283
|
-
final value = controller?.text.trim() ?? '';
|
|
3284
|
-
if (value.isNotEmpty) {
|
|
3285
|
-
snapshot[field.storageKey] = value;
|
|
3286
|
-
}
|
|
3598
|
+
if (value.isNotEmpty) {
|
|
3599
|
+
snapshot[field.storageKey] = value;
|
|
3287
3600
|
}
|
|
3288
3601
|
}
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
),
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3602
|
+
}
|
|
3603
|
+
snapshot[platform.settingsKey] = jsonEncode(config);
|
|
3604
|
+
final connected = await _connectMessagingPlatformHelper(
|
|
3605
|
+
context,
|
|
3606
|
+
controller,
|
|
3607
|
+
platform: platform.id,
|
|
3608
|
+
platformLabel: platform.label,
|
|
3609
|
+
config: config,
|
|
3610
|
+
configSnapshot: snapshot,
|
|
3611
|
+
);
|
|
3612
|
+
if (connected && context.mounted) {
|
|
3613
|
+
Navigator.of(context).pop();
|
|
3614
|
+
}
|
|
3615
|
+
},
|
|
3616
|
+
child: Text('Connect'),
|
|
3617
|
+
),
|
|
3618
|
+
],
|
|
3619
|
+
);
|
|
3620
|
+
},
|
|
3621
|
+
);
|
|
3622
|
+
},
|
|
3623
|
+
);
|
|
3624
|
+
} finally {
|
|
3625
|
+
for (final controller in textControllers.values) {
|
|
3626
|
+
controller.dispose();
|
|
3314
3627
|
}
|
|
3315
3628
|
}
|
|
3316
|
-
|
|
3629
|
+
}
|