| 1 | import 'package:flutter_riverpod/flutter_riverpod.dart'; |
| 2 | import 'package:localmind/core/providers/app_providers.dart'; |
| 3 | import 'package:localmind/features/conversations/providers/conversation_providers.dart' |
| 4 | as conv; |
| 5 | import 'package:localmind/features/personas/providers/personas_providers.dart'; |
| 6 | import 'package:localmind/features/personas/utils/persona_prompt_utils.dart'; |
| 7 | import '../data/models/chat_parameters.dart'; |
| 8 | import 'chat_reasoning_providers.dart'; |
| 9 | import 'model_selection_providers.dart'; |
| 10 | |
| 11 | final chatParamsProvider = Provider<ChatParameters>((ref) { |
| 12 | final settings = ref.watch(settingsProvider); |
| 13 | final activeConv = ref.watch(conv.activeConversationProvider); |
| 14 | final selectedModel = ref.watch(selectedModelProvider); |
| 15 | final reasoningConfig = ref.watch(chatReasoningConfigProvider); |
| 16 | final modelSupportsReasoning = selectedModel?.supportsReasoning ?? false; |
| 17 | // Mandatory-reasoning models can't run with thinking off, so force it on |
| 18 | // regardless of the toggle state. |
| 19 | final reasoningEnabled = modelSupportsReasoning |
| 20 | ? (selectedModel!.reasoningMandatory ? true : reasoningConfig.enabled) |
| 21 | : null; |
| 22 | |
| 23 | double temperature = settings.temperature; |
| 24 | double topP = settings.topP; |
| 25 | int maxTokens = settings.maxTokens; |
| 26 | int contextLength = settings.contextLength; |
| 27 | |
| 28 | if (activeConv?.temperature != null) { |
| 29 | temperature = activeConv!.temperature!; |
| 30 | } |
| 31 | if (activeConv?.topP != null) { |
| 32 | topP = activeConv!.topP!; |
| 33 | } |
| 34 | if (activeConv?.maxTokens != null) { |
| 35 | maxTokens = activeConv!.maxTokens!; |
| 36 | } |
| 37 | if (activeConv?.contextLength != null) { |
| 38 | contextLength = activeConv!.contextLength!; |
| 39 | } |
| 40 | |
| 41 | String? systemPrompt; |
| 42 | if (activeConv?.systemPrompt != null && |
| 43 | activeConv!.systemPrompt!.trim().isNotEmpty) { |
| 44 | systemPrompt = activeConv.systemPrompt; |
| 45 | } else if (activeConv?.personaId != null) { |
| 46 | final personasAsync = ref.read(personasNotifierProvider); |
| 47 | final personas = personasAsync.value ?? []; |
| 48 | final selected = PersonaPromptUtils.resolvePersonas( |
| 49 | activeConv!.personaId, |
| 50 | personas, |
| 51 | ); |
| 52 | if (selected.isNotEmpty) { |
| 53 | systemPrompt = PersonaPromptUtils.combineSystemPrompts(selected); |
| 54 | final persona = selected.first; |
| 55 | if (persona.preferredParams != null) { |
| 56 | final params = persona.preferredParams as Map<String, dynamic>; |
| 57 | if (params['temperature'] != null) { |
| 58 | temperature = (params['temperature'] as num).toDouble(); |
| 59 | } |
| 60 | if (params['topP'] != null) topP = (params['topP'] as num).toDouble(); |
| 61 | if (params['maxTokens'] != null) { |
| 62 | maxTokens = (params['maxTokens'] as num).toInt(); |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // Fallback-only: never stacks with a chat prompt or persona (#81). |
| 69 | final defaultSystemPrompt = settings.defaultSystemPrompt.trim(); |
| 70 | if (systemPrompt == null && defaultSystemPrompt.isNotEmpty) { |
| 71 | systemPrompt = defaultSystemPrompt; |
| 72 | } |
| 73 | |
| 74 | return ChatParameters( |
| 75 | temperature: temperature, |
| 76 | topP: topP, |
| 77 | sendTemperature: settings.sendTemperature, |
| 78 | sendTopP: settings.sendTopP, |
| 79 | maxTokens: maxTokens, |
| 80 | contextLength: contextLength, |
| 81 | systemPrompt: systemPrompt, |
| 82 | reasoningEnabled: reasoningEnabled, |
| 83 | reasoningEffort: resolveEffortForModel( |
| 84 | selectedModel, |
| 85 | reasoningConfig.effort, |
| 86 | ), |
| 87 | reasoningAllowedOptions: selectedModel?.supportedReasoningEfforts, |
| 88 | reasoningDefaultOption: selectedModel?.defaultReasoningEffort, |
| 89 | ); |
| 90 | }); |