page history session messages (#1099)

This commit is contained in:
ekko
2026-05-28 19:47:24 +08:00
committed by GitHub
parent 9d2e82cd06
commit 932c913d63
4 changed files with 246 additions and 61 deletions
@@ -1,5 +1,5 @@
import * as hermesCli from '../../services/hermes/hermes-cli'
import { listSessionSummaries, getUsageStatsFromDb, getSessionDetailFromDb, getSessionDetailFromDbWithProfile, getExactSessionDetailFromDbWithProfile } from '../../db/hermes/sessions-db'
import { listSessionSummaries, getUsageStatsFromDb, getSessionDetailFromDb, getSessionDetailFromDbWithProfile, getSessionDetailPaginatedFromDbWithProfile, getExactSessionDetailFromDbWithProfile } from '../../db/hermes/sessions-db'
import {
listSessions as localListSessions,
searchSessions as localSearchSessions,
@@ -872,29 +872,35 @@ function serializeAsText(title: string | null, messages: any[]): string {
export async function getConversationMessagesPaginated(ctx: any) {
const offset = ctx.query.offset ? parseInt(ctx.query.offset as string, 10) : 0
const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : 50
const profile = requestedProfile(ctx)
const { getSessionDetailPaginated } = await import('../../db/hermes/session-store')
const result = getSessionDetailPaginated(ctx.params.id, offset, limit)
const localResult = getSessionDetailPaginated(ctx.params.id, offset, limit)
const result = localResult && (!profile || localResult.session.profile === profile)
? localResult
: await getSessionDetailPaginatedFromDbWithProfile(ctx.params.id, profile || 'default', offset, limit)
if (!result) {
ctx.status = 404
ctx.body = { error: 'Conversation not found' }
return
}
if (denySessionAccess(ctx, result.session)) return
const session = { ...result.session, profile: (result.session as any).profile || profile || 'default' }
if (denySessionAccess(ctx, session)) return
ctx.body = {
session: {
id: result.session.id,
source: result.session.source,
model: result.session.model,
title: result.session.title,
started_at: result.session.started_at,
ended_at: result.session.ended_at,
last_active: result.session.last_active,
message_count: result.session.message_count,
input_tokens: result.session.input_tokens,
output_tokens: result.session.output_tokens,
id: session.id,
profile: session.profile,
source: session.source,
model: session.model,
title: session.title,
started_at: session.started_at,
ended_at: session.ended_at,
last_active: session.last_active,
message_count: session.message_count,
input_tokens: session.input_tokens,
output_tokens: session.output_tokens,
},
messages: result.messages,
total: result.total,
@@ -62,6 +62,15 @@ export interface HermesSessionDetailRow extends HermesSessionRow {
thread_session_count: number
}
export interface PaginatedHermesSessionDetailResult {
session: HermesSessionDetailRow
messages: HermesMessageRow[]
total: number
offset: number
limit: number
hasMore: boolean
}
interface HermesSessionInternalRow extends HermesSessionRow {
parent_session_id: string | null
}
@@ -669,6 +678,52 @@ export async function getSessionDetailFromDbWithProfile(sessionId: string, profi
}
}
export async function getSessionDetailPaginatedFromDbWithProfile(
sessionId: string,
profile: string,
offset = 0,
limit = 300,
): Promise<PaginatedHermesSessionDetailResult | null> {
const db = await openSessionDb(profile)
try {
const idx = loadAllSessions(db)
const requested = idx.byId.get(sessionId) || null
if (!requested) return null
const chain = collectSessionChainForMatchedSession(requested, idx)
if (!chain.length) return null
const ids = chain.map(session => session.id)
const placeholders = ids.map(() => '?').join(', ')
const orderSql = chainOrderSql(ids)
const totalRow = db.prepare(`
SELECT COUNT(*) AS total
FROM messages
WHERE session_id IN (${placeholders})
`).get(...ids) as { total: number } | undefined
const total = Number(totalRow?.total || 0)
const messageRows = db.prepare(`
SELECT * FROM messages
WHERE session_id IN (${placeholders})
ORDER BY CASE session_id ${orderSql} ELSE ${ids.length} END DESC, id DESC
LIMIT ? OFFSET ?
`).all(...ids, ...ids, limit, offset) as Record<string, unknown>[]
const messages = messageRows.map(mapMessageRow).reverse()
return {
session: aggregateSessionDetail(chain, messages, sessionId),
messages,
total,
offset,
limit,
hasMore: offset + messages.length < total,
}
} finally {
db.close()
}
}
export async function getExactSessionDetailFromDbWithProfile(sessionId: string, profile: string): Promise<HermesSessionDetailRow | null> {
const { DatabaseSync } = await import('node:sqlite')
const dbPath = join(getProfileDir(profile), 'state.db')