neoagent 2.3.1-beta.62 → 2.3.1-beta.64
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 +9 -0
- package/docs/configuration.md +11 -0
- package/flutter_app/lib/main.dart +2 -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 +433 -309
- package/flutter_app/lib/main_controller.dart +164 -6
- package/flutter_app/lib/main_integrations.dart +15 -7
- package/flutter_app/lib/main_models.dart +116 -7
- package/flutter_app/lib/main_navigation.dart +27 -18
- package/flutter_app/lib/main_operations.dart +162 -91
- package/flutter_app/lib/main_runtime.dart +22 -0
- package/flutter_app/lib/main_settings.dart +287 -75
- package/flutter_app/lib/main_shared.dart +165 -6
- package/flutter_app/lib/main_unified.dart +388 -0
- package/flutter_app/lib/src/analytics_service.dart +294 -0
- package/flutter_app/lib/src/backend_client.dart +4 -0
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +8 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/flutter_app/web/index.html +1 -0
- package/package.json +1 -1
- package/server/config/analytics.js +30 -0
- package/server/db/database.js +52 -0
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/NOTICES +183 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/packages/mixpanel_flutter/assets/mixpanel.js +3 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/index.html +1 -0
- package/server/public/main.dart.js +83685 -82046
- package/server/routes/agents.js +2 -1
- package/server/routes/memory.js +75 -3
- package/server/routes/runtime.js +14 -0
- package/server/routes/widgets.js +4 -4
- package/server/services/ai/engine.js +86 -0
- package/server/services/ai/runEvents.js +100 -0
- package/server/services/memory/manager.js +242 -26
- package/server/services/websocket.js +3 -1
- package/server/services/widgets/focus_widget.js +126 -0
- package/server/services/widgets/service.js +130 -2
|
@@ -343,9 +343,7 @@ class _MessagingPanelState extends State<MessagingPanel> {
|
|
|
343
343
|
const (
|
|
344
344
|
'Hardware Bridges',
|
|
345
345
|
'Local device bridges and TCP-connected integrations.',
|
|
346
|
-
[
|
|
347
|
-
'meshtastic',
|
|
348
|
-
],
|
|
346
|
+
['meshtastic'],
|
|
349
347
|
),
|
|
350
348
|
const ('Voice', 'Telephony integrations.', ['telnyx']),
|
|
351
349
|
];
|
|
@@ -440,7 +438,11 @@ class _MessagingPanelState extends State<MessagingPanel> {
|
|
|
440
438
|
accessCatalog: controller
|
|
441
439
|
.currentMessagingAccessCatalog(platform.id),
|
|
442
440
|
controller: controller,
|
|
443
|
-
onConnect: () => openMessagingConfig(
|
|
441
|
+
onConnect: () => openMessagingConfig(
|
|
442
|
+
context,
|
|
443
|
+
controller,
|
|
444
|
+
platform,
|
|
445
|
+
),
|
|
444
446
|
onDisconnect: () => controller
|
|
445
447
|
.disconnectMessagingPlatform(platform.id),
|
|
446
448
|
onLogout: () => controller
|
|
@@ -1017,9 +1019,10 @@ class _MessagingActivityItem extends StatelessWidget {
|
|
|
1017
1019
|
}
|
|
1018
1020
|
|
|
1019
1021
|
class RunsPanel extends StatefulWidget {
|
|
1020
|
-
const RunsPanel({super.key, required this.controller});
|
|
1022
|
+
const RunsPanel({super.key, required this.controller, this.embedded = false});
|
|
1021
1023
|
|
|
1022
1024
|
final NeoAgentController controller;
|
|
1025
|
+
final bool embedded;
|
|
1023
1026
|
|
|
1024
1027
|
@override
|
|
1025
1028
|
State<RunsPanel> createState() => _RunsPanelState();
|
|
@@ -1210,18 +1213,31 @@ class _RunsPanelState extends State<RunsPanel> {
|
|
|
1210
1213
|
final detail = _detail?.run.id == selected?.id ? _detail : null;
|
|
1211
1214
|
|
|
1212
1215
|
return ListView(
|
|
1213
|
-
padding: _pagePadding(context),
|
|
1216
|
+
padding: widget.embedded ? EdgeInsets.zero : _pagePadding(context),
|
|
1214
1217
|
children: <Widget>[
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1218
|
+
if (!widget.embedded)
|
|
1219
|
+
_PageTitle(
|
|
1220
|
+
title: 'Runs',
|
|
1221
|
+
subtitle:
|
|
1222
|
+
'Inspect recent runs, failures, tool steps, and final responses.',
|
|
1223
|
+
trailing: OutlinedButton.icon(
|
|
1224
|
+
onPressed: _refreshRuns,
|
|
1225
|
+
icon: Icon(Icons.refresh),
|
|
1226
|
+
label: Text('Refresh'),
|
|
1227
|
+
),
|
|
1228
|
+
)
|
|
1229
|
+
else
|
|
1230
|
+
Align(
|
|
1231
|
+
alignment: Alignment.centerRight,
|
|
1232
|
+
child: Padding(
|
|
1233
|
+
padding: const EdgeInsets.only(bottom: 12),
|
|
1234
|
+
child: OutlinedButton.icon(
|
|
1235
|
+
onPressed: _refreshRuns,
|
|
1236
|
+
icon: const Icon(Icons.refresh),
|
|
1237
|
+
label: const Text('Refresh'),
|
|
1238
|
+
),
|
|
1239
|
+
),
|
|
1223
1240
|
),
|
|
1224
|
-
),
|
|
1225
1241
|
if (controller.errorMessage != null) ...<Widget>[
|
|
1226
1242
|
_InlineError(message: controller.errorMessage!),
|
|
1227
1243
|
const SizedBox(height: 16),
|
|
@@ -2595,6 +2611,12 @@ class _RunDetailWorkspace extends StatelessWidget {
|
|
|
2595
2611
|
helper: 'Subagents or helpers',
|
|
2596
2612
|
color: _accentHover,
|
|
2597
2613
|
),
|
|
2614
|
+
_RunMetricCard(
|
|
2615
|
+
title: 'Trace events',
|
|
2616
|
+
value: '${snapshot.events.length}',
|
|
2617
|
+
helper: 'Structured run timeline',
|
|
2618
|
+
color: _warning,
|
|
2619
|
+
),
|
|
2598
2620
|
],
|
|
2599
2621
|
),
|
|
2600
2622
|
const SizedBox(height: 16),
|
|
@@ -2604,6 +2626,8 @@ class _RunDetailWorkspace extends StatelessWidget {
|
|
|
2604
2626
|
),
|
|
2605
2627
|
const SizedBox(height: 16),
|
|
2606
2628
|
_RunTimelineCard(steps: snapshot.steps, loading: loading),
|
|
2629
|
+
const SizedBox(height: 16),
|
|
2630
|
+
_RunEventTimelineCard(events: snapshot.events, loading: loading),
|
|
2607
2631
|
] else
|
|
2608
2632
|
const _EmptyCard(
|
|
2609
2633
|
title: 'No detail available',
|
|
@@ -2832,6 +2856,105 @@ class _RunTimelineCard extends StatelessWidget {
|
|
|
2832
2856
|
}
|
|
2833
2857
|
}
|
|
2834
2858
|
|
|
2859
|
+
class _RunEventTimelineCard extends StatelessWidget {
|
|
2860
|
+
const _RunEventTimelineCard({required this.events, required this.loading});
|
|
2861
|
+
|
|
2862
|
+
final List<RunEventItem> events;
|
|
2863
|
+
final bool loading;
|
|
2864
|
+
|
|
2865
|
+
@override
|
|
2866
|
+
Widget build(BuildContext context) {
|
|
2867
|
+
return Card(
|
|
2868
|
+
child: Padding(
|
|
2869
|
+
padding: const EdgeInsets.all(18),
|
|
2870
|
+
child: Column(
|
|
2871
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2872
|
+
children: <Widget>[
|
|
2873
|
+
Row(
|
|
2874
|
+
children: <Widget>[
|
|
2875
|
+
Expanded(child: _SectionTitle('Execution Trace')),
|
|
2876
|
+
if (loading)
|
|
2877
|
+
const SizedBox.square(
|
|
2878
|
+
dimension: 16,
|
|
2879
|
+
child: CircularProgressIndicator(strokeWidth: 2),
|
|
2880
|
+
),
|
|
2881
|
+
],
|
|
2882
|
+
),
|
|
2883
|
+
const SizedBox(height: 12),
|
|
2884
|
+
if (events.isEmpty)
|
|
2885
|
+
Text(
|
|
2886
|
+
'No structured run events recorded yet.',
|
|
2887
|
+
style: TextStyle(color: _textSecondary),
|
|
2888
|
+
)
|
|
2889
|
+
else
|
|
2890
|
+
...events.map((event) {
|
|
2891
|
+
return Padding(
|
|
2892
|
+
padding: const EdgeInsets.only(bottom: 10),
|
|
2893
|
+
child: _RunEventCard(event: event),
|
|
2894
|
+
);
|
|
2895
|
+
}),
|
|
2896
|
+
],
|
|
2897
|
+
),
|
|
2898
|
+
),
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
class _RunEventCard extends StatelessWidget {
|
|
2904
|
+
const _RunEventCard({required this.event});
|
|
2905
|
+
|
|
2906
|
+
final RunEventItem event;
|
|
2907
|
+
|
|
2908
|
+
@override
|
|
2909
|
+
Widget build(BuildContext context) {
|
|
2910
|
+
final accent = event.isFailure ? _danger : _info;
|
|
2911
|
+
return Container(
|
|
2912
|
+
padding: const EdgeInsets.all(14),
|
|
2913
|
+
decoration: BoxDecoration(
|
|
2914
|
+
color: _bgSecondary,
|
|
2915
|
+
borderRadius: BorderRadius.circular(16),
|
|
2916
|
+
border: Border.all(color: accent.withValues(alpha: 0.24)),
|
|
2917
|
+
),
|
|
2918
|
+
child: Column(
|
|
2919
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2920
|
+
children: <Widget>[
|
|
2921
|
+
Row(
|
|
2922
|
+
children: <Widget>[
|
|
2923
|
+
Container(
|
|
2924
|
+
width: 10,
|
|
2925
|
+
height: 10,
|
|
2926
|
+
decoration: BoxDecoration(
|
|
2927
|
+
color: accent,
|
|
2928
|
+
shape: BoxShape.circle,
|
|
2929
|
+
),
|
|
2930
|
+
),
|
|
2931
|
+
const SizedBox(width: 10),
|
|
2932
|
+
Expanded(
|
|
2933
|
+
child: Text(
|
|
2934
|
+
event.title,
|
|
2935
|
+
style: const TextStyle(fontWeight: FontWeight.w700),
|
|
2936
|
+
),
|
|
2937
|
+
),
|
|
2938
|
+
if (event.createdAtLabel.isNotEmpty)
|
|
2939
|
+
Text(
|
|
2940
|
+
event.createdAtLabel,
|
|
2941
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
2942
|
+
),
|
|
2943
|
+
],
|
|
2944
|
+
),
|
|
2945
|
+
if (event.detail.trim().isNotEmpty) ...<Widget>[
|
|
2946
|
+
const SizedBox(height: 8),
|
|
2947
|
+
Text(
|
|
2948
|
+
event.detail,
|
|
2949
|
+
style: TextStyle(color: _textSecondary, height: 1.4),
|
|
2950
|
+
),
|
|
2951
|
+
],
|
|
2952
|
+
],
|
|
2953
|
+
),
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2835
2958
|
class _RunStepCard extends StatelessWidget {
|
|
2836
2959
|
const _RunStepCard({required this.step});
|
|
2837
2960
|
|
|
@@ -2994,323 +3117,324 @@ class _RunDetailBlock extends StatelessWidget {
|
|
|
2994
3117
|
}
|
|
2995
3118
|
}
|
|
2996
3119
|
|
|
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,
|
|
3120
|
+
Future<void> openMessagingConfig(
|
|
3121
|
+
BuildContext context,
|
|
3122
|
+
NeoAgentController controller,
|
|
3123
|
+
MessagingPlatformDescriptor platform,
|
|
3124
|
+
) async {
|
|
3125
|
+
switch (platform.id) {
|
|
3126
|
+
case 'whatsapp':
|
|
3127
|
+
await _connectMessagingPlatformHelper(
|
|
3128
|
+
context,
|
|
3129
|
+
controller,
|
|
3130
|
+
platform: 'whatsapp',
|
|
3131
|
+
platformLabel: platform.label,
|
|
3025
3132
|
);
|
|
3026
|
-
return
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
SnackBar(
|
|
3032
|
-
content: Text(
|
|
3033
|
-
'Failed to connect $platformLabel: ${controller.friendlyErrorMessage(error)}',
|
|
3034
|
-
),
|
|
3035
|
-
),
|
|
3036
|
-
);
|
|
3037
|
-
return false;
|
|
3038
|
-
}
|
|
3133
|
+
return;
|
|
3134
|
+
case 'telnyx':
|
|
3135
|
+
return _openTelnyxConfigHelper(context, controller);
|
|
3136
|
+
default:
|
|
3137
|
+
return _openGenericMessagingConfigHelper(context, controller, platform);
|
|
3039
3138
|
}
|
|
3139
|
+
}
|
|
3040
3140
|
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3141
|
+
Future<bool> _connectMessagingPlatformHelper(
|
|
3142
|
+
BuildContext context,
|
|
3143
|
+
NeoAgentController controller, {
|
|
3144
|
+
required String platform,
|
|
3145
|
+
required String platformLabel,
|
|
3146
|
+
Map<String, dynamic>? config,
|
|
3147
|
+
Map<String, dynamic>? configSnapshot,
|
|
3148
|
+
}) async {
|
|
3149
|
+
try {
|
|
3150
|
+
await controller.connectMessagingPlatform(
|
|
3151
|
+
platform: platform,
|
|
3152
|
+
config: config,
|
|
3153
|
+
configSnapshot: configSnapshot,
|
|
3053
3154
|
);
|
|
3054
|
-
|
|
3055
|
-
|
|
3155
|
+
return true;
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
if (!context.mounted) return false;
|
|
3158
|
+
final messenger = ScaffoldMessenger.maybeOf(context);
|
|
3159
|
+
messenger?.showSnackBar(
|
|
3160
|
+
SnackBar(
|
|
3161
|
+
content: Text(
|
|
3162
|
+
'Failed to connect $platformLabel: ${controller.friendlyErrorMessage(error)}',
|
|
3163
|
+
),
|
|
3164
|
+
),
|
|
3056
3165
|
);
|
|
3166
|
+
return false;
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3057
3169
|
|
|
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
|
-
|
|
3170
|
+
Future<void> _openTelnyxConfigHelper(
|
|
3171
|
+
BuildContext context,
|
|
3172
|
+
NeoAgentController controller,
|
|
3173
|
+
) async {
|
|
3174
|
+
final saved = _jsonMap(
|
|
3175
|
+
_decodeMaybeJson(controller.settings['telnyx_config']),
|
|
3176
|
+
);
|
|
3177
|
+
final apiKey = TextEditingController(text: saved['apiKey']?.toString() ?? '');
|
|
3178
|
+
final phoneNumber = TextEditingController(
|
|
3179
|
+
text: saved['phoneNumber']?.toString() ?? '',
|
|
3180
|
+
);
|
|
3181
|
+
final connectionId = TextEditingController(
|
|
3182
|
+
text: saved['connectionId']?.toString() ?? '',
|
|
3183
|
+
);
|
|
3184
|
+
final webhookUrl = TextEditingController(
|
|
3185
|
+
text: saved['webhookUrl']?.toString() ?? controller.backendUrl,
|
|
3186
|
+
);
|
|
3187
|
+
|
|
3188
|
+
try {
|
|
3189
|
+
await showDialog<void>(
|
|
3190
|
+
context: context,
|
|
3191
|
+
builder: (context) {
|
|
3192
|
+
return StatefulBuilder(
|
|
3193
|
+
builder: (context, setLocalState) {
|
|
3194
|
+
return AlertDialog(
|
|
3195
|
+
backgroundColor: _bgCard,
|
|
3196
|
+
title: Text('Telnyx Voice'),
|
|
3197
|
+
content: SizedBox(
|
|
3198
|
+
width: 620,
|
|
3199
|
+
child: SingleChildScrollView(
|
|
3200
|
+
child: Column(
|
|
3201
|
+
mainAxisSize: MainAxisSize.min,
|
|
3202
|
+
children: <Widget>[
|
|
3203
|
+
TextField(
|
|
3204
|
+
controller: apiKey,
|
|
3205
|
+
obscureText: true,
|
|
3206
|
+
decoration: const InputDecoration(labelText: 'API Key'),
|
|
3207
|
+
),
|
|
3208
|
+
const SizedBox(height: 12),
|
|
3209
|
+
TextField(
|
|
3210
|
+
controller: phoneNumber,
|
|
3211
|
+
decoration: const InputDecoration(
|
|
3212
|
+
labelText: 'Phone Number',
|
|
3093
3213
|
),
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3214
|
+
),
|
|
3215
|
+
const SizedBox(height: 12),
|
|
3216
|
+
TextField(
|
|
3217
|
+
controller: connectionId,
|
|
3218
|
+
decoration: const InputDecoration(
|
|
3219
|
+
labelText: 'Connection ID',
|
|
3100
3220
|
),
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3221
|
+
),
|
|
3222
|
+
const SizedBox(height: 12),
|
|
3223
|
+
TextField(
|
|
3224
|
+
controller: webhookUrl,
|
|
3225
|
+
decoration: const InputDecoration(
|
|
3226
|
+
labelText: 'Webhook Base URL',
|
|
3105
3227
|
),
|
|
3106
|
-
|
|
3107
|
-
|
|
3228
|
+
),
|
|
3229
|
+
const SizedBox(height: 12),
|
|
3230
|
+
Text(
|
|
3231
|
+
'Voice STT/TTS providers and models are configured in global Settings > Voice.',
|
|
3232
|
+
style: TextStyle(color: _textSecondary),
|
|
3233
|
+
),
|
|
3234
|
+
],
|
|
3108
3235
|
),
|
|
3109
3236
|
),
|
|
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
|
-
|
|
3237
|
+
),
|
|
3238
|
+
actions: <Widget>[
|
|
3239
|
+
TextButton(
|
|
3240
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
3241
|
+
child: Text('Cancel'),
|
|
3242
|
+
),
|
|
3243
|
+
FilledButton(
|
|
3244
|
+
onPressed: () async {
|
|
3245
|
+
final config = <String, dynamic>{
|
|
3246
|
+
'apiKey': apiKey.text.trim(),
|
|
3247
|
+
'phoneNumber': phoneNumber.text.trim(),
|
|
3248
|
+
'connectionId': connectionId.text.trim(),
|
|
3249
|
+
'webhookUrl': webhookUrl.text.trim(),
|
|
3250
|
+
};
|
|
3251
|
+
final connected = await _connectMessagingPlatformHelper(
|
|
3252
|
+
context,
|
|
3253
|
+
controller,
|
|
3254
|
+
platform: 'telnyx',
|
|
3255
|
+
platformLabel: 'Telnyx Voice',
|
|
3256
|
+
config: config,
|
|
3257
|
+
configSnapshot: <String, dynamic>{
|
|
3258
|
+
'telnyx_config': jsonEncode(config),
|
|
3259
|
+
},
|
|
3260
|
+
);
|
|
3261
|
+
if (connected && context.mounted) {
|
|
3262
|
+
Navigator.of(context).pop();
|
|
3263
|
+
}
|
|
3264
|
+
},
|
|
3265
|
+
child: Text('Connect'),
|
|
3266
|
+
),
|
|
3267
|
+
],
|
|
3268
|
+
);
|
|
3269
|
+
},
|
|
3270
|
+
);
|
|
3271
|
+
},
|
|
3272
|
+
);
|
|
3273
|
+
} finally {
|
|
3274
|
+
apiKey.dispose();
|
|
3275
|
+
phoneNumber.dispose();
|
|
3276
|
+
connectionId.dispose();
|
|
3277
|
+
webhookUrl.dispose();
|
|
3149
3278
|
}
|
|
3279
|
+
}
|
|
3150
3280
|
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3281
|
+
Future<void> _openGenericMessagingConfigHelper(
|
|
3282
|
+
BuildContext context,
|
|
3283
|
+
NeoAgentController controller,
|
|
3284
|
+
MessagingPlatformDescriptor platform,
|
|
3285
|
+
) async {
|
|
3286
|
+
final saved = _jsonMap(
|
|
3287
|
+
_decodeMaybeJson(controller.settings[platform.settingsKey]),
|
|
3288
|
+
);
|
|
3289
|
+
final textControllers = <String, TextEditingController>{};
|
|
3290
|
+
final boolValues = <String, bool>{};
|
|
3291
|
+
for (final field in platform.configFields) {
|
|
3292
|
+
final savedValue = field.settingsKey == null
|
|
3293
|
+
? saved[field.key]
|
|
3294
|
+
: controller.settings[field.storageKey];
|
|
3295
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3296
|
+
boolValues[field.key] =
|
|
3297
|
+
savedValue == true || savedValue?.toString() == 'true';
|
|
3298
|
+
} else {
|
|
3299
|
+
textControllers[field.key] = TextEditingController(
|
|
3300
|
+
text: savedValue?.toString() ?? field.defaultValue ?? '',
|
|
3301
|
+
);
|
|
3171
3302
|
}
|
|
3303
|
+
}
|
|
3172
3304
|
|
|
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
|
-
),
|
|
3305
|
+
try {
|
|
3306
|
+
await showDialog<void>(
|
|
3307
|
+
context: context,
|
|
3308
|
+
builder: (context) {
|
|
3309
|
+
return StatefulBuilder(
|
|
3310
|
+
builder: (context, setLocalState) {
|
|
3311
|
+
return AlertDialog(
|
|
3312
|
+
backgroundColor: _bgCard,
|
|
3313
|
+
title: Text(platform.label),
|
|
3314
|
+
content: SizedBox(
|
|
3315
|
+
width: 620,
|
|
3316
|
+
child: SingleChildScrollView(
|
|
3317
|
+
child: Column(
|
|
3318
|
+
mainAxisSize: MainAxisSize.min,
|
|
3319
|
+
children: <Widget>[
|
|
3320
|
+
if (platform.configFields.isEmpty)
|
|
3321
|
+
Text(
|
|
3322
|
+
'No extra settings are required.',
|
|
3323
|
+
style: TextStyle(color: _textSecondary),
|
|
3324
|
+
)
|
|
3325
|
+
else
|
|
3326
|
+
...platform.configFields.map((field) {
|
|
3327
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3328
|
+
return SwitchListTile(
|
|
3329
|
+
contentPadding: EdgeInsets.zero,
|
|
3330
|
+
title: Text(field.label),
|
|
3331
|
+
value: boolValues[field.key] ?? false,
|
|
3332
|
+
onChanged: (value) {
|
|
3333
|
+
setLocalState(() {
|
|
3334
|
+
boolValues[field.key] = value;
|
|
3335
|
+
});
|
|
3336
|
+
},
|
|
3231
3337
|
);
|
|
3232
|
-
}
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3338
|
+
}
|
|
3339
|
+
final controller = textControllers[field.key]!;
|
|
3340
|
+
return Padding(
|
|
3341
|
+
padding: const EdgeInsets.only(bottom: 12),
|
|
3342
|
+
child: TextField(
|
|
3343
|
+
controller: controller,
|
|
3344
|
+
obscureText:
|
|
3345
|
+
field.obscure ||
|
|
3346
|
+
field.kind ==
|
|
3347
|
+
MessagingConfigFieldKind.password,
|
|
3348
|
+
minLines:
|
|
3349
|
+
field.kind ==
|
|
3350
|
+
MessagingConfigFieldKind.multiline
|
|
3351
|
+
? 4
|
|
3352
|
+
: 1,
|
|
3353
|
+
maxLines:
|
|
3354
|
+
field.kind ==
|
|
3355
|
+
MessagingConfigFieldKind.multiline
|
|
3356
|
+
? 8
|
|
3357
|
+
: 1,
|
|
3358
|
+
decoration: InputDecoration(
|
|
3359
|
+
labelText: field.label,
|
|
3360
|
+
),
|
|
3248
3361
|
),
|
|
3249
|
-
)
|
|
3250
|
-
|
|
3251
|
-
|
|
3362
|
+
);
|
|
3363
|
+
}),
|
|
3364
|
+
const SizedBox(height: 8),
|
|
3365
|
+
if (platform.id == 'meshtastic')
|
|
3366
|
+
Text(
|
|
3367
|
+
'Meshtastic connects directly to the device TCP API on port 4403 by default. Normal chat is limited to the configured channel.',
|
|
3368
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
3369
|
+
)
|
|
3370
|
+
else
|
|
3371
|
+
SelectableText(
|
|
3372
|
+
'Inbound webhook: ${controller.backendUrl}/api/messaging/webhook/${platform.id}',
|
|
3373
|
+
style: TextStyle(color: _textSecondary, fontSize: 12),
|
|
3374
|
+
),
|
|
3375
|
+
],
|
|
3252
3376
|
),
|
|
3253
3377
|
),
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
),
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3378
|
+
),
|
|
3379
|
+
actions: <Widget>[
|
|
3380
|
+
TextButton(
|
|
3381
|
+
onPressed: () => Navigator.of(context).pop(),
|
|
3382
|
+
child: Text('Cancel'),
|
|
3383
|
+
),
|
|
3384
|
+
FilledButton(
|
|
3385
|
+
onPressed: () async {
|
|
3386
|
+
final config = <String, dynamic>{};
|
|
3387
|
+
final snapshot = <String, dynamic>{};
|
|
3388
|
+
for (final field in platform.configFields) {
|
|
3389
|
+
if (field.kind == MessagingConfigFieldKind.boolean ||
|
|
3390
|
+
!field.includeInConfig) {
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
final controller = textControllers[field.key];
|
|
3394
|
+
final value = controller?.text.trim() ?? '';
|
|
3395
|
+
if (value.isNotEmpty) config[field.key] = value;
|
|
3396
|
+
}
|
|
3397
|
+
for (final field in platform.configFields) {
|
|
3398
|
+
if (field.kind == MessagingConfigFieldKind.boolean) {
|
|
3399
|
+
final value = boolValues[field.key] ?? false;
|
|
3400
|
+
if (field.includeInConfig) {
|
|
3401
|
+
config[field.key] = value;
|
|
3402
|
+
}
|
|
3403
|
+
if (field.settingsKey != null) {
|
|
3404
|
+
snapshot[field.storageKey] = value;
|
|
3267
3405
|
}
|
|
3406
|
+
} else if (field.settingsKey != null) {
|
|
3268
3407
|
final controller = textControllers[field.key];
|
|
3269
3408
|
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
|
-
}
|
|
3409
|
+
if (value.isNotEmpty) {
|
|
3410
|
+
snapshot[field.storageKey] = value;
|
|
3287
3411
|
}
|
|
3288
3412
|
}
|
|
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
|
-
|
|
3413
|
+
}
|
|
3414
|
+
snapshot[platform.settingsKey] = jsonEncode(config);
|
|
3415
|
+
final connected = await _connectMessagingPlatformHelper(
|
|
3416
|
+
context,
|
|
3417
|
+
controller,
|
|
3418
|
+
platform: platform.id,
|
|
3419
|
+
platformLabel: platform.label,
|
|
3420
|
+
config: config,
|
|
3421
|
+
configSnapshot: snapshot,
|
|
3422
|
+
);
|
|
3423
|
+
if (connected && context.mounted) {
|
|
3424
|
+
Navigator.of(context).pop();
|
|
3425
|
+
}
|
|
3426
|
+
},
|
|
3427
|
+
child: Text('Connect'),
|
|
3428
|
+
),
|
|
3429
|
+
],
|
|
3430
|
+
);
|
|
3431
|
+
},
|
|
3432
|
+
);
|
|
3433
|
+
},
|
|
3434
|
+
);
|
|
3435
|
+
} finally {
|
|
3436
|
+
for (final controller in textControllers.values) {
|
|
3437
|
+
controller.dispose();
|
|
3314
3438
|
}
|
|
3315
3439
|
}
|
|
3316
|
-
|
|
3440
|
+
}
|