package background import ( "log" "strings" "time" ) // ProactiveDecision represents a decision about whether to send a proactive message. type ProactiveDecision struct { ShouldSend bool `json:"should_send"` Urgency string `json:"urgency"` // low, medium, high Reason string `json:"reason"` } // ProactiveGuard evaluates whether a proactive message should be sent // based on time-of-day, urgency, rate limits, and user state. type ProactiveGuard struct { // Quiet hours: no non-urgent messages during this window QuietHoursStart int // 0-23, default 23 QuietHoursEnd int // 0-23, default 7 // Min gap between proactive messages, by urgency MinGapByUrgency map[string]time.Duration // Max proactive messages per hour MaxMessagesPerHour int // Track recent send times for rate limiting recentSends []time.Time } // DefaultProactiveGuard returns a guard with sensible defaults. func DefaultProactiveGuard() *ProactiveGuard { return &ProactiveGuard{ QuietHoursStart: 23, QuietHoursEnd: 7, MinGapByUrgency: map[string]time.Duration{ "low": 15 * time.Minute, "medium": 5 * time.Minute, "high": 1 * time.Minute, }, MaxMessagesPerHour: 5, } } // IsQuietHour returns true if the given time falls within quiet hours. func (g *ProactiveGuard) IsQuietHour(now time.Time) bool { hour := now.Hour() if g.QuietHoursStart < g.QuietHoursEnd { return hour >= g.QuietHoursStart && hour < g.QuietHoursEnd } // Overnight quiet hours (e.g., 23:00 - 07:00) return hour >= g.QuietHoursStart || hour < g.QuietHoursEnd } // Evaluate checks whether a proactive message should be sent. func (g *ProactiveGuard) Evaluate(now time.Time, lastProactiveTime time.Time, urgency string, userState string) ProactiveDecision { // 1. Quiet hours: only high urgency messages pass if g.IsQuietHour(now) && urgency != "high" { return ProactiveDecision{ ShouldSend: false, Urgency: urgency, Reason: "当前处于安静时段(23:00-07:00),仅紧急消息可推送", } } // 2. User state check: don't disturb if user is resting/busy if userState == "resting" || userState == "busy" || userState == "sleeping" { if urgency != "high" { return ProactiveDecision{ ShouldSend: false, Urgency: urgency, Reason: "开拓者正在休息/忙碌,不打扰", } } } // 3. Rate limit by urgency minGap, ok := g.MinGapByUrgency[urgency] if !ok { minGap = g.MinGapByUrgency["low"] } if !lastProactiveTime.IsZero() && now.Sub(lastProactiveTime) < minGap { return ProactiveDecision{ ShouldSend: false, Urgency: urgency, Reason: "距上次主动消息时间过短(" + minGap.String() + " 最小间隔)", } } // 4. Hourly rate limit g.pruneOldSends(now) if len(g.recentSends) >= g.MaxMessagesPerHour { return ProactiveDecision{ ShouldSend: false, Urgency: urgency, Reason: "本小时主动消息已达上限", } } // 5. Content length validation (caller should also check) return ProactiveDecision{ ShouldSend: true, Urgency: urgency, Reason: "", } } // RecordSend records a proactive message send for rate limiting. func (g *ProactiveGuard) RecordSend(now time.Time) { g.recentSends = append(g.recentSends, now) g.pruneOldSends(now) } // pruneOldSends removes sends older than 1 hour. func (g *ProactiveGuard) pruneOldSends(now time.Time) { cutoff := now.Add(-1 * time.Hour) valid := g.recentSends[:0] for _, t := range g.recentSends { if t.After(cutoff) { valid = append(valid, t) } } g.recentSends = valid } // ExtractUrgencyFromContent tries to infer urgency from the proactive message content. func ExtractUrgencyFromContent(content string) string { lower := strings.ToLower(content) // High urgency indicators highIndicators := []string{"紧急", "立刻", "马上", "危险", "警告", "报警", "异常", "urgent", "alert"} for _, kw := range highIndicators { if strings.Contains(lower, kw) { return "high" } } // Medium urgency indicators mediumIndicators := []string{"建议", "提醒", "注意", "该", "要", "应该", "记得", "别忘了"} for _, kw := range mediumIndicators { if strings.Contains(lower, kw) { return "medium" } } return "low" } // ValidateProactiveMessage performs post-extraction validation on a message. func ValidateProactiveMessage(content string) (valid bool, reason string) { runes := []rune(content) if len(runes) == 0 { return false, "消息为空" } if len(runes) > 500 { return false, "消息过长(>500字符)" } // Check for prohibited patterns (should not tell user they're resting when they're active) prohibited := []string{ "系统检测到", "根据分析", "经检测", "后台监控", } for _, p := range prohibited { if strings.Contains(content, p) { return false, "包含机械语言: " + p } } return true, "" } // DetermineUserState checks conversation history for user state indicators. func DetermineUserState(lastUserMsg string) string { lower := strings.ToLower(lastUserMsg) restIndicators := []string{"睡", "休息", "躺", "困", "累", "晚安", "午安", "小憩"} busyIndicators := []string{"忙", "工作", "开会", "出去", "走了", "拜拜", "再见", "回头", "晚点"} for _, kw := range restIndicators { if strings.Contains(lower, kw) { return "resting" } } for _, kw := range busyIndicators { if strings.Contains(lower, kw) { return "busy" } } return "active" } // logDecision logs the proactive decision for debugging. func logDecision(d ProactiveDecision) { if d.ShouldSend { log.Printf("[主动消息决策] 允许推送 (紧急程度=%s)", d.Urgency) } else { log.Printf("[主动消息决策] 阻止推送 (紧急程度=%s, 原因=%s)", d.Urgency, d.Reason) } }