@@ -333,9 +333,16 @@ public partial class MainWindow : Window
333333 private int _remindersShownToday = 0 ;
334334 private DateTime _reminderCounterDate = DateTime . Today ;
335335 private readonly Dictionary < string , TaskInteractionState > _taskInteractionStates = new Dictionary < string , TaskInteractionState > ( ) ;
336- private const int DailyStuckNudgeLimit = 2 ;
336+ private const int DefaultDailyStuckNudgeLimit = 2 ;
337337 private static readonly TimeSpan StuckNudgeCooldown = TimeSpan . FromHours ( 2 ) ;
338- private static readonly TimeSpan StuckNoProgressThreshold = TimeSpan . FromMinutes ( 90 ) ;
338+ private static readonly TimeSpan DefaultStuckNoProgressThreshold = TimeSpan . FromMinutes ( 90 ) ;
339+ private static readonly TimeSpan MinStuckNoProgressThreshold = TimeSpan . FromMinutes ( 60 ) ;
340+ private static readonly TimeSpan MaxStuckNoProgressThreshold = TimeSpan . FromMinutes ( 180 ) ;
341+ private const int MinDailyStuckNudgeLimit = 1 ;
342+ private const int MaxDailyStuckNudgeLimit = 3 ;
343+ private TimeSpan _adaptiveStuckNoProgressThreshold = DefaultStuckNoProgressThreshold ;
344+ private int _adaptiveDailyStuckNudgeLimit = DefaultDailyStuckNudgeLimit ;
345+ private DateTime _lastAdaptiveTuneAt = DateTime . MinValue ;
339346 private int _stuckNudgesShownToday = 0 ;
340347 private DateTime _stuckNudgeCounterDate = DateTime . Today ;
341348 private DateTime _lastStuckNudgeAt = DateTime . MinValue ;
@@ -585,6 +592,7 @@ public MainWindow()
585592
586593 _llmService = LlmService . Create ( ) ;
587594 _userProfileManager = new UserProfileManager ( ) ;
595+ UpdateAdaptiveNudgeParameters ( force : true ) ;
588596
589597 var normalizedPosition = NormalizeWindowPosition (
590598 ( double ) Properties . Settings . Default . Left ,
@@ -979,6 +987,9 @@ private class TaskInteractionState
979987 public int SnoozeCount { get ; set ; } = 0 ;
980988 public int DismissCount { get ; set ; } = 0 ;
981989 public DateTime ? LastNudgedAt { get ; set ; } = null ;
990+ public string LastSuggestedActionId { get ; set ; } = null ;
991+ public string PendingSuggestedActionId { get ; set ; } = null ;
992+ public DateTime ? PendingSuggestedAt { get ; set ; } = null ;
982993 }
983994
984995 private void StartPeriodicTaskReminderChecks ( )
@@ -991,10 +1002,52 @@ private void StartPeriodicTaskReminderChecks()
9911002
9921003 private async void TaskReminderTimer_Tick ( object sender , EventArgs e )
9931004 {
1005+ UpdateAdaptiveNudgeParameters ( ) ;
9941006 await CheckForStaleTasksAndRemind ( ) ;
9951007 CheckForPotentialStuckTasks ( ) ;
9961008 }
9971009
1010+ private void UpdateAdaptiveNudgeParameters ( bool force = false )
1011+ {
1012+ DateTime now = DateTime . Now ;
1013+ if ( ! force && ( now - _lastAdaptiveTuneAt ) < TimeSpan . FromHours ( 6 ) )
1014+ {
1015+ return ;
1016+ }
1017+
1018+ try
1019+ {
1020+ var recommendation = _userProfileManager ? . GetAdaptiveNudgeRecommendation ( 7 ) ;
1021+ if ( recommendation == null )
1022+ {
1023+ return ;
1024+ }
1025+
1026+ _adaptiveStuckNoProgressThreshold = ClampThreshold ( TimeSpan . FromMinutes ( recommendation . RecommendedStuckThresholdMinutes ) ) ;
1027+ _adaptiveDailyStuckNudgeLimit = Math . Max ( MinDailyStuckNudgeLimit , Math . Min ( MaxDailyStuckNudgeLimit , recommendation . RecommendedDailyNudgeLimit ) ) ;
1028+ _lastAdaptiveTuneAt = now ;
1029+ }
1030+ catch ( Exception ex )
1031+ {
1032+ Console . WriteLine ( $ "Adaptive tuning failed: { ex . Message } ") ;
1033+ }
1034+ }
1035+
1036+ private static TimeSpan ClampThreshold ( TimeSpan threshold )
1037+ {
1038+ if ( threshold < MinStuckNoProgressThreshold )
1039+ {
1040+ return MinStuckNoProgressThreshold ;
1041+ }
1042+
1043+ if ( threshold > MaxStuckNoProgressThreshold )
1044+ {
1045+ return MaxStuckNoProgressThreshold ;
1046+ }
1047+
1048+ return threshold ;
1049+ }
1050+
9981051 private void CheckForPotentialStuckTasks ( )
9991052 {
10001053 DateTime now = DateTime . Now ;
@@ -1004,7 +1057,7 @@ private void CheckForPotentialStuckTasks()
10041057 _stuckNudgesShownToday = 0 ;
10051058 }
10061059
1007- if ( _stuckNudgesShownToday >= DailyStuckNudgeLimit || ( now - _lastStuckNudgeAt ) < StuckNudgeCooldown )
1060+ if ( _stuckNudgesShownToday >= _adaptiveDailyStuckNudgeLimit || ( now - _lastStuckNudgeAt ) < StuckNudgeCooldown )
10081061 {
10091062 return ;
10101063 }
@@ -1041,7 +1094,7 @@ private void CheckForPotentialStuckTasks()
10411094 continue ;
10421095 }
10431096
1044- if ( noProgressDuration >= StuckNoProgressThreshold && ( hasHighFriction || hasAvoidancePattern ) )
1097+ if ( noProgressDuration >= _adaptiveStuckNoProgressThreshold && ( hasHighFriction || hasAvoidancePattern ) )
10451098 {
10461099 if ( candidate == null || noProgressDuration > candidateNoProgress )
10471100 {
@@ -1058,15 +1111,20 @@ private void CheckForPotentialStuckTasks()
10581111 return ;
10591112 }
10601113
1061- ShowPassiveStuckSuggestion ( candidate , candidateNoProgress ) ;
1114+ ShowPassiveStuckSuggestion ( candidate , candidateNoProgress , candidateState ) ;
10621115 candidateState . LastNudgedAt = now ;
10631116 _stuckNudgesShownToday ++ ;
10641117 _lastStuckNudgeAt = now ;
10651118 }
10661119
1067- private void ShowPassiveStuckSuggestion ( ItemGrid task , TimeSpan noProgressDuration )
1120+ private void ShowPassiveStuckSuggestion ( ItemGrid task , TimeSpan noProgressDuration , TaskInteractionState state )
10681121 {
1069- string nextStep = BuildStuckNextStep ( task , noProgressDuration ) ;
1122+ var suggestion = BuildStuckNextStep ( task , noProgressDuration , state ) ;
1123+ string nextStep = suggestion . Text ;
1124+ state . LastSuggestedActionId = suggestion . Id ;
1125+ state . PendingSuggestedActionId = suggestion . Id ;
1126+ state . PendingSuggestedAt = DateTime . Now ;
1127+ _userProfileManager ? . RecordSuggestionShown ( suggestion . Id ) ;
10701128
10711129 var notification = new System . Windows . Forms . NotifyIcon
10721130 {
@@ -1084,28 +1142,21 @@ private void ShowPassiveStuckSuggestion(ItemGrid task, TimeSpan noProgressDurati
10841142 } ) ;
10851143 }
10861144
1087- private static string BuildStuckNextStep ( ItemGrid task , TimeSpan noProgressDuration )
1145+ private UserProfileManager . StuckActionSuggestion BuildStuckNextStep ( ItemGrid task , TimeSpan noProgressDuration , TaskInteractionState state )
10881146 {
1089- int hours = Math . Max ( 1 , ( int ) Math . Round ( noProgressDuration . TotalHours ) ) ;
1090- bool highImportance = string . Equals ( task . Importance , "High" , StringComparison . OrdinalIgnoreCase ) ;
1091- bool highUrgency = string . Equals ( task . Urgency , "High" , StringComparison . OrdinalIgnoreCase ) ;
1092-
1093- if ( highImportance && highUrgency )
1094- {
1095- return $ "已卡住约 { hours } 小时,先做 10 分钟最小动作,完成后再扩展。";
1096- }
1097-
1098- if ( highImportance && ! highUrgency )
1147+ var suggestions = _userProfileManager ? . GetRankedStuckSuggestions ( task , noProgressDuration , state ? . LastSuggestedActionId ) ;
1148+ if ( suggestions != null && suggestions . Count > 0 )
10991149 {
1100- return $ "已卡住约 { hours } 小时,拆成一个 20 分钟子任务并安排到今天。" ;
1150+ return suggestions [ 0 ] ;
11011151 }
11021152
1103- if ( ! highImportance && highUrgency )
1153+ int hours = Math . Max ( 1 , ( int ) Math . Round ( noProgressDuration . TotalHours ) ) ;
1154+ return new UserProfileManager . StuckActionSuggestion
11041155 {
1105- return $ "已卡住约 { hours } 小时,建议先确认是否委托或降低优先级。" ;
1106- }
1107-
1108- return $ "已卡住约 { hours } 小时,建议先暂停此任务,转到更关键事项。" ;
1156+ Id = "fallback_min_step" ,
1157+ Text = $ "已卡住约 { hours } 小时,先做一个最小下一步动作。" ,
1158+ Score = 0
1159+ } ;
11091160 }
11101161
11111162 private void TrackTaskInteraction ( ItemGrid task , string interactionType )
@@ -1143,11 +1194,14 @@ private void TrackTaskInteraction(ItemGrid task, string interactionType)
11431194 break ;
11441195 case "snooze" :
11451196 state . SnoozeCount ++ ;
1197+ TryResolvePendingSuggestionFeedback ( state , now , "deferred" ) ;
11461198 break ;
11471199 case "dismiss" :
11481200 state . DismissCount ++ ;
1201+ TryResolvePendingSuggestionFeedback ( state , now , "rejected" ) ;
11491202 break ;
11501203 case "progress" :
1204+ TryResolvePendingSuggestionFeedback ( state , now , "accepted" ) ;
11511205 state . EditCount = 0 ;
11521206 state . ReorderCount = 0 ;
11531207 state . MoveCount = 0 ;
@@ -1158,6 +1212,25 @@ private void TrackTaskInteraction(ItemGrid task, string interactionType)
11581212 }
11591213 }
11601214
1215+ private void TryResolvePendingSuggestionFeedback ( TaskInteractionState state , DateTime now , string feedbackType )
1216+ {
1217+ if ( state == null || string . IsNullOrWhiteSpace ( state . PendingSuggestedActionId ) || ! state . PendingSuggestedAt . HasValue )
1218+ {
1219+ return ;
1220+ }
1221+
1222+ if ( ( now - state . PendingSuggestedAt . Value ) > TimeSpan . FromMinutes ( 30 ) )
1223+ {
1224+ state . PendingSuggestedActionId = null ;
1225+ state . PendingSuggestedAt = null ;
1226+ return ;
1227+ }
1228+
1229+ _userProfileManager ? . RecordSuggestionFeedback ( state . PendingSuggestedActionId , feedbackType ) ;
1230+ state . PendingSuggestedActionId = null ;
1231+ state . PendingSuggestedAt = null ;
1232+ }
1233+
11611234 private static void ResetInteractionWindowIfNeeded ( TaskInteractionState state , DateTime now )
11621235 {
11631236 if ( ( now - state . WindowStart ) <= TimeSpan . FromHours ( 2 ) )
0 commit comments