Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit d39d52d

Browse files
committed
finish logs page
1 parent 0d8d324 commit d39d52d

File tree

22 files changed

+564
-250
lines changed

22 files changed

+564
-250
lines changed

cmd/root.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,6 @@ func setupSubscriptions(app *app.App) (chan tea.Msg, func()) {
107107
wg.Done()
108108
}()
109109
}
110-
{
111-
sub := app.Status.Subscribe(ctx)
112-
wg.Add(1)
113-
go func() {
114-
for ev := range sub {
115-
ch <- ev
116-
}
117-
wg.Done()
118-
}()
119-
}
120110
return ch, func() {
121111
cancel()
122112
wg.Wait()

internal/app/services.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ import (
1111
"github.com/kujtimiihoxha/termai/internal/lsp/watcher"
1212
"github.com/kujtimiihoxha/termai/internal/message"
1313
"github.com/kujtimiihoxha/termai/internal/permission"
14-
"github.com/kujtimiihoxha/termai/internal/pubsub"
1514
"github.com/kujtimiihoxha/termai/internal/session"
16-
"github.com/kujtimiihoxha/termai/internal/tui/util"
1715
)
1816

1917
type App struct {
@@ -27,16 +25,14 @@ type App struct {
2725

2826
Logger logging.Interface
2927

30-
Status *pubsub.Broker[util.InfoMsg]
3128
ceanups []func()
3229
}
3330

3431
func New(ctx context.Context, conn *sql.DB) *App {
3532
cfg := config.Get()
3633
q := db.New(conn)
37-
log := logging.NewLogger(logging.Options{
38-
Level: cfg.Log.Level,
39-
})
34+
log := logging.Get()
35+
log.SetLevel(cfg.Log.Level)
4036
sessions := session.NewService(ctx, q)
4137
messages := message.NewService(ctx, q)
4238

@@ -46,7 +42,6 @@ func New(ctx context.Context, conn *sql.DB) *App {
4642
Messages: messages,
4743
Permissions: permission.NewPermissionService(),
4844
Logger: log,
49-
Status: pubsub.NewBroker[util.InfoMsg](),
5045
LSPClients: make(map[string]*lsp.Client),
5146
}
5247

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ type Config struct {
6565
LSP map[string]LSPConfig `json:"lsp,omitempty"`
6666

6767
Model *Model `json:"model,omitempty"`
68+
69+
Debug bool `json:"debug,omitempty"`
6870
}
6971

7072
var cfg *Config
@@ -90,8 +92,10 @@ func Load(debug bool) error {
9092
// Add defaults
9193
viper.SetDefault("data.directory", defaultDataDirectory)
9294
if debug {
95+
viper.SetDefault("debug", true)
9396
viper.Set("log.level", "debug")
9497
} else {
98+
viper.SetDefault("debug", false)
9599
viper.SetDefault("log.level", defaultLogLevel)
96100
}
97101

internal/llm/agent/agent.go

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"log"
87
"strings"
98
"sync"
109

@@ -15,8 +14,6 @@ import (
1514
"github.com/kujtimiihoxha/termai/internal/llm/provider"
1615
"github.com/kujtimiihoxha/termai/internal/llm/tools"
1716
"github.com/kujtimiihoxha/termai/internal/message"
18-
"github.com/kujtimiihoxha/termai/internal/pubsub"
19-
"github.com/kujtimiihoxha/termai/internal/tui/util"
2017
)
2118

2219
type Agent interface {
@@ -94,24 +91,13 @@ func (c *agent) processEvent(
9491
assistantMsg.AppendContent(event.Content)
9592
return c.Messages.Update(*assistantMsg)
9693
case provider.EventError:
97-
// TODO: remove when realease
98-
log.Println("error", event.Error)
99-
c.App.Status.Publish(pubsub.UpdatedEvent, util.InfoMsg{
100-
Type: util.InfoTypeError,
101-
Msg: event.Error.Error(),
102-
})
94+
c.App.Logger.PersistError(event.Error.Error())
10395
return event.Error
10496
case provider.EventWarning:
105-
c.App.Status.Publish(pubsub.UpdatedEvent, util.InfoMsg{
106-
Type: util.InfoTypeWarn,
107-
Msg: event.Info,
108-
})
97+
c.App.Logger.PersistWarn(event.Info)
10998
return nil
11099
case provider.EventInfo:
111-
c.App.Status.Publish(pubsub.UpdatedEvent, util.InfoMsg{
112-
Type: util.InfoTypeInfo,
113-
Msg: event.Info,
114-
})
100+
c.App.Logger.PersistInfo(event.Info)
115101
case provider.EventComplete:
116102
assistantMsg.SetToolCalls(event.Response.ToolCalls)
117103
assistantMsg.AddFinish(event.Response.FinishReason)

internal/llm/agent/mcp-tools.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"log"
87

98
"github.com/kujtimiihoxha/termai/internal/config"
109
"github.com/kujtimiihoxha/termai/internal/llm/tools"
10+
"github.com/kujtimiihoxha/termai/internal/logging"
1111
"github.com/kujtimiihoxha/termai/internal/permission"
1212
"github.com/kujtimiihoxha/termai/internal/version"
1313

@@ -22,6 +22,8 @@ type mcpTool struct {
2222
permissions permission.Service
2323
}
2424

25+
var logger = logging.Get()
26+
2527
type MCPClient interface {
2628
Initialize(
2729
ctx context.Context,
@@ -141,13 +143,13 @@ func getTools(ctx context.Context, name string, m config.MCPServer, permissions
141143

142144
_, err := c.Initialize(ctx, initRequest)
143145
if err != nil {
144-
log.Printf("error initializing mcp client: %s", err)
146+
logger.Error("error initializing mcp client", "error", err)
145147
return stdioTools
146148
}
147149
toolsRequest := mcp.ListToolsRequest{}
148150
tools, err := c.ListTools(ctx, toolsRequest)
149151
if err != nil {
150-
log.Printf("error listing tools: %s", err)
152+
logger.Error("error listing tools", "error", err)
151153
return stdioTools
152154
}
153155
for _, t := range tools.Tools {
@@ -170,7 +172,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
170172
m.Args...,
171173
)
172174
if err != nil {
173-
log.Printf("error creating mcp client: %s", err)
175+
logger.Error("error creating mcp client", "error", err)
174176
continue
175177
}
176178

@@ -181,7 +183,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
181183
client.WithHeaders(m.Headers),
182184
)
183185
if err != nil {
184-
log.Printf("error creating mcp client: %s", err)
186+
logger.Error("error creating mcp client", "error", err)
185187
continue
186188
}
187189
mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)

internal/llm/provider/gemini.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import (
44
"context"
55
"encoding/json"
66
"errors"
7-
"log"
87

98
"github.com/google/generative-ai-go/genai"
109
"github.com/google/uuid"
1110
"github.com/kujtimiihoxha/termai/internal/llm/models"
1211
"github.com/kujtimiihoxha/termai/internal/llm/tools"
1312
"github.com/kujtimiihoxha/termai/internal/message"
14-
"google.golang.org/api/googleapi"
1513
"google.golang.org/api/iterator"
1614
"google.golang.org/api/option"
1715
)
@@ -242,10 +240,6 @@ func (p *geminiProvider) StreamResponse(ctx context.Context, messages []message.
242240
break
243241
}
244242
if err != nil {
245-
var apiErr *googleapi.Error
246-
if errors.As(err, &apiErr) {
247-
log.Printf("%s", apiErr.Body)
248-
}
249243
eventChan <- ProviderEvent{
250244
Type: EventError,
251245
Error: err,

internal/logging/logger.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import (
1212

1313
const DefaultLevel = "info"
1414

15+
const (
16+
persistKeyArg = "$persist"
17+
PersistTimeArg = "$persist_time"
18+
)
19+
1520
var levels = map[string]slog.Level{
1621
"debug": slog.LevelDebug,
1722
DefaultLevel: slog.LevelInfo,
@@ -36,16 +41,16 @@ func ValidLevels() []string {
3641
return keys
3742
}
3843

39-
func NewLogger(opts Options) *Logger {
44+
func NewLogger(opts Options) Interface {
4045
logger := &Logger{}
41-
broker := pubsub.NewBroker[Message]()
46+
broker := pubsub.NewBroker[LogMessage]()
4247
writer := &writer{
43-
messages: []Message{},
48+
messages: []LogMessage{},
4449
Broker: broker,
4550
}
4651

4752
handler := slog.NewTextHandler(
48-
io.MultiWriter(append(opts.AdditionalWriters, writer)...),
53+
io.MultiWriter(writer),
4954
&slog.HandlerOptions{
5055
Level: slog.Level(levels[opts.Level]),
5156
},
@@ -57,15 +62,51 @@ func NewLogger(opts Options) *Logger {
5762
}
5863

5964
type Options struct {
60-
Level string
61-
AdditionalWriters []io.Writer
65+
Level string
6266
}
6367

6468
type Logger struct {
6569
logger *slog.Logger
6670
writer *writer
6771
}
6872

73+
func (l *Logger) SetLevel(level string) {
74+
if _, ok := levels[level]; !ok {
75+
level = DefaultLevel
76+
}
77+
handler := slog.NewTextHandler(
78+
io.MultiWriter(l.writer),
79+
&slog.HandlerOptions{
80+
Level: levels[level],
81+
},
82+
)
83+
l.logger = slog.New(handler)
84+
}
85+
86+
// PersistDebug implements Interface.
87+
func (l *Logger) PersistDebug(msg string, args ...any) {
88+
args = append(args, persistKeyArg, true)
89+
l.Debug(msg, args...)
90+
}
91+
92+
// PersistError implements Interface.
93+
func (l *Logger) PersistError(msg string, args ...any) {
94+
args = append(args, persistKeyArg, true)
95+
l.Error(msg, args...)
96+
}
97+
98+
// PersistInfo implements Interface.
99+
func (l *Logger) PersistInfo(msg string, args ...any) {
100+
args = append(args, persistKeyArg, true)
101+
l.Info(msg, args...)
102+
}
103+
104+
// PersistWarn implements Interface.
105+
func (l *Logger) PersistWarn(msg string, args ...any) {
106+
args = append(args, persistKeyArg, true)
107+
l.Warn(msg, args...)
108+
}
109+
69110
func (l *Logger) Debug(msg string, args ...any) {
70111
l.logger.Debug(msg, args...)
71112
}
@@ -82,19 +123,19 @@ func (l *Logger) Error(msg string, args ...any) {
82123
l.logger.Error(msg, args...)
83124
}
84125

85-
func (l *Logger) List() []Message {
126+
func (l *Logger) List() []LogMessage {
86127
return l.writer.messages
87128
}
88129

89-
func (l *Logger) Get(id string) (Message, error) {
130+
func (l *Logger) Get(id string) (LogMessage, error) {
90131
for _, msg := range l.writer.messages {
91132
if msg.ID == id {
92133
return msg, nil
93134
}
94135
}
95-
return Message{}, io.EOF
136+
return LogMessage{}, io.EOF
96137
}
97138

98-
func (l *Logger) Subscribe(ctx context.Context) <-chan pubsub.Event[Message] {
139+
func (l *Logger) Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
99140
return l.writer.Subscribe(ctx)
100141
}

internal/logging/logging.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ type Interface interface {
1111
Info(msg string, args ...any)
1212
Warn(msg string, args ...any)
1313
Error(msg string, args ...any)
14-
Subscribe(ctx context.Context) <-chan pubsub.Event[Message]
14+
Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage]
1515

16-
List() []Message
16+
PersistDebug(msg string, args ...any)
17+
PersistInfo(msg string, args ...any)
18+
PersistWarn(msg string, args ...any)
19+
PersistError(msg string, args ...any)
20+
List() []LogMessage
21+
22+
SetLevel(level string)
1723
}

internal/logging/message.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"time"
55
)
66

7-
// Message is the event payload for a log message
8-
type Message struct {
9-
ID string
10-
Time time.Time
11-
Level string
12-
Message string `json:"msg"`
13-
Attributes []Attr
7+
// LogMessage is the event payload for a log message
8+
type LogMessage struct {
9+
ID string
10+
Time time.Time
11+
Level string
12+
Persist bool // used when we want to show the mesage in the status bar
13+
PersistTime time.Duration // used when we want to show the mesage in the status bar
14+
Message string `json:"msg"`
15+
Attributes []Attr
1416
}
1517

1618
type Attr struct {

internal/logging/writer.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import (
1010
)
1111

1212
type writer struct {
13-
messages []Message
14-
*pubsub.Broker[Message]
13+
messages []LogMessage
14+
*pubsub.Broker[LogMessage]
1515
}
1616

1717
func (w *writer) Write(p []byte) (int, error) {
1818
d := logfmt.NewDecoder(bytes.NewReader(p))
1919
for d.ScanRecord() {
20-
msg := Message{
21-
ID: time.Now().Format(time.RFC3339Nano),
20+
msg := LogMessage{
21+
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
2222
}
2323
for d.ScanKeyval() {
2424
switch string(d.Key()) {
@@ -33,10 +33,20 @@ func (w *writer) Write(p []byte) (int, error) {
3333
case "msg":
3434
msg.Message = string(d.Value())
3535
default:
36-
msg.Attributes = append(msg.Attributes, Attr{
37-
Key: string(d.Key()),
38-
Value: string(d.Value()),
39-
})
36+
if string(d.Key()) == persistKeyArg {
37+
msg.Persist = true
38+
} else if string(d.Key()) == PersistTimeArg {
39+
parsed, err := time.ParseDuration(string(d.Value()))
40+
if err != nil {
41+
continue
42+
}
43+
msg.PersistTime = parsed
44+
} else {
45+
msg.Attributes = append(msg.Attributes, Attr{
46+
Key: string(d.Key()),
47+
Value: string(d.Value()),
48+
})
49+
}
4050
}
4151
}
4252
w.messages = append(w.messages, msg)

0 commit comments

Comments
 (0)