feat(history): 会话列表lastMessage预览(B3) — 单查询无N+1
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- history/session_preview.go: SessionMeta.LastMessage + 单条SQL(LEFT JOIN取每会话ORDER BY created_at DESC,rowid DESC LIMIT 1,走覆盖索引)
- ListSessions 迁出重写(签名不变), content折叠空白截前60rune
- gateway: historySessionSummary 增 lastMessage{role,content,createdAt}(omitempty), /sessions与/admin/sessions共用
- 新增测试(session_preview_test, history_lastmessage_test)
- Docs/api-client.md v0.22 同步
This commit is contained in:
AskaEth committed 2026-09-08 21:17:26 +08:00
1 parent 89c5fe8027
commit d102f75469
6 files changed
+331 -35

No files matched your search

+5 -30
View File
@@ -109,6 +109,11 @@ type SessionMeta struct {
Type string // "main" | "sub"
Title string
CreatedAt int64
// LastMessage is a display-only preview of the session's newest message, filled
// by ListSessions in the same query. It is nil for a session with no messages.
// A client uses it to render the conversation row without a per-session request;
// identity and permissions never derive from it.
LastMessage *MessageSummary
}
// HistoryStore abstracts the chat-history store so callers (the gateway) and tests can
@@ -481,36 +486,6 @@ func (s *Store) NewSubSession(ctx context.Context, ownerID string) (string, erro
return sessionID, nil
}
// ListSessions returns the main session first, then every sub session, oldest first. The
// returned slice is non-nil.
func (s *Store) ListSessions(ctx context.Context) ([]SessionMeta, error) {
rows, err := s.db.QueryContext(ctx, `SELECT id, type, title, created_at FROM chat_sessions
ORDER BY (type = ?) DESC, created_at ASC`, SessionMain)
if err != nil {
return nil, fmt.Errorf("history: list sessions: %w", err)
}
defer func() { _ = rows.Close() }()
sessions := make([]SessionMeta, 0)
for rows.Next() {
var (
meta SessionMeta
typeStr string
createdAt int64
)
if err := rows.Scan(&meta.ID, &typeStr, &meta.Title, &createdAt); err != nil {
return nil, fmt.Errorf("history: scan session: %w", err)
}
meta.Type = typeStr
meta.CreatedAt = createdAt
sessions = append(sessions, meta)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("history: iterate sessions: %w", err)
}
return sessions, nil
}
// ensureMainSession inserts the fixed main session row when it is absent, so ListSessions
// always reports the main session even before any message is written.
func (s *Store) ensureMainSession(ctx context.Context) error {