docs(comments): 注释中文化 · internal/runtime/history(15 文件,纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 只翻注释/文档字符串:剥离注释后代码行逐字节不变(机械校验只剩 4 条行尾注释的翻译,代码前缀全等)
- 交叉引用(design/16 §4.6、D4/B3、0010 迁移文件名)逐字保留
- 保留未译技术词:DDL/rowid/LIKE/LEFT JOIN/DTO/rune/WS/actor 及全部标识符与表名
- 顺带副作用:store.go、session_persona_test.go 由注释文字改变而变得 gofmt 干净(未运行 gofmt -w)
This commit is contained in:
AskaEth committed 2026-10-04 12:28:48 +08:00
1 parent b0fbb8bc0d
commit 09fba77f87
15 files changed
+430 -516

No files matched your search

+26 -33
View File
@@ -7,34 +7,29 @@ import (
"strings"
)
// This file owns the two derived per-session display fields carried in the session
// list (GET /api/v1/sessions): the last-message preview and the unread count. Both are
// read-only projections so a client can render a conversation row (with its unread
// badge) without one request per session. The preview is truncated for display, and
// neither field ever feeds identity or permission decisions — identity comes from
// channel actor metadata / JWT (project red line).
// 本文件负责会话列表中携带的两个派生展示字段(GET /api/v1/sessions):最后
// 消息预览与未读数。两者都是只读投影,使客户端无需逐会话请求就能渲染对话行
// (及其未读角标)。预览会被截断以便展示,两个字段都绝不参与身份或权限判定
// —— 身份来自渠道 actor 元数据 / JWT(项目红线)。
// lastMessagePreviewRunes bounds the preview content so a long message cannot bloat the
// session-list response. A longer message is whitespace-collapsed and truncated to this
// many runes with an ellipsis.
// lastMessagePreviewRunes 限制预览内容,使一条长消息不会撑大会话列表响应。
// 更长的消息会先折叠空白,再截断到这么多 rune 并加省略号。
const lastMessagePreviewRunes = 60
// MessageSummary is a display-only preview of a session's most recent message.
// MessageSummary 是会话最新消息的纯展示预览。
type MessageSummary struct {
Role string // "user" | "assistant"
Content string // whitespace-collapsed, truncated preview
CreatedAt int64 // Unix milliseconds
Role string // "user" | "assistant"(用户 | 助手)
Content string // 已折叠空白、已截断的预览
CreatedAt int64 // Unix 毫秒
}
// listSessionsQuery reads every session plus its newest message and its unread count
// in one statement. The LEFT JOIN's correlated subquery resolves each session's newest
// row via the (session_id, created_at DESC) index (rowid DESC breaks a same-millisecond
// tie by insertion order), so a session with no messages still appears with NULL preview
// columns. The unread count is a correlated COUNT(*) over the same index
// (session_id = s.id AND created_at > s.read_at is an equality + range scan), so no
// per-session round trip (N+1) is needed and no extra index is required. Ordering floats
// pinned sessions first, then keeps the main session before unpinned subs, then subs
// oldest-first.
// listSessionsQuery 用一条语句读取每个会话及其最新消息与未读数。LEFT JOIN 的
// 关联子查询经由 (session_id, created_at DESC) 索引解析每个会话最新的那一行
// (rowid DESC 按插入顺序打破同一毫秒的并列),因此没有消息的会话仍会以预览
// 列全为 NULL 的形式出现。未读数是同一个索引上的关联 COUNT(*)
// (session_id = s.id AND created_at > s.read_at 是一次等值 + 范围扫描),
// 因此无需逐会话往返(N+1),也不需要额外索引。排序把置顶会话浮到最前,
// 再把主会话排在未置顶子会话之前,然后子会话最旧优先。
const listSessionsQuery = `SELECT s.id, s.type, s.title, s.pinned, s.created_at, s.persona_id,
m.role, m.content, m.created_at,
(SELECT COUNT(*) FROM chat_messages c
@@ -48,11 +43,10 @@ const listSessionsQuery = `SELECT s.id, s.type, s.title, s.pinned, s.created_at,
)
ORDER BY s.pinned DESC, (s.type = ?) DESC, s.created_at ASC`
// ListSessions returns pinned sessions first, then the main session, then every sub
// session, oldest first, each carrying a LastMessage preview of its newest message (nil
// when the session has none) and the UnreadCount derived from the session's read cursor.
// Sessions, previews and unread counts are read in a single query, so the query count
// does not grow with the number of sessions. The returned slice is non-nil.
// ListSessions 按置顶会话在前、其后是主会话、最后是每个子会话(最旧优先)的
// 顺序返回,每个会话携带其最新消息的 LastMessage 预览(没有消息时为 nil)与
// 由会话读游标推导出的 UnreadCount。会话、预览与未读数在单次查询中读出,
// 因此查询数量不随会话数增长。返回切片非 nil。
func (s *Store) ListSessions(ctx context.Context) ([]SessionMeta, error) {
rows, err := s.db.QueryContext(ctx, listSessionsQuery, SessionMain)
if err != nil {
@@ -74,10 +68,9 @@ func (s *Store) ListSessions(ctx context.Context) ([]SessionMeta, error) {
return sessions, nil
}
// scanSessionMeta reads one joined chat_sessions + last-message + unread-count row. The
// three message columns are NULL for a session that has never received a message, so they
// scan into nullable holders and only produce a LastMessage when a row is present. The
// unread count is a non-NULL COUNT(*), so it scans into a plain int.
// scanSessionMeta 读取一行已连接的 chat_sessions + 最新消息 + 未读数。从未收到
// 消息的会话那三个消息列为 NULL,因此它们扫描到可空的容器里,只有该行存在时
// 才产出 LastMessage。未读数是非 NULL 的 COUNT(*),因此扫描到普通 int。
func scanSessionMeta(sc rowScanner) (SessionMeta, error) {
var (
meta SessionMeta
@@ -102,8 +95,8 @@ func scanSessionMeta(sc rowScanner) (SessionMeta, error) {
return meta, nil
}
// previewContent collapses whitespace runs (newlines included) to single spaces and
// truncates to lastMessagePreviewRunes runes with an ellipsis. It is display-only.
// previewContent 把连续空白(含换行)折叠为单个空格,并截断到
// lastMessagePreviewRunes 个 rune 加省略号。它只用于展示。
func previewContent(content string) string {
joined := strings.Join(strings.Fields(content), " ")
runes := []rune(joined)