improve(session): redesign /session list display for clarity
- Show summary (truncated to 40 chars) as primary identifier - Move #id to trailing parentheses to reduce visual noise - Use relative time (2h ago) instead of absolute timestamps - Add Summary field to SessionMeta, populated from Session.Summary - List row number = resume parameter, zero ambiguity Before: 1. [*] agent:main:telegram:direct:300900269#10 | updated: 2026-03-01 23:22 | messages: 8 After: 1. [*] Discussing React performance... | 8 msgs | 2h ago (#10) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
be2b9507dd
commit
b323131c27
3 changed files with 62 additions and 9 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/session"
|
||||
)
|
||||
|
|
@ -72,20 +73,65 @@ func sessionResumeHandler() Handler {
|
|||
|
||||
func formatSessionList(list []session.SessionMeta) string {
|
||||
lines := make([]string, 0, len(list)+1)
|
||||
lines = append(lines, "Sessions for current chat:")
|
||||
lines = append(lines, "Sessions:")
|
||||
now := time.Now()
|
||||
for _, item := range list {
|
||||
activeMarker := " "
|
||||
if item.Active {
|
||||
activeMarker = "*"
|
||||
activeMarker = "[*]"
|
||||
}
|
||||
updated := "-"
|
||||
|
||||
summary := truncateSummary(item.Summary, 40)
|
||||
|
||||
age := "-"
|
||||
if !item.UpdatedAt.IsZero() {
|
||||
updated = item.UpdatedAt.Format("2006-01-02 15:04")
|
||||
age = relativeTime(now, item.UpdatedAt)
|
||||
}
|
||||
|
||||
tag := extractSessionTag(item.SessionKey)
|
||||
|
||||
lines = append(lines, fmt.Sprintf(
|
||||
"%d. [%s] %s | updated: %s | messages: %d",
|
||||
item.Ordinal, activeMarker, item.SessionKey, updated, item.MessageCnt,
|
||||
"%d. %s %s | %d msgs | %s (%s)",
|
||||
item.Ordinal, activeMarker, summary, item.MessageCnt, age, tag,
|
||||
))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func truncateSummary(s string, maxLen int) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return "(no summary)"
|
||||
}
|
||||
s = strings.ReplaceAll(s, "\n", " ")
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
|
||||
// extractSessionTag returns the "#N" suffix from a session key, or "#1" for
|
||||
// the initial session that has no "#" separator.
|
||||
func extractSessionTag(sessionKey string) string {
|
||||
if i := strings.LastIndex(sessionKey, "#"); i >= 0 {
|
||||
return "#" + sessionKey[i+1:]
|
||||
}
|
||||
return "#1"
|
||||
}
|
||||
|
||||
func relativeTime(now, t time.Time) string {
|
||||
d := now.Sub(t)
|
||||
switch {
|
||||
case d < time.Minute:
|
||||
return "just now"
|
||||
case d < time.Hour:
|
||||
m := int(d.Minutes())
|
||||
return fmt.Sprintf("%dm ago", m)
|
||||
case d < 24*time.Hour:
|
||||
h := int(d.Hours())
|
||||
return fmt.Sprintf("%dh ago", h)
|
||||
default:
|
||||
days := int(d.Hours() / 24)
|
||||
return fmt.Sprintf("%dd ago", days)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package commands
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -119,6 +120,7 @@ func TestSessionHandlers_SessionList(t *testing.T) {
|
|||
UpdatedAt: time.Date(2026, 3, 1, 9, 7, 0, 0, time.UTC),
|
||||
MessageCnt: 4,
|
||||
Active: true,
|
||||
Summary: "Discussing React performance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -135,8 +137,11 @@ func TestSessionHandlers_SessionList(t *testing.T) {
|
|||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
if reply != "Sessions for current chat:\n1. [*] scope#3 | updated: 2026-03-01 09:07 | messages: 4" {
|
||||
t.Fatalf("reply=%q", reply)
|
||||
// Check key elements: header, active marker, summary, message count, session tag
|
||||
for _, want := range []string{"Sessions:", "[*]", "Discussing React performance", "4 msgs", "(#3)"} {
|
||||
if !strings.Contains(reply, want) {
|
||||
t.Fatalf("reply missing %q, got %q", want, reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ type SessionMeta struct {
|
|||
UpdatedAt time.Time `json:"updated_at"`
|
||||
MessageCnt int `json:"message_cnt"`
|
||||
Active bool `json:"active"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
}
|
||||
|
||||
type SessionManager struct {
|
||||
|
|
@ -740,6 +741,7 @@ func (sm *SessionManager) buildSessionMetaListLocked(scope *scopeIndex) []Sessio
|
|||
if session, ok := sm.sessions[key]; ok {
|
||||
meta.UpdatedAt = session.Updated
|
||||
meta.MessageCnt = len(session.Messages)
|
||||
meta.Summary = session.Summary
|
||||
}
|
||||
list = append(list, meta)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue