v0.0.4: add sessions and refine tui
This commit is contained in:
+165
-8
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"agentu/internal/agent"
|
||||
"agentu/internal/config"
|
||||
@@ -12,14 +13,16 @@ import (
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
cfg *config.Config
|
||||
agent *agent.Agent
|
||||
httpClient *http.Client
|
||||
providerName string
|
||||
provider config.ProviderConfig
|
||||
cfg *config.Config
|
||||
agent *agent.Agent
|
||||
httpClient *http.Client
|
||||
providerName string
|
||||
provider config.ProviderConfig
|
||||
store *Store
|
||||
currentSession *Session
|
||||
}
|
||||
|
||||
func NewManager(cfg *config.Config, assistant *agent.Agent, httpClient *http.Client) (*Manager, error) {
|
||||
func NewManager(cfg *config.Config, assistant *agent.Agent, httpClient *http.Client, store *Store) (*Manager, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
@@ -28,13 +31,160 @@ func NewManager(cfg *config.Config, assistant *agent.Agent, httpClient *http.Cli
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no provider is configured")
|
||||
}
|
||||
return &Manager{
|
||||
m := &Manager{
|
||||
cfg: cfg,
|
||||
agent: assistant,
|
||||
httpClient: httpClient,
|
||||
providerName: providerName,
|
||||
provider: provider,
|
||||
}, nil
|
||||
store: store,
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ResumeLatest loads the most recent session, or creates a new one.
|
||||
func (m *Manager) ResumeLatest() error {
|
||||
if m.store == nil {
|
||||
return nil
|
||||
}
|
||||
metas, err := m.store.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(metas) > 0 {
|
||||
return m.Resume(metas[0].ID)
|
||||
}
|
||||
m.newSession()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resume loads a session by ID and restores it into the agent.
|
||||
func (m *Manager) Resume(id string) error {
|
||||
if m.store == nil {
|
||||
return fmt.Errorf("session storage is not configured")
|
||||
}
|
||||
sess, err := m.store.Load(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.currentSession = sess
|
||||
m.agent.SetMessages(sess.Messages)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save persists the current session to disk.
|
||||
func (m *Manager) Save() error {
|
||||
if m.store == nil || m.currentSession == nil {
|
||||
return nil
|
||||
}
|
||||
m.currentSession.Messages = m.agent.Messages()
|
||||
m.currentSession.Provider = m.providerName
|
||||
m.currentSession.Model = m.provider.Model
|
||||
// Auto-name if empty
|
||||
if m.currentSession.Name == "" {
|
||||
m.currentSession.Name = DefaultName(m.currentSession.Messages)
|
||||
}
|
||||
return m.store.Save(m.currentSession)
|
||||
}
|
||||
|
||||
// Rename changes the current session name and saves.
|
||||
func (m *Manager) Rename(name string) (string, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("name is required")
|
||||
}
|
||||
if m.currentSession == nil {
|
||||
return "", fmt.Errorf("no active session")
|
||||
}
|
||||
m.currentSession.Name = name
|
||||
if err := m.Save(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Renamed session to %q.", name), nil
|
||||
}
|
||||
|
||||
// Sessions returns a formatted list of all sessions.
|
||||
func (m *Manager) Sessions() (string, error) {
|
||||
if m.store == nil {
|
||||
return "", fmt.Errorf("session storage is not configured")
|
||||
}
|
||||
metas, err := m.store.List()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(metas) == 0 {
|
||||
return "No saved sessions.", nil
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("Sessions:\n")
|
||||
for _, meta := range metas {
|
||||
name := meta.Name
|
||||
if name == "" {
|
||||
name = "(unnamed)"
|
||||
}
|
||||
current := ""
|
||||
if m.currentSession != nil && meta.ID == m.currentSession.ID {
|
||||
current = " (current)"
|
||||
}
|
||||
ts := meta.UpdatedAt.Local().Format("2006-01-02 15:04")
|
||||
fmt.Fprintf(&b, " %s %-30s %s %d msgs%s\n", meta.ID, fitName(name, 30), ts, meta.MessageCount, current)
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// NewSession starts a fresh session, saving the current one first.
|
||||
func (m *Manager) NewSession() (string, error) {
|
||||
// Save current if it has user messages
|
||||
if m.currentSession != nil {
|
||||
msgs := m.agent.Messages()
|
||||
hasUser := false
|
||||
for _, msg := range msgs {
|
||||
if msg.Role == llm.RoleUser {
|
||||
hasUser = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasUser {
|
||||
m.currentSession.Messages = msgs
|
||||
m.currentSession.Provider = m.providerName
|
||||
m.currentSession.Model = m.provider.Model
|
||||
if m.currentSession.Name == "" {
|
||||
m.currentSession.Name = DefaultName(msgs)
|
||||
}
|
||||
if m.store != nil {
|
||||
_ = m.store.Save(m.currentSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.newSession()
|
||||
return "Session saved. Starting new session.", nil
|
||||
}
|
||||
|
||||
func (m *Manager) newSession() {
|
||||
m.currentSession = &Session{
|
||||
ID: GenerateID(),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
Provider: m.providerName,
|
||||
Model: m.provider.Model,
|
||||
}
|
||||
m.agent.Clear()
|
||||
}
|
||||
|
||||
func (m *Manager) CurrentSessionID() string {
|
||||
if m.currentSession == nil {
|
||||
return ""
|
||||
}
|
||||
return m.currentSession.ID
|
||||
}
|
||||
|
||||
func (m *Manager) CurrentSessionName() string {
|
||||
if m.currentSession == nil {
|
||||
return ""
|
||||
}
|
||||
if m.currentSession.Name != "" {
|
||||
return m.currentSession.Name
|
||||
}
|
||||
return m.currentSession.ID
|
||||
}
|
||||
|
||||
func (m *Manager) CurrentProvider() string {
|
||||
@@ -140,3 +290,10 @@ func contains(values []string, value string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fitName(name string, width int) string {
|
||||
if len(name) <= width {
|
||||
return name
|
||||
}
|
||||
return name[:width-3] + "..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user