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(); 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().updateModel(model); await context.read().updateSessionModel(model); }, ), ), PanelField( section: "Conversation", label: const shad.Text("Reasoning"), child: _effortSelect( context: context, value: effortLevel, onChanged: (level) async { await context.read().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().updateAdvisorModel(model); }, ), ), PanelField( section: "Advisor", label: const shad.Text("Reasoning"), child: _effortSelect( context: context, value: advisorEffortLevel, onChanged: (level) async { await context.read().updateAdvisorEffortLevel(level); }, ), ), ], ), ), ); } } Map> _groupedModels() { final map = >{}; for (final m in selectableAiModels) { map.putIfAbsent(m.group, () => []).add(m); } return map; } Iterable>> _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 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( 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 Function(String? level) onChanged, }) { return shad.Select( 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), ], ), ), ); }