146 lines
4.6 KiB
Dart
146 lines
4.6 KiB
Dart
// PARITY GAP: legacy model picker kept around while the new models panel takes over.
|
|
import "package:flutter/material.dart";
|
|
import "package:provider/provider.dart";
|
|
|
|
import "../../../src/api/openrouter_client.dart";
|
|
import "../../providers/settings_provider.dart";
|
|
|
|
class ModelPicker extends StatefulWidget {
|
|
const ModelPicker();
|
|
|
|
@override
|
|
State<ModelPicker> createState() => _ModelPickerState();
|
|
}
|
|
|
|
class _ModelPickerState extends State<ModelPicker> {
|
|
late Future<List<Map<String, dynamic>>> _modelsFuture;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_modelsFuture = _loadModels();
|
|
}
|
|
|
|
Future<List<Map<String, dynamic>>> _loadModels() async {
|
|
try {
|
|
final apiKey = context.read<SettingsProvider>().settings.openRouterApiKey;
|
|
if (apiKey == null || apiKey.isEmpty) {
|
|
return [
|
|
{"id": "openai/gpt-4o", "name": "GPT-4o"},
|
|
{"id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6"},
|
|
{"id": "google/gemini-2.0-flash-001", "name": "Gemini 2.0 Flash"},
|
|
];
|
|
}
|
|
|
|
final client = await OpenRouterClientFactory.create(apiKey: apiKey);
|
|
final models = await client.listModels();
|
|
client.close();
|
|
return models;
|
|
} catch (e) {
|
|
return [
|
|
{"id": "openai/gpt-4o", "name": "GPT-4o"},
|
|
{"id": "anthropic/claude-sonnet-4.6", "name": "Claude Sonnet 4.6"},
|
|
{"id": "google/gemini-2.0-flash-001", "name": "Gemini 2.0 Flash"},
|
|
];
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Consumer<SettingsProvider>(
|
|
builder: (context, settingsProvider, _) {
|
|
final currentModel = settingsProvider.normalizeModelId(
|
|
settingsProvider.settings.model,
|
|
);
|
|
|
|
return FutureBuilder<List<Map<String, dynamic>>>(
|
|
future: _modelsFuture,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Text("Loading models...");
|
|
}
|
|
|
|
final models = snapshot.data ?? [];
|
|
final selectedModel = models.firstWhere(
|
|
(m) => m["id"] == currentModel,
|
|
orElse: () => {"id": currentModel, "name": currentModel},
|
|
);
|
|
|
|
return GestureDetector(
|
|
onTap: () => _showModelMenu(
|
|
context,
|
|
models,
|
|
currentModel,
|
|
settingsProvider,
|
|
),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: const Color(0xFFE2E8F0)),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text(selectedModel["name"] as String? ?? currentModel),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
void _showModelMenu(
|
|
BuildContext context,
|
|
List<Map<String, dynamic>> models,
|
|
String currentModel,
|
|
SettingsProvider settingsProvider,
|
|
) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => Dialog(
|
|
child: Container(
|
|
constraints: const BoxConstraints(maxHeight: 400),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
children: models.map((model) {
|
|
final modelId = model["id"] as String;
|
|
final modelName = model["name"] as String? ?? modelId;
|
|
final isSelected = modelId == currentModel;
|
|
|
|
return Container(
|
|
color: isSelected
|
|
? const Color(0xFFF1F5F9)
|
|
: Colors.transparent,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
settingsProvider.updateModel(modelId);
|
|
Navigator.pop(ctx);
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Row(
|
|
children: [
|
|
if (isSelected)
|
|
const Padding(
|
|
padding: EdgeInsets.only(right: 8),
|
|
child: Text(
|
|
"✓",
|
|
style: TextStyle(fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
Expanded(child: Text(modelName)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|