Skip to content

Commit 7a34099

Browse files
Copilotmithun50
andauthored
feat(flutter): add custom API URL field per AI provider
Agent-Logs-Url: https://github.com/mithun50/openclaw-termux/sessions/0cf373fa-8392-4c58-9180-0e7b224d24f2 Co-authored-by: mithun50 <99024517+mithun50@users.noreply.github.com>
1 parent 84bed80 commit 7a34099

3 files changed

Lines changed: 34 additions & 3 deletions

File tree

flutter_app/lib/screens/provider_detail_screen.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ class ProviderDetailScreen extends StatefulWidget {
88
final AiProvider provider;
99
final String? existingApiKey;
1010
final String? existingModel;
11+
final String? existingBaseUrl;
1112

1213
const ProviderDetailScreen({
1314
super.key,
1415
required this.provider,
1516
this.existingApiKey,
1617
this.existingModel,
18+
this.existingBaseUrl,
1719
});
1820

1921
@override
@@ -24,6 +26,7 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
2426
static const _customModelSentinel = '__custom__';
2527

2628
late final TextEditingController _apiKeyController;
29+
late final TextEditingController _baseUrlController;
2730
late final TextEditingController _customModelController;
2831
late String _selectedModel;
2932
bool _isCustomModel = false;
@@ -41,6 +44,9 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
4144
void initState() {
4245
super.initState();
4346
_apiKeyController = TextEditingController(text: widget.existingApiKey ?? '');
47+
_baseUrlController = TextEditingController(
48+
text: widget.existingBaseUrl ?? widget.provider.baseUrl,
49+
);
4450
_customModelController = TextEditingController();
4551

4652
final existing = widget.existingModel ?? widget.provider.defaultModels.first;
@@ -57,6 +63,7 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
5763
@override
5864
void dispose() {
5965
_apiKeyController.dispose();
66+
_baseUrlController.dispose();
6067
_customModelController.dispose();
6168
super.dispose();
6269
}
@@ -69,6 +76,13 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
6976
);
7077
return;
7178
}
79+
final baseUrl = _baseUrlController.text.trim();
80+
if (baseUrl.isEmpty) {
81+
ScaffoldMessenger.of(context).showSnackBar(
82+
const SnackBar(content: Text('API URL cannot be empty')),
83+
);
84+
return;
85+
}
7286
final model = _effectiveModel;
7387
if (model.isEmpty) {
7488
ScaffoldMessenger.of(context).showSnackBar(
@@ -82,6 +96,7 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
8296
await ProviderConfigService.saveProviderConfig(
8397
provider: widget.provider,
8498
apiKey: apiKey,
99+
baseUrl: baseUrl,
85100
model: model,
86101
);
87102
if (mounted) {
@@ -214,6 +229,20 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
214229
),
215230
const SizedBox(height: 24),
216231

232+
// API URL
233+
Text(
234+
'API URL',
235+
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
236+
),
237+
const SizedBox(height: 8),
238+
TextField(
239+
controller: _baseUrlController,
240+
decoration: const InputDecoration(
241+
hintText: 'https://api.example.com/v1',
242+
),
243+
),
244+
const SizedBox(height: 24),
245+
217246
// Model selection
218247
Text(
219248
'Model',

flutter_app/lib/screens/providers_screen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class _ProvidersScreenState extends State<ProvidersScreen> {
4141
builder: (_) => ProviderDetailScreen(
4242
provider: provider,
4343
existingApiKey: providerConfig?['apiKey'] as String?,
44+
existingBaseUrl: providerConfig?['baseUrl'] as String?,
4445
existingModel: _activeModel,
4546
),
4647
),

flutter_app/lib/services/provider_config_service.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,18 @@ class ProviderConfigService {
5353
}
5454
}
5555

56-
/// Save a provider's API key and set its model as the active model.
56+
/// Save a provider's API key/base URL and set its model as the active model.
5757
/// Tries a Node.js one-liner in proot first, then falls back to a direct
5858
/// file write via NativeBridge.writeRootfsFile if proot/DNS is unavailable.
5959
static Future<void> saveProviderConfig({
6060
required AiProvider provider,
6161
required String apiKey,
62+
required String baseUrl,
6263
required String model,
6364
}) async {
6465
final providerIdJson = jsonEncode(provider.id);
6566
final apiKeyJson = jsonEncode(apiKey);
66-
final baseUrlJson = jsonEncode(provider.baseUrl);
67+
final baseUrlJson = jsonEncode(baseUrl);
6768
final modelJson = jsonEncode(model);
6869

6970
// Build the provider object with the model as an object containing `id`,
@@ -100,7 +101,7 @@ fs.writeFileSync(p, JSON.stringify(c, null, 2));
100101
await _saveConfigDirect(
101102
providerId: provider.id,
102103
apiKey: apiKey,
103-
baseUrl: provider.baseUrl,
104+
baseUrl: baseUrl,
104105
model: model,
105106
);
106107
}

0 commit comments

Comments
 (0)