internal/tui/tui.go (view raw)
1package tui
2
3import (
4 "context"
5 "fmt"
6 "time"
7
8 "llm_aggregator/internal/progress"
9 "llm_aggregator/internal/runtime"
10
11 tea "github.com/charmbracelet/bubbletea"
12)
13
14// TUIProgress implements the progress.Progress interface for TUI
15type TUIProgress struct {
16 program *tea.Program
17 model *Model
18}
19
20// NewTUIProgress creates a new TUIProgress
21func NewTUIProgress() *TUIProgress {
22 model := New()
23 p := tea.NewProgram(model, tea.WithAltScreen())
24 return &TUIProgress{
25 program: p,
26 model: model,
27 }
28}
29
30// Logf logs a message to the TUI
31func (tp *TUIProgress) Logf(format string, args ...interface{}) {
32 tp.program.Send(StepWithCounts(tp.model.currentStep, fmt.Sprintf(format, args...),
33 tp.model.articlesCount, tp.model.processedCount))
34}
35
36// Warningf logs a warning to the TUI
37func (tp *TUIProgress) Warningf(format string, args ...interface{}) {
38 tp.Logf("Warning: "+format, args...)
39}
40
41// Debugf logs a debug message to the TUI
42func (tp *TUIProgress) Debugf(format string, args ...interface{}) {
43 // Debug messages not shown in TUI by default
44}
45
46// SetStage sets the current stage
47func (tp *TUIProgress) SetStage(stage string) {
48 tp.program.Send(Step(tp.model.currentStep, stage))
49}
50
51// SetSubStage sets the sub-stage status
52func (tp *TUIProgress) SetSubStage(status string) {
53 tp.program.Send(Step(tp.model.currentStep, status))
54}
55
56// SetArticleCount sets the article counts
57func (tp *TUIProgress) SetArticleCount(total, processed int) {
58 tp.model.articlesCount = total
59 tp.model.processedCount = processed
60}
61
62// Run runs the TUI
63func (tp *TUIProgress) Run() error {
64 _, err := tp.program.Run()
65 return err
66}
67
68// Context returns a progress.Context for the TUI
69func (tp *TUIProgress) Context() *progress.Context {
70 return progress.NewContext(tp)
71}
72
73// RunWithRuntime runs the TUI with a runtime
74func RunWithRuntime(rt *runtime.Runtime) error {
75 // Create TUI progress
76 tp := NewTUIProgress()
77
78 // Run TUI in goroutine
79 tuiDone := make(chan error)
80 go func() {
81 tuiDone <- tp.Run()
82 }()
83
84 // Run aggregation in another goroutine
85 runDone := make(chan error)
86 go func() {
87 // Create context for cancellation
88 ctx, cancel := context.WithCancel(context.Background())
89 defer cancel()
90
91 // Update TUI status
92 tp.program.Send(Step(1, fmt.Sprintf("Aggregating feeds from: %s", rt.FeedsFile)))
93
94 // TODO: Configure runtime with progress context
95
96 // Execute the runtime
97 err := rt.Execute(ctx)
98 if err != nil {
99 tp.program.Send(Error(err.Error()))
100 runDone <- err
101 return
102 }
103
104 // Update TUI status
105 tp.program.Send(StepWithCounts(5, "Formatting output", len(rt.Articles), len(rt.Articles)))
106
107 // Write output
108 if rt.OutputFile != "" {
109 if err := rt.WriteOutputToFile(); err != nil {
110 tp.program.Send(Error(fmt.Sprintf("Error writing output: %v", err)))
111 runDone <- err
112 return
113 }
114 tp.program.Send(Step(6, fmt.Sprintf("Output written to: %s", rt.OutputFile)))
115 } else {
116 if err := rt.WriteOutputToFile(); err != nil {
117 tp.program.Send(Error(fmt.Sprintf("Error writing output: %v", err)))
118 runDone <- err
119 return
120 }
121 tp.program.Send(Step(6, "Output complete"))
122 }
123
124 // Mark as done
125 tp.program.Send(Step(7, "Processing completed successfully"))
126 time.Sleep(2 * time.Second) // Show success message briefly
127 tp.program.Send(tea.Quit())
128 runDone <- nil
129 }()
130
131 // Wait for either TUI or run to complete
132 select {
133 case err := <-tuiDone:
134 return err
135 case err := <-runDone:
136 if err != nil {
137 return err
138 }
139 // Wait for TUI to quit gracefully
140 select {
141 case err := <-tuiDone:
142 return err
143 case <-time.After(5 * time.Second):
144 return fmt.Errorf("TUI did not exit gracefully")
145 }
146 }
147}