ai-agent/internal/tui/session.go
admin 8dc496b626
Some checks failed
CI / test (push) Has been cancelled
Release / release (push) Failing after 4m36s
first commit
2026-03-08 15:40:34 +07:00

138 lines
3.7 KiB
Go

package tui
import (
"encoding/json"
"fmt"
"os/exec"
"strconv"
"strings"
)
type SessionListItem struct {
ID int `json:"id"`
Title string `json:"title"`
CreatedAt string `json:"created_at"`
}
type SessionNote struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
}
func notedAvailable() bool {
_, err := exec.LookPath("noted")
return err == nil
}
func createSessionNote(timestamp string) (int, error) {
title := fmt.Sprintf("ai-agent session %s", timestamp)
cmd := exec.Command("noted", "add", "-t", title, "-c", "(session in progress)", "--tags", "ai-agent,session", "--json")
out, err := cmd.Output()
if err != nil {
return 0, fmt.Errorf("noted add: %w", err)
}
var result struct {
ID int `json:"id"`
}
if err := json.Unmarshal(out, &result); err != nil {
return 0, fmt.Errorf("parse noted output: %w", err)
}
return result.ID, nil
}
func updateSessionNote(id int, content string) error {
cmd := exec.Command("noted", "edit", strconv.Itoa(id), "-c", content)
return cmd.Run()
}
func listSessions(limit int) ([]SessionListItem, error) {
cmd := exec.Command("noted", "list", "--tag", "session", "--json", "-n", strconv.Itoa(limit))
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("noted list: %w", err)
}
var sessions []SessionListItem
if err := json.Unmarshal(out, &sessions); err != nil {
return nil, fmt.Errorf("parse noted output: %w", err)
}
return sessions, nil
}
func loadSession(id int) (*SessionNote, error) {
cmd := exec.Command("noted", "show", strconv.Itoa(id), "--json")
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("noted show: %w", err)
}
var note SessionNote
if err := json.Unmarshal(out, &note); err != nil {
return nil, fmt.Errorf("parse noted output: %w", err)
}
return &note, nil
}
func serializeEntries(entries []ChatEntry) string {
var b strings.Builder
for _, e := range entries {
switch e.Kind {
case "user":
b.WriteString("## User\n\n")
b.WriteString(e.Content)
b.WriteString("\n\n")
case "assistant":
b.WriteString("## Assistant\n\n")
b.WriteString(e.Content)
b.WriteString("\n\n")
case "system":
b.WriteString("## System\n\n")
b.WriteString(e.Content)
b.WriteString("\n\n")
case "error":
b.WriteString("## Error\n\n")
b.WriteString(e.Content)
b.WriteString("\n\n")
}
}
return strings.TrimRight(b.String(), "\n")
}
func deserializeEntries(content string) []ChatEntry {
if content == "" {
return nil
}
var entries []ChatEntry
sections := strings.Split(content, "## ")
for _, section := range sections {
section = strings.TrimSpace(section)
if section == "" {
continue
}
nlIdx := strings.Index(section, "\n")
if nlIdx == -1 {
continue
}
header := strings.TrimSpace(section[:nlIdx])
body := strings.TrimSpace(section[nlIdx+1:])
var kind string
switch header {
case "User":
kind = "user"
case "Assistant":
kind = "assistant"
case "System":
kind = "system"
case "Error":
kind = "error"
default:
continue
}
entries = append(entries, ChatEntry{
Kind: kind,
Content: body,
})
}
return entries
}