add llm processing notification
This commit is contained in:
@@ -91,6 +91,7 @@ type MessageConfig struct {
|
|||||||
type MessagesConfig struct {
|
type MessagesConfig struct {
|
||||||
RecordingStarted MessageConfig `toml:"recording_started"`
|
RecordingStarted MessageConfig `toml:"recording_started"`
|
||||||
Transcribing MessageConfig `toml:"transcribing"`
|
Transcribing MessageConfig `toml:"transcribing"`
|
||||||
|
LLMProcessing MessageConfig `toml:"llm_processing"`
|
||||||
ConfigReloaded MessageConfig `toml:"config_reloaded"`
|
ConfigReloaded MessageConfig `toml:"config_reloaded"`
|
||||||
OperationCancelled MessageConfig `toml:"operation_cancelled"`
|
OperationCancelled MessageConfig `toml:"operation_cancelled"`
|
||||||
RecordingAborted MessageConfig `toml:"recording_aborted"`
|
RecordingAborted MessageConfig `toml:"recording_aborted"`
|
||||||
@@ -721,6 +722,9 @@ keywords = []
|
|||||||
# [notifications.messages.transcribing]
|
# [notifications.messages.transcribing]
|
||||||
# title = "Hyprvoice"
|
# title = "Hyprvoice"
|
||||||
# body = "Recording Ended... Transcribing"
|
# body = "Recording Ended... Transcribing"
|
||||||
|
# [notifications.messages.llm_processing]
|
||||||
|
# title = "Hyprvoice"
|
||||||
|
# body = "Processing..."
|
||||||
# [notifications.messages.config_reloaded]
|
# [notifications.messages.config_reloaded]
|
||||||
# title = "Hyprvoice"
|
# title = "Hyprvoice"
|
||||||
# body = "Config Reloaded"
|
# body = "Config Reloaded"
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ func (d *Daemon) toggle() {
|
|||||||
|
|
||||||
go d.notifier.Send(notify.MsgRecordingStarted)
|
go d.notifier.Send(notify.MsgRecordingStarted)
|
||||||
go d.monitorPipelineErrors(p)
|
go d.monitorPipelineErrors(p)
|
||||||
|
go d.monitorPipelineNotifications(p)
|
||||||
|
|
||||||
case pipeline.Recording:
|
case pipeline.Recording:
|
||||||
d.stopPipeline()
|
d.stopPipeline()
|
||||||
@@ -240,3 +241,15 @@ func (d *Daemon) monitorPipelineErrors(p pipeline.Pipeline) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Daemon) monitorPipelineNotifications(p pipeline.Pipeline) {
|
||||||
|
notifyCh := p.GetNotifyCh()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case mt := <-notifyCh:
|
||||||
|
d.notifier.Send(mt)
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/leonardotrapani/hyprvoice/internal/notify"
|
||||||
"github.com/leonardotrapani/hyprvoice/internal/pipeline"
|
"github.com/leonardotrapani/hyprvoice/internal/pipeline"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -480,3 +481,6 @@ func (m *MockPipeline) GetErrorCh() <-chan pipeline.PipelineError {
|
|||||||
return make(chan pipeline.PipelineError)
|
return make(chan pipeline.PipelineError)
|
||||||
}
|
}
|
||||||
func (m *MockPipeline) GetActionCh() chan<- pipeline.Action { return make(chan pipeline.Action) }
|
func (m *MockPipeline) GetActionCh() chan<- pipeline.Action { return make(chan pipeline.Action) }
|
||||||
|
func (m *MockPipeline) GetNotifyCh() <-chan notify.MessageType {
|
||||||
|
return make(chan notify.MessageType)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type MessageType int
|
|||||||
const (
|
const (
|
||||||
MsgRecordingStarted MessageType = iota
|
MsgRecordingStarted MessageType = iota
|
||||||
MsgTranscribing
|
MsgTranscribing
|
||||||
|
MsgLLMProcessing
|
||||||
MsgConfigReloaded
|
MsgConfigReloaded
|
||||||
MsgOperationCancelled
|
MsgOperationCancelled
|
||||||
MsgRecordingAborted
|
MsgRecordingAborted
|
||||||
@@ -25,6 +26,7 @@ type MessageDef struct {
|
|||||||
var MessageDefs = []MessageDef{
|
var MessageDefs = []MessageDef{
|
||||||
{MsgRecordingStarted, "recording_started", "Hyprvoice", "Recording Started", false},
|
{MsgRecordingStarted, "recording_started", "Hyprvoice", "Recording Started", false},
|
||||||
{MsgTranscribing, "transcribing", "Hyprvoice", "Recording Ended... Transcribing", false},
|
{MsgTranscribing, "transcribing", "Hyprvoice", "Recording Ended... Transcribing", false},
|
||||||
|
{MsgLLMProcessing, "llm_processing", "Hyprvoice", "Processing...", false},
|
||||||
{MsgConfigReloaded, "config_reloaded", "Hyprvoice", "Config Reloaded", false},
|
{MsgConfigReloaded, "config_reloaded", "Hyprvoice", "Config Reloaded", false},
|
||||||
{MsgOperationCancelled, "operation_cancelled", "Hyprvoice", "Operation Cancelled", false},
|
{MsgOperationCancelled, "operation_cancelled", "Hyprvoice", "Operation Cancelled", false},
|
||||||
{MsgRecordingAborted, "recording_aborted", "", "Recording Aborted", true},
|
{MsgRecordingAborted, "recording_aborted", "", "Recording Aborted", true},
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ func TestNotifierInterface(t *testing.T) {
|
|||||||
|
|
||||||
func TestMessageDefs(t *testing.T) {
|
func TestMessageDefs(t *testing.T) {
|
||||||
// Verify MessageDefs contains expected entries
|
// Verify MessageDefs contains expected entries
|
||||||
if len(MessageDefs) != 6 {
|
if len(MessageDefs) != 7 {
|
||||||
t.Errorf("Expected 6 MessageDefs, got %d", len(MessageDefs))
|
t.Errorf("Expected 7 MessageDefs, got %d", len(MessageDefs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify each has required fields
|
// Verify each has required fields
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/leonardotrapani/hyprvoice/internal/config"
|
"github.com/leonardotrapani/hyprvoice/internal/config"
|
||||||
"github.com/leonardotrapani/hyprvoice/internal/injection"
|
"github.com/leonardotrapani/hyprvoice/internal/injection"
|
||||||
"github.com/leonardotrapani/hyprvoice/internal/llm"
|
"github.com/leonardotrapani/hyprvoice/internal/llm"
|
||||||
|
"github.com/leonardotrapani/hyprvoice/internal/notify"
|
||||||
"github.com/leonardotrapani/hyprvoice/internal/recording"
|
"github.com/leonardotrapani/hyprvoice/internal/recording"
|
||||||
"github.com/leonardotrapani/hyprvoice/internal/transcriber"
|
"github.com/leonardotrapani/hyprvoice/internal/transcriber"
|
||||||
)
|
)
|
||||||
@@ -41,12 +42,14 @@ type Pipeline interface {
|
|||||||
Status() Status
|
Status() Status
|
||||||
GetActionCh() chan<- Action
|
GetActionCh() chan<- Action
|
||||||
GetErrorCh() <-chan PipelineError
|
GetErrorCh() <-chan PipelineError
|
||||||
|
GetNotifyCh() <-chan notify.MessageType
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeline struct {
|
type pipeline struct {
|
||||||
status Status
|
status Status
|
||||||
actionCh chan Action
|
actionCh chan Action
|
||||||
errorCh chan PipelineError
|
errorCh chan PipelineError
|
||||||
|
notifyCh chan notify.MessageType
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@@ -61,6 +64,7 @@ func New(cfg *config.Config) Pipeline {
|
|||||||
return &pipeline{
|
return &pipeline{
|
||||||
actionCh: make(chan Action, 1),
|
actionCh: make(chan Action, 1),
|
||||||
errorCh: make(chan PipelineError, 10),
|
errorCh: make(chan PipelineError, 10),
|
||||||
|
notifyCh: make(chan notify.MessageType, 10),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,6 +193,12 @@ func (p *pipeline) GetErrorCh() <-chan PipelineError {
|
|||||||
return p.errorCh
|
return p.errorCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) GetNotifyCh() <-chan notify.MessageType {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
return p.notifyCh
|
||||||
|
}
|
||||||
|
|
||||||
func (p *pipeline) sendError(title, message string, err error) {
|
func (p *pipeline) sendError(title, message string, err error) {
|
||||||
pipelineErr := PipelineError{
|
pipelineErr := PipelineError{
|
||||||
Title: title,
|
Title: title,
|
||||||
@@ -203,6 +213,14 @@ func (p *pipeline) sendError(title, message string, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pipeline) sendNotify(mt notify.MessageType) {
|
||||||
|
select {
|
||||||
|
case p.notifyCh <- mt:
|
||||||
|
default:
|
||||||
|
log.Printf("Pipeline: Notify channel full, dropping notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *pipeline) handleInjectAction(ctx context.Context, recorder *recording.Recorder, t transcriber.Transcriber) {
|
func (p *pipeline) handleInjectAction(ctx context.Context, recorder *recording.Recorder, t transcriber.Transcriber) {
|
||||||
status := p.Status()
|
status := p.Status()
|
||||||
|
|
||||||
@@ -232,6 +250,7 @@ func (p *pipeline) handleInjectAction(ctx context.Context, recorder *recording.R
|
|||||||
textToInject := transcriptionText
|
textToInject := transcriptionText
|
||||||
if p.config.IsLLMEnabled() {
|
if p.config.IsLLMEnabled() {
|
||||||
p.setStatus(Processing)
|
p.setStatus(Processing)
|
||||||
|
p.sendNotify(notify.MsgLLMProcessing)
|
||||||
log.Printf("Pipeline: LLM post-processing enabled, processing text")
|
log.Printf("Pipeline: LLM post-processing enabled, processing text")
|
||||||
|
|
||||||
llmCfg := p.config.ToLLMConfig()
|
llmCfg := p.config.ToLLMConfig()
|
||||||
|
|||||||
@@ -189,3 +189,19 @@ Key decisions:
|
|||||||
- Providers section at top for visibility
|
- Providers section at top for visibility
|
||||||
- Keywords before any table definitions (TOML syntax requirement)
|
- Keywords before any table definitions (TOML syntax requirement)
|
||||||
- Concise comments, full reference at bottom
|
- Concise comments, full reference at bottom
|
||||||
|
|
||||||
|
## Task 11: Add LLM processing notification - COMPLETE
|
||||||
|
|
||||||
|
Added notification when LLM post-processing starts:
|
||||||
|
- Added `MsgLLMProcessing` to `notify/message.go` (default: "Hyprvoice", "Processing...")
|
||||||
|
- Added `LLMProcessing` field to `MessagesConfig` in config.go (toml: `llm_processing`)
|
||||||
|
- Added notification channel to pipeline (`GetNotifyCh()` method)
|
||||||
|
- Pipeline sends `MsgLLMProcessing` when entering Processing status
|
||||||
|
- Daemon monitors `notifyCh` via `monitorPipelineNotifications` goroutine
|
||||||
|
- Updated default config template with `llm_processing` message example
|
||||||
|
- Fixed tests: MockPipeline implements `GetNotifyCh`, notify test expects 7 MessageDefs
|
||||||
|
|
||||||
|
Key decisions:
|
||||||
|
- Notification channel approach (vs direct notifier access) keeps pipeline decoupled
|
||||||
|
- Notification sent at same time status changes to Processing
|
||||||
|
- Configurable like all other notifications via `[notifications.messages.llm_processing]`
|
||||||
|
|||||||
+1
-1
@@ -216,7 +216,7 @@
|
|||||||
"Configurable in config",
|
"Configurable in config",
|
||||||
"Typecheck passes"
|
"Typecheck passes"
|
||||||
],
|
],
|
||||||
"passes": false
|
"passes": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Update README documentation",
|
"title": "Update README documentation",
|
||||||
|
|||||||
Reference in New Issue
Block a user