// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "strings" "sync" "git.stormux.org/storm/cthulhu/internal/config" ) // speechHistory retains the most recent distinct utterances. The history // window suspends capture so its own feedback never changes the list. type speechHistory struct { mu sync.Mutex items []string limit int paused bool monitor chan string } func newSpeechHistory(limit int) *speechHistory { if limit < config.MinSpeechHistoryLimit || limit > config.MaxSpeechHistoryLimit { limit = config.DefaultSpeechHistoryLimit } return &speechHistory{limit: limit} } func (h *speechHistory) setLimit(limit int) { if limit < config.MinSpeechHistoryLimit || limit > config.MaxSpeechHistoryLimit { return } h.mu.Lock() defer h.mu.Unlock() h.limit = limit if len(h.items) > limit { clear(h.items[limit:]) h.items = h.items[:limit] } } func (h *speechHistory) add(text, role string) { if role == "password text" { return } text = strings.TrimSpace(text) if text == "" { return } h.mu.Lock() defer h.mu.Unlock() if h.monitor != nil { select { case h.monitor <- text: default: } } if h.paused { return } for _, item := range h.items { if item == text { return } } h.items = append(h.items, "") copy(h.items[1:], h.items[:len(h.items)-1]) h.items[0] = text if len(h.items) > h.limit { clear(h.items[h.limit:]) h.items = h.items[:h.limit] } } func (h *speechHistory) snapshot() []string { h.mu.Lock() defer h.mu.Unlock() return append([]string(nil), h.items...) } func (h *speechHistory) remove(text string) bool { h.mu.Lock() defer h.mu.Unlock() for i, item := range h.items { if item == text { h.items = append(h.items[:i], h.items[i+1:]...) return true } } return false } func (h *speechHistory) setPaused(paused bool) { h.mu.Lock() h.paused = paused h.mu.Unlock() } func (h *speechHistory) setMonitor(ch chan string) { h.mu.Lock() h.monitor = ch h.mu.Unlock() }