175 lines
5.3 KiB
Dart
175 lines
5.3 KiB
Dart
import "package:shadcn_flutter/shadcn_flutter.dart" as shad;
|
|
import "package:provider/provider.dart";
|
|
|
|
import "../../constants.dart";
|
|
import "../../providers/session_provider.dart";
|
|
import "../../providers/settings_provider.dart";
|
|
import "../common/panel_layout.dart";
|
|
import "../../../src/local_state.dart";
|
|
|
|
|
|
class ModelsPanel extends shad.StatelessWidget {
|
|
const ModelsPanel({super.key});
|
|
|
|
@override
|
|
shad.Widget build(shad.BuildContext context) {
|
|
final settings = context.watch<SettingsProvider>();
|
|
final currentModel = settings.normalizeModelId(settings.settings.model);
|
|
final advisorModel = settings.settings.advisorModel;
|
|
final effortLevel = settings.settings.effortLevel;
|
|
final advisorEffortLevel = settings.settings.advisorEffortLevel;
|
|
|
|
return shad.SizedBox(
|
|
height: 300,
|
|
child: shad.SingleChildScrollView(
|
|
child: PanelList(
|
|
fields: [
|
|
PanelField(
|
|
section: "Conversation",
|
|
label: const shad.Text("Model"),
|
|
child: _modelSelect(
|
|
context: context,
|
|
value: currentModel,
|
|
onChanged: (model) async {
|
|
await context.read<SettingsProvider>().updateModel(model);
|
|
await context.read<SessionProvider>().updateSessionModel(model);
|
|
},
|
|
),
|
|
),
|
|
|
|
PanelField(
|
|
section: "Conversation",
|
|
label: const shad.Text("Reasoning"),
|
|
child: _effortSelect(
|
|
context: context,
|
|
value: effortLevel,
|
|
onChanged: (level) async {
|
|
await context.read<SettingsProvider>().updateEffortLevel(level ?? "medium");
|
|
},
|
|
),
|
|
),
|
|
|
|
PanelField(
|
|
section: "Advisor",
|
|
label: const shad.Text("Model"),
|
|
child: _modelSelect(
|
|
context: context,
|
|
value: advisorModel ?? "",
|
|
placeholder: "None",
|
|
onChanged: (model) async {
|
|
await context.read<SettingsProvider>().updateAdvisorModel(model);
|
|
},
|
|
),
|
|
),
|
|
|
|
PanelField(
|
|
section: "Advisor",
|
|
label: const shad.Text("Reasoning"),
|
|
child: _effortSelect(
|
|
context: context,
|
|
value: advisorEffortLevel,
|
|
onChanged: (level) async {
|
|
await context.read<SettingsProvider>().updateAdvisorEffortLevel(level);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
Map<String, List<SelectableAiModel>> _groupedModels() {
|
|
final map = <String, List<SelectableAiModel>>{};
|
|
for (final m in selectableAiModels) {
|
|
map.putIfAbsent(m.group, () => []).add(m);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
Iterable<MapEntry<String, List<SelectableAiModel>>> _filteredGroups(String query) sync* {
|
|
for (final entry in _groupedModels().entries) {
|
|
final matched = entry.value
|
|
.where((m) => m.label.toLowerCase().contains(query) || m.id.toLowerCase().contains(query))
|
|
.toList();
|
|
if (matched.isNotEmpty) {
|
|
yield MapEntry(entry.key, matched);
|
|
} else if (entry.key.toLowerCase().contains(query)) {
|
|
yield entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
shad.Widget _modelSelect({
|
|
required shad.BuildContext context,
|
|
required String value,
|
|
String? placeholder,
|
|
required Future<void> Function(String model) onChanged,
|
|
}) {
|
|
final current = value.isEmpty
|
|
? null
|
|
: selectableAiModels.firstWhere(
|
|
(m) => m.id == value,
|
|
orElse: () => SelectableAiModel(group: "", id: value, label: value),
|
|
);
|
|
|
|
return shad.Select<SelectableAiModel>(
|
|
itemBuilder: (ctx, item) => _modelOption(item),
|
|
onChanged: (v) {
|
|
if (v == null) return;
|
|
onChanged(v.id);
|
|
},
|
|
value: current,
|
|
placeholder: current != null
|
|
? _modelOption(current)
|
|
: shad.Text(placeholder ?? "Select...").muted,
|
|
popup: shad.SelectPopup.builder(
|
|
searchPlaceholder: const shad.Text("Search models"),
|
|
builder: (context, searchQuery) {
|
|
final groups = searchQuery == null
|
|
? _groupedModels().entries
|
|
: _filteredGroups(searchQuery.toLowerCase());
|
|
|
|
return shad.SelectItemList(
|
|
children: [
|
|
for (final entry in groups)
|
|
shad.SelectGroup(
|
|
headers: [shad.SelectLabel(child: shad.Text(entry.key).xSmall.muted)],
|
|
children: [
|
|
for (final m in entry.value)
|
|
shad.SelectItemButton(value: m, child: _modelOption(m)),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
shad.Widget _modelOption(SelectableAiModel m) => shad.Text(m.label).xSmall;
|
|
|
|
|
|
shad.Widget _effortSelect({
|
|
required shad.BuildContext context,
|
|
required String? value,
|
|
required Future<void> Function(String? level) onChanged,
|
|
}) {
|
|
return shad.Select<String>(
|
|
itemBuilder: (ctx, item) => shad.Text(item).xSmall,
|
|
onChanged: (v) => onChanged(v),
|
|
value: value,
|
|
placeholder: shad.Text(value ?? "none").xSmall.muted,
|
|
popup: shad.SelectPopup(
|
|
items: shad.SelectItemList(
|
|
children: [
|
|
shad.SelectItemButton(value: null, child: shad.Text("none").xSmall),
|
|
for (final level in supportedEffortLevels)
|
|
shad.SelectItemButton(value: level, child: shad.Text(level).xSmall),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|