fix(chat): 修复会话刷新丢失最终答案(#3) + 反向分页/详情懒加载/会话URL持久化
- 后端消息列表默认按 id 升序取最旧一页,长会话刷新后最终答案落在页外而丢失
(发新消息推进游标才重现)。改为:
- db.ConvActivityPage:反向分页,取最新/更早一页 + hasMore
- 消息接口按 since(增量)/before(更早页)/最新页 三种模式返回
- 前端聊天页:
- 打开会话仅加载最新一页,滚动到顶部再反向加载更早(保持滚动位置不跳)
- Transcript 的 UserRow/AnswerBlock 改为进入视口才拉 detail,避免打开会话即对
整页每步发详情请求
- 选中会话 id 持久化到 URL(?c=<id>),刷新回到当前会话
- 修复新建会话后被"清理失效 id"effect 误清导致停留在新建页、输入框内容与灰色
发送按钮卡死:移除该 effect + onStarted 乐观插入
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
1 parent
349b0d104d
commit
5000aadd8f
5 files changed
+207
-17
No files matched your search
@@ -174,6 +174,49 @@ FROM conversation_activities WHERE conversation_id=$1 AND id>$2 ORDER BY id LIMI
|
||||
return out, cursor, rows.Err()
|
||||
}
|
||||
|
||||
// ConvActivityPage returns one page for reverse (newest-first) pagination: up to
|
||||
// `limit` steps ending before id `before` (exclusive; before<=0 = the latest
|
||||
// page), returned in ASCENDING id order. hasMore reports whether still-older steps
|
||||
// exist before the returned window, so the client can stop loading earlier history
|
||||
// on scroll-up. Summary-only columns (detail is lazy-loaded via ConvActivityDetail).
|
||||
func (d *DB) ConvActivityPage(convID, before int64, limit int) ([]Activity, bool, error) {
|
||||
if limit <= 0 {
|
||||
limit = 200
|
||||
}
|
||||
const cols = `id, COALESCE(worker,''), COALESCE(kind,''), COALESCE(tool,''), COALESCE(tool_use_id,''), is_error, COALESCE(summary,''), created_at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens`
|
||||
// fetch one extra row to detect whether older history remains before this window.
|
||||
rows, err := d.Query(`SELECT `+cols+`
|
||||
FROM conversation_activities
|
||||
WHERE conversation_id=$1 AND ($2 <= 0 OR id < $2)
|
||||
ORDER BY id DESC LIMIT $3`, convID, before, limit+1)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer rows.Close()
|
||||
desc := []Activity{}
|
||||
for rows.Next() {
|
||||
var a Activity
|
||||
if err := rows.Scan(&a.ID, &a.Worker, &a.Kind, &a.Tool, &a.ToolUseID, &a.IsError, &a.Summary, &a.CreatedAt,
|
||||
&a.InputTokens, &a.OutputTokens, &a.CacheReadTokens, &a.CacheWriteTokens); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
desc = append(desc, a)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
hasMore := len(desc) > limit
|
||||
if hasMore {
|
||||
desc = desc[:limit]
|
||||
}
|
||||
// reverse the newest-first window into ascending id order for display.
|
||||
out := make([]Activity, len(desc))
|
||||
for i, a := range desc {
|
||||
out[len(desc)-1-i] = a
|
||||
}
|
||||
return out, hasMore, nil
|
||||
}
|
||||
|
||||
// ConvActivityDetail lazily returns the full detail blob for one step.
|
||||
func (d *DB) ConvActivityDetail(convID, id int64) (string, error) {
|
||||
var s sql.NullString
|
||||
|
||||
Reference in new issue
Block a user