cmd/llm_aggregator.go (view raw)
1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/signal"
8 "syscall"
9
10 "llm_aggregator/internal/cli"
11 "llm_aggregator/internal/runtime"
12 "llm_aggregator/internal/tui"
13
14 tea "github.com/charmbracelet/bubbletea"
15)
16
17var (
18 version string
19 buildDate string
20)
21
22func main() {
23 cli.BuildDate = buildDate
24 cli.Version = version
25 // Parse command line arguments
26 args, err := cli.ParseArgs()
27 if err != nil {
28 fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
29 os.Exit(1)
30 }
31
32 // Create runtime configuration
33 rt := runtime.NewRuntime()
34 rt.FeedsFile = args.FeedsFile
35 rt.MaxArticlesPerFeed = args.MaxArticlesPerFeed
36 rt.MaxDaysOld = args.MaxDaysOld
37 rt.MaxTotalArticles = args.MaxTotalArticles
38 rt.IncludeKeywords = cli.ParseKeywords(args.IncludeKeywords)
39 rt.ExcludeKeywords = cli.ParseKeywords(args.ExcludeKeywords)
40 rt.APIKey = args.APIKey
41 if rt.APIKey == "" {
42 rt.APIKey = os.Getenv("DEEPSEEK_API_KEY")
43 }
44 rt.Model = args.Model
45 rt.MaxTokens = args.MaxTokens
46 rt.Temperature = args.Temperature
47 rt.Prompt = args.Prompt
48 rt.SystemPrompt = args.SystemPrompt
49 rt.Output = args.Output
50 rt.OutputFile = args.OutputFile
51 rt.IncludeArticles = args.IncludeArticles
52
53 // Validate API key
54 if rt.APIKey == "" {
55 fmt.Fprintln(os.Stderr, "Error: DeepSeek API key is required. Set via --api-key or DEEPSEEK_API_KEY environment variable.")
56 os.Exit(1)
57 }
58
59 // Run with TUI if requested
60 if args.TUI {
61 runWithTUI(rt)
62 } else {
63 runWithoutTUI(rt, args.Verbose)
64 }
65}
66
67func runWithTUI(rt *runtime.Runtime) {
68 // Create TUI model
69 m := tui.New()
70 p := tea.NewProgram(m, tea.WithAltScreen())
71
72 // Run TUI in goroutine
73 done := make(chan error)
74 go func() {
75 if _, err := p.Run(); err != nil {
76 done <- err
77 return
78 }
79 done <- nil
80 }()
81
82 // Run aggregation in another goroutine
83 go func() {
84 // Create context for cancellation
85 ctx, cancel := context.WithCancel(context.Background())
86 defer cancel()
87
88 // Set up signal handling
89 sigCh := make(chan os.Signal, 1)
90 signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
91
92 // Handle signals
93 go func() {
94 <-sigCh
95 p.Send(tui.Error("Operation cancelled"))
96 cancel()
97 }()
98
99 // Update TUI status
100 p.Send(tui.Step(1, fmt.Sprintf("Aggregating feeds from: %s", rt.FeedsFile)))
101
102 // Execute the runtime
103 err := rt.Execute(ctx)
104 if err != nil {
105 p.Send(tui.Error(err.Error()))
106 return
107 }
108
109 // Update TUI status
110 p.Send(tui.StepWithCounts(5, "Formatting output", len(rt.Articles), len(rt.Articles)))
111
112 // Write output
113 if rt.OutputFile != "" {
114 if err := rt.WriteOutputToFile(); err != nil {
115 p.Send(tui.Error(fmt.Sprintf("Error writing output: %v", err)))
116 return
117 }
118 p.Send(tui.Step(6, fmt.Sprintf("Output written to: %s", rt.OutputFile)))
119 } else {
120 if err := rt.WriteOutput(os.Stdout); err != nil {
121 p.Send(tui.Error(fmt.Sprintf("Error writing output: %v", err)))
122 return
123 }
124 p.Send(tui.Step(6, "Output complete"))
125 }
126
127 // Mark as done
128 p.Send(tui.Step(7, "Processing completed successfully"))
129 }()
130
131 // Wait for TUI to complete
132 if err := <-done; err != nil {
133 fmt.Fprintf(os.Stderr, "TUI error: %v\n", err)
134 os.Exit(1)
135 }
136}
137
138func runWithoutTUI(rt *runtime.Runtime, verbose bool) {
139 if verbose {
140 fmt.Printf("Aggregating feeds from: %s\n", rt.FeedsFile)
141 }
142
143 // Execute the runtime
144 err := rt.Execute(context.Background())
145 if err != nil {
146 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
147 os.Exit(1)
148 }
149
150 if verbose {
151 fmt.Printf("Aggregated %d articles\n", len(rt.Articles))
152 fmt.Printf("Requesting summary from DeepSeek with model: %s\n", rt.Model)
153 }
154
155 // Write output
156 if rt.OutputFile != "" {
157 if err := rt.WriteOutputToFile(); err != nil {
158 fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err)
159 os.Exit(1)
160 }
161 if verbose {
162 fmt.Printf("Output written to: %s\n", rt.OutputFile)
163 }
164 } else {
165 if err := rt.WriteOutput(os.Stdout); err != nil {
166 fmt.Fprintf(os.Stderr, "Error writing output: %v\n", err)
167 os.Exit(1)
168 }
169 }
170
171 if verbose {
172 fmt.Println("Processing completed successfully")
173 }
174}
175