neoagent 2.4.1-beta.39 → 2.4.1-beta.40
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 +11 -0
- package/docs/configuration.md +2 -0
- package/flutter_app/lib/main_model_picker.dart +55 -1
- package/flutter_app/lib/main_models.dart +4 -0
- package/flutter_app/lib/main_settings.dart +1 -318
- package/package.json +1 -1
- 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 +41021 -41183
- package/server/services/ai/models.js +141 -97
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +34 -7
- package/server/services/ai/providers/grok.js +14 -1
- package/server/services/ai/providers/nvidia.js +12 -0
- package/server/services/ai/providers/openai.js +18 -4
- package/server/services/ai/providers/openrouter.js +139 -0
- package/server/services/ai/settings.js +10 -0
- package/server/services/ai/systemPrompt.js +36 -20
package/.env.example
CHANGED
|
@@ -111,6 +111,17 @@ GOOGLE_AI_KEY=your-google-ai-key-here
|
|
|
111
111
|
# • MiniMax-M2.7 via the Anthropic-compatible MiniMax endpoint
|
|
112
112
|
MINIMAX_API_KEY=your-minimax-api-key-here
|
|
113
113
|
|
|
114
|
+
# NVIDIA NIM API key — used for:
|
|
115
|
+
# • Free-tier and paid models on NVIDIA's hosted inference (Nemotron, Kimi, Llama 4, DeepSeek, etc.)
|
|
116
|
+
# Get your key at: https://build.nvidia.com
|
|
117
|
+
NVIDIA_API_KEY=your-nvidia-nim-api-key-here
|
|
118
|
+
|
|
119
|
+
# OpenRouter API key — used for:
|
|
120
|
+
# • 300+ models from all major providers through a single API
|
|
121
|
+
# • Includes free-tier models (no credit needed for :free variants)
|
|
122
|
+
# Get your key at: https://openrouter.ai/keys
|
|
123
|
+
OPENROUTER_API_KEY=your-openrouter-api-key-here
|
|
124
|
+
|
|
114
125
|
# GitHub Copilot OAuth token — used for:
|
|
115
126
|
# • Copilot-backed chat/coding models (gpt-5.3, gpt-4.1)
|
|
116
127
|
# Set via: neoagent login github-copilot
|
package/docs/configuration.md
CHANGED
|
@@ -40,6 +40,8 @@ At least one key is required unless you only use local Ollama.
|
|
|
40
40
|
| `XAI_BASE_URL` | Optional xAI-compatible base URL override |
|
|
41
41
|
| `GOOGLE_AI_KEY` | Gemini (Google) |
|
|
42
42
|
| `MINIMAX_API_KEY` | MiniMax (including `MiniMax-M2.7`) |
|
|
43
|
+
| `NVIDIA_API_KEY` | NVIDIA NIM (free-tier + paid: Nemotron, Kimi, Llama 4, DeepSeek, etc.) |
|
|
44
|
+
| `OPENROUTER_API_KEY` | OpenRouter — access 300+ models from all providers through one API; free-tier models included |
|
|
43
45
|
| `BRAVE_SEARCH_API_KEY` | Brave Search for the `web_search` tool |
|
|
44
46
|
| `OPENAI_BASE_URL` | Optional OpenAI-compatible base URL override |
|
|
45
47
|
| `ANTHROPIC_BASE_URL` | Optional Anthropic-compatible base URL override |
|
|
@@ -13,6 +13,7 @@ class _ModelPickerOption {
|
|
|
13
13
|
this.color,
|
|
14
14
|
this.icon,
|
|
15
15
|
this.isAuto = false,
|
|
16
|
+
this.priceTier,
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
final String value;
|
|
@@ -22,6 +23,8 @@ class _ModelPickerOption {
|
|
|
22
23
|
final Color? color;
|
|
23
24
|
final IconData? icon;
|
|
24
25
|
final bool isAuto;
|
|
26
|
+
/// 'free' | 'cheap' | 'medium' | 'expensive' | null
|
|
27
|
+
final String? priceTier;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
// ─── Provider helpers ─────────────────────────────────────────────────────────
|
|
@@ -43,6 +46,9 @@ Color _providerPickerColor(String provider) {
|
|
|
43
46
|
if (p.contains('copilot') || p.contains('github')) {
|
|
44
47
|
return const Color(0xFF238636);
|
|
45
48
|
}
|
|
49
|
+
if (p.contains('openrouter')) return const Color(0xFF6366F1);
|
|
50
|
+
if (p.contains('nvidia')) return const Color(0xFF76B900);
|
|
51
|
+
if (p.contains('minimax')) return const Color(0xFF0EA5E9);
|
|
46
52
|
if (p.contains('deepgram')) return const Color(0xFF13D4A0);
|
|
47
53
|
if (p.contains('ollama')) return const Color(0xFF6C8EBF);
|
|
48
54
|
return const Color(0xFF7C8CFF);
|
|
@@ -64,6 +70,9 @@ IconData _providerPickerIcon(String provider) {
|
|
|
64
70
|
}
|
|
65
71
|
if (p.contains('grok') || p.contains('xai')) return Icons.psychology_rounded;
|
|
66
72
|
if (p.contains('copilot') || p.contains('github')) return Icons.code_rounded;
|
|
73
|
+
if (p.contains('openrouter')) return Icons.hub_rounded;
|
|
74
|
+
if (p.contains('nvidia')) return Icons.speed_rounded;
|
|
75
|
+
if (p.contains('minimax')) return Icons.water_rounded;
|
|
67
76
|
if (p.contains('deepgram')) return Icons.hearing_rounded;
|
|
68
77
|
if (p.contains('ollama')) return Icons.device_hub_rounded;
|
|
69
78
|
return Icons.memory_rounded;
|
|
@@ -78,10 +87,15 @@ String _providerPickerLabel(String id) {
|
|
|
78
87
|
'meta': 'Meta',
|
|
79
88
|
'mistral': 'Mistral',
|
|
80
89
|
'grok': 'xAI',
|
|
90
|
+
'grok-oauth': 'xAI (OAuth)',
|
|
81
91
|
'xai': 'xAI',
|
|
82
92
|
'ollama': 'Ollama',
|
|
83
93
|
'github-copilot': 'GitHub Copilot',
|
|
84
|
-
'openai-codex': 'OpenAI',
|
|
94
|
+
'openai-codex': 'OpenAI Codex',
|
|
95
|
+
'claude-code': 'Claude Code',
|
|
96
|
+
'openrouter': 'OpenRouter',
|
|
97
|
+
'nvidia': 'NVIDIA NIM',
|
|
98
|
+
'minimax': 'MiniMax',
|
|
85
99
|
'deepgram': 'Deepgram',
|
|
86
100
|
};
|
|
87
101
|
return labels[id.toLowerCase()] ?? id;
|
|
@@ -113,6 +127,7 @@ List<_ModelPickerOption> _modelPickerOptions(
|
|
|
113
127
|
subtitle: parts.isNotEmpty ? parts.join(' · ') : null,
|
|
114
128
|
color: _providerPickerColor(m.provider),
|
|
115
129
|
icon: _providerPickerIcon(m.provider),
|
|
130
|
+
priceTier: m.priceTier,
|
|
116
131
|
);
|
|
117
132
|
}),
|
|
118
133
|
];
|
|
@@ -500,6 +515,41 @@ class _ModelPickerDialogState extends State<_ModelPickerDialog> {
|
|
|
500
515
|
// Sub-widgets
|
|
501
516
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
502
517
|
|
|
518
|
+
class _PriceTierChip extends StatelessWidget {
|
|
519
|
+
const _PriceTierChip({required this.tier});
|
|
520
|
+
|
|
521
|
+
final String tier;
|
|
522
|
+
|
|
523
|
+
@override
|
|
524
|
+
Widget build(BuildContext context) {
|
|
525
|
+
final (String text, Color color) = switch (tier) {
|
|
526
|
+
'free' => ('FREE', const Color(0xFF22C55E)),
|
|
527
|
+
'cheap' => ('\$', const Color(0xFF4ADE80)),
|
|
528
|
+
'medium' => ('\$\$', const Color(0xFFF59E0B)),
|
|
529
|
+
'expensive' => ('\$\$\$', const Color(0xFFEF4444)),
|
|
530
|
+
_ => ('', Colors.transparent),
|
|
531
|
+
};
|
|
532
|
+
if (text.isEmpty) return const SizedBox.shrink();
|
|
533
|
+
return Container(
|
|
534
|
+
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
|
535
|
+
decoration: BoxDecoration(
|
|
536
|
+
color: color.withValues(alpha: 0.15),
|
|
537
|
+
borderRadius: BorderRadius.circular(5),
|
|
538
|
+
border: Border.all(color: color.withValues(alpha: 0.35), width: 0.8),
|
|
539
|
+
),
|
|
540
|
+
child: Text(
|
|
541
|
+
text,
|
|
542
|
+
style: TextStyle(
|
|
543
|
+
fontSize: 10,
|
|
544
|
+
fontWeight: FontWeight.w700,
|
|
545
|
+
color: color,
|
|
546
|
+
letterSpacing: 0.2,
|
|
547
|
+
),
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
503
553
|
class _PickerGroupHeader extends StatelessWidget {
|
|
504
554
|
const _PickerGroupHeader({required this.label, required this.color});
|
|
505
555
|
|
|
@@ -612,6 +662,10 @@ class _PickerRow extends StatelessWidget {
|
|
|
612
662
|
),
|
|
613
663
|
),
|
|
614
664
|
const SizedBox(width: 8),
|
|
665
|
+
if (option.priceTier != null) ...<Widget>[
|
|
666
|
+
_PriceTierChip(tier: option.priceTier!),
|
|
667
|
+
const SizedBox(width: 6),
|
|
668
|
+
],
|
|
615
669
|
SizedBox(
|
|
616
670
|
width: 20,
|
|
617
671
|
child: selected
|
|
@@ -2015,6 +2015,7 @@ class ModelMeta {
|
|
|
2015
2015
|
this.available = true,
|
|
2016
2016
|
this.providerStatus = '',
|
|
2017
2017
|
this.providerStatusLabel = '',
|
|
2018
|
+
this.priceTier,
|
|
2018
2019
|
});
|
|
2019
2020
|
|
|
2020
2021
|
factory ModelMeta.fromJson(Map<dynamic, dynamic> json) {
|
|
@@ -2026,6 +2027,7 @@ class ModelMeta {
|
|
|
2026
2027
|
available: json['available'] != false,
|
|
2027
2028
|
providerStatus: json['providerStatus']?.toString() ?? '',
|
|
2028
2029
|
providerStatusLabel: json['providerStatusLabel']?.toString() ?? '',
|
|
2030
|
+
priceTier: json['priceTier']?.toString(),
|
|
2029
2031
|
);
|
|
2030
2032
|
}
|
|
2031
2033
|
|
|
@@ -2036,6 +2038,8 @@ class ModelMeta {
|
|
|
2036
2038
|
final bool available;
|
|
2037
2039
|
final String providerStatus;
|
|
2038
2040
|
final String providerStatusLabel;
|
|
2041
|
+
/// Pricing tier: 'free' | 'cheap' | 'medium' | 'expensive' | null (unknown)
|
|
2042
|
+
final String? priceTier;
|
|
2039
2043
|
}
|
|
2040
2044
|
|
|
2041
2045
|
class AiProviderConfig {
|
|
@@ -187,7 +187,6 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
187
187
|
final Map<String, bool> _providerEnabled = <String, bool>{};
|
|
188
188
|
final Map<String, TextEditingController> _providerBaseUrlControllers =
|
|
189
189
|
<String, TextEditingController>{};
|
|
190
|
-
final Set<String> _expandedProviderIds = <String>{};
|
|
191
190
|
|
|
192
191
|
// Inline runtime test state — ephemeral, not stored in controller.
|
|
193
192
|
bool _cliTestRunning = false;
|
|
@@ -868,88 +867,10 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
868
867
|
const _SectionTitle('Models'),
|
|
869
868
|
const SizedBox(height: 10),
|
|
870
869
|
Text(
|
|
871
|
-
'
|
|
870
|
+
'Choose defaults for chat, agents, fallback behavior, and smart routing.',
|
|
872
871
|
style: TextStyle(color: _textSecondary, height: 1.45),
|
|
873
872
|
),
|
|
874
873
|
const SizedBox(height: 16),
|
|
875
|
-
Text(
|
|
876
|
-
'Providers',
|
|
877
|
-
style: TextStyle(
|
|
878
|
-
fontWeight: FontWeight.w700,
|
|
879
|
-
color: _textPrimary,
|
|
880
|
-
),
|
|
881
|
-
),
|
|
882
|
-
const SizedBox(height: 14),
|
|
883
|
-
if (controller.aiProviders.isEmpty)
|
|
884
|
-
Text(
|
|
885
|
-
'Provider metadata is unavailable on this server version.',
|
|
886
|
-
style: TextStyle(color: _textSecondary),
|
|
887
|
-
)
|
|
888
|
-
else
|
|
889
|
-
LayoutBuilder(
|
|
890
|
-
builder: (context, constraints) {
|
|
891
|
-
final compact = constraints.maxWidth < 960;
|
|
892
|
-
final cardWidth = compact
|
|
893
|
-
? constraints.maxWidth
|
|
894
|
-
: (constraints.maxWidth - 16) / 2;
|
|
895
|
-
return Wrap(
|
|
896
|
-
spacing: 16,
|
|
897
|
-
runSpacing: 16,
|
|
898
|
-
children: controller.aiProviders
|
|
899
|
-
.where(
|
|
900
|
-
(provider) =>
|
|
901
|
-
provider.available ||
|
|
902
|
-
_providerEnabled[provider.id] == true ||
|
|
903
|
-
controller
|
|
904
|
-
.aiProviderConfigs[provider.id]
|
|
905
|
-
?.enabled ==
|
|
906
|
-
true,
|
|
907
|
-
)
|
|
908
|
-
.map((provider) {
|
|
909
|
-
return SizedBox(
|
|
910
|
-
width: cardWidth,
|
|
911
|
-
child: _AiProviderCard(
|
|
912
|
-
provider: provider,
|
|
913
|
-
enabled:
|
|
914
|
-
_providerEnabled[provider.id] ??
|
|
915
|
-
controller
|
|
916
|
-
.aiProviderConfigs[provider.id]
|
|
917
|
-
?.enabled ??
|
|
918
|
-
true,
|
|
919
|
-
models: controller.supportedModels
|
|
920
|
-
.where(
|
|
921
|
-
(model) => model.provider == provider.id,
|
|
922
|
-
)
|
|
923
|
-
.toList(),
|
|
924
|
-
baseUrlController:
|
|
925
|
-
_providerBaseUrlControllers[provider.id]!,
|
|
926
|
-
expanded: _expandedProviderIds.contains(
|
|
927
|
-
provider.id,
|
|
928
|
-
),
|
|
929
|
-
onEnabledChanged: (value) {
|
|
930
|
-
setState(() {
|
|
931
|
-
_providerEnabled[provider.id] = value;
|
|
932
|
-
});
|
|
933
|
-
},
|
|
934
|
-
onExpandToggle: () {
|
|
935
|
-
setState(() {
|
|
936
|
-
if (_expandedProviderIds.contains(
|
|
937
|
-
provider.id,
|
|
938
|
-
)) {
|
|
939
|
-
_expandedProviderIds.remove(provider.id);
|
|
940
|
-
} else {
|
|
941
|
-
_expandedProviderIds.add(provider.id);
|
|
942
|
-
}
|
|
943
|
-
});
|
|
944
|
-
},
|
|
945
|
-
),
|
|
946
|
-
);
|
|
947
|
-
})
|
|
948
|
-
.toList(),
|
|
949
|
-
);
|
|
950
|
-
},
|
|
951
|
-
),
|
|
952
|
-
const Divider(height: 32),
|
|
953
874
|
Text(
|
|
954
875
|
'Default Routing',
|
|
955
876
|
style: TextStyle(
|
|
@@ -2205,244 +2126,6 @@ class _SettingsPanelState extends State<SettingsPanel> {
|
|
|
2205
2126
|
}
|
|
2206
2127
|
}
|
|
2207
2128
|
|
|
2208
|
-
class _AiProviderCard extends StatelessWidget {
|
|
2209
|
-
const _AiProviderCard({
|
|
2210
|
-
required this.provider,
|
|
2211
|
-
required this.enabled,
|
|
2212
|
-
required this.expanded,
|
|
2213
|
-
required this.models,
|
|
2214
|
-
required this.baseUrlController,
|
|
2215
|
-
required this.onEnabledChanged,
|
|
2216
|
-
required this.onExpandToggle,
|
|
2217
|
-
});
|
|
2218
|
-
|
|
2219
|
-
final AiProviderMeta provider;
|
|
2220
|
-
final bool enabled;
|
|
2221
|
-
final bool expanded;
|
|
2222
|
-
final List<ModelMeta> models;
|
|
2223
|
-
final TextEditingController baseUrlController;
|
|
2224
|
-
final ValueChanged<bool> onEnabledChanged;
|
|
2225
|
-
final VoidCallback onExpandToggle;
|
|
2226
|
-
|
|
2227
|
-
@override
|
|
2228
|
-
Widget build(BuildContext context) {
|
|
2229
|
-
final availableCount = models.where((model) => model.available).length;
|
|
2230
|
-
final hasAdvancedFields = provider.supportsBaseUrl || models.isNotEmpty;
|
|
2231
|
-
return Container(
|
|
2232
|
-
padding: const EdgeInsets.all(16),
|
|
2233
|
-
decoration: BoxDecoration(
|
|
2234
|
-
color: _bgSecondary,
|
|
2235
|
-
borderRadius: BorderRadius.circular(18),
|
|
2236
|
-
border: Border.all(color: _borderLight),
|
|
2237
|
-
),
|
|
2238
|
-
child: Column(
|
|
2239
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2240
|
-
children: <Widget>[
|
|
2241
|
-
Row(
|
|
2242
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2243
|
-
children: <Widget>[
|
|
2244
|
-
Container(
|
|
2245
|
-
width: 44,
|
|
2246
|
-
height: 44,
|
|
2247
|
-
decoration: BoxDecoration(
|
|
2248
|
-
color: _accentMuted,
|
|
2249
|
-
borderRadius: BorderRadius.circular(14),
|
|
2250
|
-
),
|
|
2251
|
-
child: Icon(provider.icon, color: _accentHover),
|
|
2252
|
-
),
|
|
2253
|
-
const SizedBox(width: 12),
|
|
2254
|
-
Expanded(
|
|
2255
|
-
child: Column(
|
|
2256
|
-
crossAxisAlignment: CrossAxisAlignment.start,
|
|
2257
|
-
children: <Widget>[
|
|
2258
|
-
Text(
|
|
2259
|
-
provider.label,
|
|
2260
|
-
style: TextStyle(
|
|
2261
|
-
fontSize: 16,
|
|
2262
|
-
fontWeight: FontWeight.w700,
|
|
2263
|
-
),
|
|
2264
|
-
),
|
|
2265
|
-
const SizedBox(height: 4),
|
|
2266
|
-
Text(
|
|
2267
|
-
provider.description,
|
|
2268
|
-
maxLines: 2,
|
|
2269
|
-
overflow: TextOverflow.ellipsis,
|
|
2270
|
-
style: TextStyle(color: _textSecondary, height: 1.4),
|
|
2271
|
-
),
|
|
2272
|
-
],
|
|
2273
|
-
),
|
|
2274
|
-
),
|
|
2275
|
-
const SizedBox(width: 8),
|
|
2276
|
-
Column(
|
|
2277
|
-
crossAxisAlignment: CrossAxisAlignment.end,
|
|
2278
|
-
children: <Widget>[
|
|
2279
|
-
_StatusPill(
|
|
2280
|
-
label: enabled ? provider.statusLabel : 'Disabled',
|
|
2281
|
-
color: enabled ? provider.statusColor : _textSecondary,
|
|
2282
|
-
),
|
|
2283
|
-
const SizedBox(height: 8),
|
|
2284
|
-
InkWell(
|
|
2285
|
-
onTap: hasAdvancedFields || models.isNotEmpty
|
|
2286
|
-
? onExpandToggle
|
|
2287
|
-
: null,
|
|
2288
|
-
borderRadius: BorderRadius.circular(999),
|
|
2289
|
-
child: Container(
|
|
2290
|
-
padding: const EdgeInsets.symmetric(
|
|
2291
|
-
horizontal: 10,
|
|
2292
|
-
vertical: 6,
|
|
2293
|
-
),
|
|
2294
|
-
decoration: BoxDecoration(
|
|
2295
|
-
color: _bgCard,
|
|
2296
|
-
borderRadius: BorderRadius.circular(999),
|
|
2297
|
-
border: Border.all(color: _border),
|
|
2298
|
-
),
|
|
2299
|
-
child: Row(
|
|
2300
|
-
mainAxisSize: MainAxisSize.min,
|
|
2301
|
-
children: <Widget>[
|
|
2302
|
-
Text(
|
|
2303
|
-
expanded ? 'Hide' : 'Setup',
|
|
2304
|
-
style: TextStyle(fontSize: 12),
|
|
2305
|
-
),
|
|
2306
|
-
const SizedBox(width: 4),
|
|
2307
|
-
Icon(
|
|
2308
|
-
expanded
|
|
2309
|
-
? Icons.keyboard_arrow_up
|
|
2310
|
-
: Icons.keyboard_arrow_down,
|
|
2311
|
-
size: 16,
|
|
2312
|
-
color: _textSecondary,
|
|
2313
|
-
),
|
|
2314
|
-
],
|
|
2315
|
-
),
|
|
2316
|
-
),
|
|
2317
|
-
),
|
|
2318
|
-
],
|
|
2319
|
-
),
|
|
2320
|
-
],
|
|
2321
|
-
),
|
|
2322
|
-
const SizedBox(height: 12),
|
|
2323
|
-
Wrap(
|
|
2324
|
-
spacing: 8,
|
|
2325
|
-
runSpacing: 8,
|
|
2326
|
-
children: <Widget>[
|
|
2327
|
-
_MetaPill(
|
|
2328
|
-
label: '$availableCount of ${models.length} models ready',
|
|
2329
|
-
icon: Icons.memory_outlined,
|
|
2330
|
-
),
|
|
2331
|
-
if (provider.supportsApiKey && provider.credentialConfigured)
|
|
2332
|
-
const _MetaPill(
|
|
2333
|
-
label: 'Credentials ready',
|
|
2334
|
-
icon: Icons.lock_outline,
|
|
2335
|
-
),
|
|
2336
|
-
if (provider.supportsApiKey && !provider.credentialConfigured)
|
|
2337
|
-
const _MetaPill(
|
|
2338
|
-
label: 'Credentials needed',
|
|
2339
|
-
icon: Icons.admin_panel_settings_outlined,
|
|
2340
|
-
),
|
|
2341
|
-
if (provider.supportsBaseUrl &&
|
|
2342
|
-
baseUrlController.text.trim().isNotEmpty)
|
|
2343
|
-
_MetaPill(
|
|
2344
|
-
label: _friendlyBaseUrlLabel(baseUrlController.text.trim()),
|
|
2345
|
-
icon: Icons.link_outlined,
|
|
2346
|
-
),
|
|
2347
|
-
],
|
|
2348
|
-
),
|
|
2349
|
-
const SizedBox(height: 12),
|
|
2350
|
-
Container(
|
|
2351
|
-
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
2352
|
-
decoration: BoxDecoration(
|
|
2353
|
-
color: _bgCard,
|
|
2354
|
-
borderRadius: BorderRadius.circular(14),
|
|
2355
|
-
border: Border.all(color: _border),
|
|
2356
|
-
),
|
|
2357
|
-
child: Row(
|
|
2358
|
-
children: <Widget>[
|
|
2359
|
-
Expanded(
|
|
2360
|
-
child: Text(
|
|
2361
|
-
provider.availabilityReason,
|
|
2362
|
-
style: TextStyle(color: _textSecondary, height: 1.35),
|
|
2363
|
-
),
|
|
2364
|
-
),
|
|
2365
|
-
const SizedBox(width: 12),
|
|
2366
|
-
Switch(value: enabled, onChanged: onEnabledChanged),
|
|
2367
|
-
],
|
|
2368
|
-
),
|
|
2369
|
-
),
|
|
2370
|
-
if (expanded) ...<Widget>[
|
|
2371
|
-
const SizedBox(height: 14),
|
|
2372
|
-
if (provider.supportsApiKey)
|
|
2373
|
-
Container(
|
|
2374
|
-
width: double.infinity,
|
|
2375
|
-
padding: const EdgeInsets.all(12),
|
|
2376
|
-
margin: const EdgeInsets.only(bottom: 12),
|
|
2377
|
-
decoration: BoxDecoration(
|
|
2378
|
-
color: _bgCard,
|
|
2379
|
-
borderRadius: BorderRadius.circular(14),
|
|
2380
|
-
border: Border.all(color: _border),
|
|
2381
|
-
),
|
|
2382
|
-
child: Text(
|
|
2383
|
-
provider.credentialConfigured
|
|
2384
|
-
? 'Credentials for this provider are already available to the runtime.'
|
|
2385
|
-
: 'Credentials for this provider are managed outside this workspace UI. Finish the server or admin setup, then return here to enable routing.',
|
|
2386
|
-
style: TextStyle(color: _textSecondary, height: 1.35),
|
|
2387
|
-
),
|
|
2388
|
-
),
|
|
2389
|
-
if (provider.supportsBaseUrl) ...<Widget>[
|
|
2390
|
-
TextField(
|
|
2391
|
-
controller: baseUrlController,
|
|
2392
|
-
keyboardType: TextInputType.url,
|
|
2393
|
-
autocorrect: false,
|
|
2394
|
-
decoration: InputDecoration(
|
|
2395
|
-
labelText: provider.id == 'ollama'
|
|
2396
|
-
? 'Server URL'
|
|
2397
|
-
: 'Base URL',
|
|
2398
|
-
helperText: provider.defaultBaseUrl.trim().isEmpty
|
|
2399
|
-
? 'Optional override.'
|
|
2400
|
-
: 'Default: ${provider.defaultBaseUrl}',
|
|
2401
|
-
),
|
|
2402
|
-
),
|
|
2403
|
-
const SizedBox(height: 12),
|
|
2404
|
-
],
|
|
2405
|
-
if (models.isNotEmpty) ...<Widget>[
|
|
2406
|
-
Text('Models', style: TextStyle(fontWeight: FontWeight.w700)),
|
|
2407
|
-
const SizedBox(height: 8),
|
|
2408
|
-
Wrap(
|
|
2409
|
-
spacing: 8,
|
|
2410
|
-
runSpacing: 8,
|
|
2411
|
-
children: models
|
|
2412
|
-
.map(
|
|
2413
|
-
(model) => Container(
|
|
2414
|
-
padding: const EdgeInsets.symmetric(
|
|
2415
|
-
horizontal: 10,
|
|
2416
|
-
vertical: 8,
|
|
2417
|
-
),
|
|
2418
|
-
decoration: BoxDecoration(
|
|
2419
|
-
color: model.available ? _bgCard : _bgPrimary,
|
|
2420
|
-
borderRadius: BorderRadius.circular(999),
|
|
2421
|
-
border: Border.all(
|
|
2422
|
-
color: model.available ? _border : _borderLight,
|
|
2423
|
-
),
|
|
2424
|
-
),
|
|
2425
|
-
child: Text(
|
|
2426
|
-
model.label,
|
|
2427
|
-
style: TextStyle(
|
|
2428
|
-
fontSize: 12,
|
|
2429
|
-
color: model.available
|
|
2430
|
-
? _textPrimary
|
|
2431
|
-
: _textSecondary,
|
|
2432
|
-
),
|
|
2433
|
-
),
|
|
2434
|
-
),
|
|
2435
|
-
)
|
|
2436
|
-
.toList(),
|
|
2437
|
-
),
|
|
2438
|
-
],
|
|
2439
|
-
],
|
|
2440
|
-
],
|
|
2441
|
-
),
|
|
2442
|
-
);
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
2129
|
class _RoutingSelectCard extends StatelessWidget {
|
|
2447
2130
|
const _RoutingSelectCard({
|
|
2448
2131
|
required this.label,
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7955c5e23a99fe6d57d7bfed7573998e
|
|
Binary file
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27d
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1994209727" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|