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

+26 -4
View File
@@ -20,6 +20,18 @@ type historySessionSummary struct {
Type string `json:"type"` // "main" | "sub"
Title string `json:"title,omitempty"`
CreatedAt int64 `json:"createdAt"`
// LastMessage is a display-only preview of the session's newest message, so the
// client can render a conversation row (e.g. the Android session list) without a
// per-session request. It is omitted for a session with no messages. Content is
// already truncated by the store.
LastMessage *historyLastMessage `json:"lastMessage,omitempty"`
}
// historyLastMessage is the wire shape of the last-message preview in a session summary.
type historyLastMessage struct {
Role string `json:"role"` // "user" | "assistant"
Content string `json:"content"`
CreatedAt int64 `json:"createdAt"`
}
// historyMessage is the wire shape of one persisted chat message in the
@@ -60,15 +72,25 @@ func (s *Server) handleHistorySessions(w http.ResponseWriter, r *http.Request) {
out := make([]historySessionSummary, 0, len(meta))
for _, m := range meta {
out = append(out, historySessionSummary{
ID: m.ID,
Type: m.Type,
Title: m.Title,
CreatedAt: m.CreatedAt,
ID: m.ID,
Type: m.Type,
Title: m.Title,
CreatedAt: m.CreatedAt,
LastMessage: toHistoryLastMessage(m.LastMessage),
})
}
writeJSON(w, http.StatusOK, out)
}
// toHistoryLastMessage maps a stored last-message preview to its wire shape, returning
// nil for a session that has no messages (so the field is omitted from the JSON).
func toHistoryLastMessage(m *history.MessageSummary) *historyLastMessage {
if m == nil {
return nil
}
return &historyLastMessage{Role: m.Role, Content: m.Content, CreatedAt: m.CreatedAt}
}
// handleCreateSubSession creates a new sub session and returns its sessionId. It is a
// user-side write, so it is owner/admin-gated like /api/v1/chat.
func (s *Server) handleCreateSubSession(w http.ResponseWriter, r *http.Request) {