fix(summary): track source message coverage

This commit is contained in:
digua committed 2026-08-10 16:52:19 +08:00
1 parent 2a412b228a
commit 3c55352d4b
16 files changed
+246 -44

No files matched your search

@@ -996,6 +996,8 @@ export interface ChatSessionItem {
endTs: number
messageCount: number
firstMessageId: number
summary?: string | null
summaryMessageCount?: number | null
}
/**
@@ -18,6 +18,7 @@ import {
getChatSessionList,
getSegmentSummary,
saveSegmentSummary,
loadSegmentMessages,
updateSessionGapThreshold,
clearSessionIndex,
generateSessionIndex,
@@ -85,7 +86,8 @@ function createSqliteDb(): TestSqliteDb {
end_ts INTEGER NOT NULL,
message_count INTEGER NOT NULL,
is_manual INTEGER DEFAULT 0,
summary TEXT
summary TEXT,
summary_message_count INTEGER
);
CREATE TABLE message_context (
message_id INTEGER NOT NULL,
@@ -276,6 +278,53 @@ describe('getSegmentSummary / saveSegmentSummary', () => {
saveSegmentSummary(db, 1, 'Test summary')
assert.equal(getSegmentSummary(db, 1), 'Test summary')
})
it('keeps an appended summary visible but treats it as stale', () => {
const db = createSqliteDb()
seedMessages(db, [{ id: 1, ts: 1000 }])
generateSessionIndex(db, 2000)
saveSegmentSummary(db, 1, 'Summary before append')
assert.equal(getSegmentSummary(db, 1), 'Summary before append')
seedMessages(db, [{ id: 2, ts: 1500 }])
generateIncrementalSessionIndex(db, 2000)
assert.equal(getSegmentSummary(db, 1), null)
assert.deepEqual(getChatSessionList(db)[0], {
id: 1,
startTs: 1000,
endTs: 1500,
messageCount: 2,
summary: 'Summary before append',
summaryMessageCount: 1,
firstMessageId: 1,
})
})
})
describe('loadSegmentMessages', () => {
it('loads every message for summary generation instead of truncating at 500', () => {
const db = createSqliteDb()
db.exec(`
ALTER TABLE message ADD COLUMN sender_id INTEGER;
ALTER TABLE message ADD COLUMN content TEXT;
CREATE TABLE member (
id INTEGER PRIMARY KEY,
platform_id TEXT NOT NULL,
account_name TEXT,
group_nickname TEXT
);
INSERT INTO member (id, platform_id, account_name) VALUES (1, 'alice', 'Alice');
`)
const insertMessage = db.prepare('INSERT INTO message (id, ts, sender_id, content) VALUES (?, ?, 1, ?)')
for (let id = 1; id <= 600; id++) insertMessage.run(id, 1000 + id, `message-${id}`)
generateSessionIndex(db, 2000)
const messages = loadSegmentMessages(db, 1)
assert.equal(messages?.length, 600)
assert.equal(messages?.[599]?.content, 'message-600')
})
})
describe('updateSessionGapThreshold', () => {
@@ -56,10 +56,10 @@ function createSessionDb(): SqliteTestAdapter {
(1, 1, 100, 0, 'one', 'pm-1'),
(2, 1, 300, 0, 'three', 'pm-3'),
(3, 2, 200, 0, 'two', NULL);
INSERT INTO segment (start_ts, end_ts, message_count, summary) VALUES
(100, 150, 1, 'summary one'),
(200, 250, 1, NULL),
(300, 350, 1, 'summary two');
INSERT INTO segment (start_ts, end_ts, message_count, summary, summary_message_count) VALUES
(100, 150, 1, 'summary one', 1),
(200, 250, 1, NULL, NULL),
(300, 350, 1, 'summary two', 1);
`)
return db
}
+36 -15
View File
@@ -95,9 +95,14 @@ export function getSessionInfo(db: DatabaseAdapter): CoreSessionInfo | null {
*/
export function getSummaryCount(db: DatabaseAdapter): number {
if (!hasTable(db, 'segment')) return 0
const row = db.prepare("SELECT COUNT(*) as count FROM segment WHERE summary IS NOT NULL AND summary != ''").get() as
| { count: number }
| undefined
const row = db
.prepare(
`SELECT COUNT(*) as count
FROM segment
WHERE summary IS NOT NULL AND summary != ''
AND summary_message_count = message_count`
)
.get() as { count: number } | undefined
return row?.count ?? 0
}
@@ -320,6 +325,7 @@ export function getSegmentSummaries(
cs.message_count as messageCount, cs.summary
FROM segment cs
WHERE cs.summary IS NOT NULL AND cs.summary != ''
AND cs.summary_message_count = cs.message_count
`
const params: unknown[] = []
@@ -388,6 +394,8 @@ export interface ChatSessionItem {
messageCount: number
firstMessageId: number
summary?: string | null
/** Number of messages covered when the summary was generated. Null means legacy/unknown coverage. */
summaryMessageCount?: number | null
}
export interface SessionIndexStats {
@@ -449,6 +457,7 @@ export function getSessionsByTimeRange(db: DatabaseAdapter, startTs: number, end
`SELECT
id, start_ts as startTs, end_ts as endTs,
message_count as messageCount, summary,
summary_message_count as summaryMessageCount,
(SELECT mc.message_id FROM message_context mc
JOIN message first_msg ON first_msg.id = mc.message_id
WHERE mc.segment_id = cs.id ORDER BY first_msg.ts ASC, first_msg.id ASC LIMIT 1) as firstMessageId
@@ -473,6 +482,7 @@ export function getRecentChatSessions(db: DatabaseAdapter, limit: number): ChatS
`SELECT
id, start_ts as startTs, end_ts as endTs,
message_count as messageCount, summary,
summary_message_count as summaryMessageCount,
(SELECT mc.message_id FROM message_context mc
JOIN message first_msg ON first_msg.id = mc.message_id
WHERE mc.segment_id = cs.id ORDER BY first_msg.ts ASC, first_msg.id ASC LIMIT 1) as firstMessageId
@@ -500,6 +510,7 @@ export function getChatSessionList(db: DatabaseAdapter): ChatSessionItem[] {
cs.end_ts as endTs,
cs.message_count as messageCount,
cs.summary,
cs.summary_message_count as summaryMessageCount,
(SELECT mc.message_id FROM message_context mc
JOIN message first_msg ON first_msg.id = mc.message_id
WHERE mc.segment_id = cs.id ORDER BY first_msg.ts ASC, first_msg.id ASC LIMIT 1) as firstMessageId
@@ -518,22 +529,24 @@ export function getChatSessionList(db: DatabaseAdapter): ChatSessionItem[] {
export function loadSegmentMessages(
db: DatabaseAdapter,
segmentId: number,
limit: number = 500
limit?: number
): Array<{ senderName: string; content: string | null }> | null {
try {
return db
.prepare(
`SELECT
const limitClause = limit === undefined ? '' : '\n LIMIT ?'
const statement = db.prepare(
`SELECT
COALESCE(mb.group_nickname, mb.account_name, mb.platform_id) as senderName,
m.content
FROM message_context mc
JOIN message m ON m.id = mc.message_id
JOIN member mb ON mb.id = m.sender_id
WHERE mc.segment_id = ?
ORDER BY m.ts ASC
LIMIT ?`
)
.all(segmentId, limit) as unknown as Array<{ senderName: string; content: string | null }>
ORDER BY m.ts ASC, m.id ASC${limitClause}`
)
return statement.all(...(limit === undefined ? [segmentId] : [segmentId, limit])) as unknown as Array<{
senderName: string
content: string | null
}>
} catch {
return null
}
@@ -544,10 +557,15 @@ export function loadSegmentMessages(
*/
export function getSegmentSummary(db: DatabaseAdapter, segmentId: number): string | null {
try {
const row = db.prepare('SELECT summary FROM segment WHERE id = ?').get(segmentId) as
| { summary: string | null }
const row = db
.prepare(
'SELECT summary, summary_message_count as summaryMessageCount, message_count as messageCount FROM segment WHERE id = ?'
)
.get(segmentId) as
| { summary: string | null; summaryMessageCount: number | null; messageCount: number }
| undefined
return row?.summary ?? null
if (!row?.summary || row.summaryMessageCount !== row.messageCount) return null
return row.summary
} catch {
return null
}
@@ -557,7 +575,10 @@ export function getSegmentSummary(db: DatabaseAdapter, segmentId: number): strin
* Save summary text for a chat session.
*/
export function saveSegmentSummary(db: DatabaseAdapter, segmentId: number, summary: string): void {
db.prepare('UPDATE segment SET summary = ? WHERE id = ?').run(summary, segmentId)
db.prepare('UPDATE segment SET summary = ?, summary_message_count = message_count WHERE id = ?').run(
summary,
segmentId
)
}
/**
+4 -3
View File
@@ -4,11 +4,11 @@
* 所有 CREATE TABLE / INDEX 语句的单一事实来源。
* 新建数据库时使用完整 Schema,现有数据库通过迁移脚本演进。
*
* 当前 Schema 版本:9
* 当前 Schema 版本:10
*/
/** 当前 Schema 版本(最新迁移的版本号) */
export const CURRENT_SCHEMA_VERSION = 9
export const CURRENT_SCHEMA_VERSION = 10
/**
* Table DDL only (no indexes). Used by bulk-import workflows that defer
@@ -66,7 +66,8 @@ export const CHAT_DB_TABLES = `
end_ts INTEGER NOT NULL,
message_count INTEGER DEFAULT 0,
is_manual INTEGER DEFAULT 0,
summary TEXT
summary TEXT,
summary_message_count INTEGER
);
CREATE TABLE IF NOT EXISTS message_context (
@@ -244,6 +244,61 @@ test('open safely upgrades a v8 session that has no FTS table', () => {
manager.closeAll()
})
test('open adds summary coverage metadata without trusting legacy summaries', () => {
const root = makeTempDir()
const dbDir = path.join(root, 'data', 'databases')
fs.mkdirSync(dbDir, { recursive: true })
const dbPath = path.join(dbDir, 'v9-summary-coverage.db')
const rawDb = new Database(dbPath, { nativeBinding })
rawDb.exec(`
CREATE TABLE meta (
name TEXT NOT NULL,
platform TEXT NOT NULL,
type TEXT NOT NULL,
imported_at INTEGER NOT NULL,
schema_version INTEGER DEFAULT 9
);
INSERT INTO meta (name, platform, type, imported_at, schema_version)
VALUES ('V9 Summary', 'qq', 'group', 1000, 9);
CREATE TABLE member (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform_id TEXT NOT NULL UNIQUE
);
CREATE TABLE message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id INTEGER NOT NULL,
ts INTEGER NOT NULL,
type INTEGER NOT NULL
);
CREATE TABLE segment (
id INTEGER PRIMARY KEY AUTOINCREMENT,
start_ts INTEGER NOT NULL,
end_ts INTEGER NOT NULL,
message_count INTEGER DEFAULT 0,
is_manual INTEGER DEFAULT 0,
summary TEXT
);
INSERT INTO segment (start_ts, end_ts, message_count, summary)
VALUES (1000, 1100, 2, 'legacy summary');
`)
rawDb.close()
const manager = new DatabaseManager(createPathProvider(root), { nativeBinding, allowMissingRuntimeForTests: true })
const db = manager.open('v9-summary-coverage')
assert.ok(db)
assert.deepEqual(db.prepare('SELECT schema_version FROM meta').get(), { schema_version: CURRENT_SCHEMA_VERSION })
assert.deepEqual(db.prepare('SELECT summary, summary_message_count FROM segment').get(), {
summary: 'legacy summary',
summary_message_count: null,
})
manager.closeAll()
})
test('open migrates v7 databases to include analysis tool indexes', () => {
const root = makeTempDir()
const dbDir = path.join(root, 'data', 'databases')
@@ -54,6 +54,14 @@ export function getChatDbMigrations(): CoreMigration[] {
return tableInfo.some((col) => col.name === columnName)
}
const hasTable = (db: DatabaseAdapter, tableName: string): boolean => {
return Boolean(
db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName) as
| Record<string, unknown>
| undefined
)
}
const addColumnIfMissing = (db: DatabaseAdapter, tableName: string, columnName: string, definition: string): void => {
if (!hasColumn(db, tableName, columnName)) {
db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition}`)
@@ -372,5 +380,14 @@ export function getChatDbMigrations(): CoreMigration[] {
db.exec('DROP TABLE IF EXISTS message_fts')
},
},
{
version: 10,
description: 'Track message coverage for generated segment summaries',
up: (db: DatabaseAdapter) => {
// Existing summaries may have been truncated or preserved after incremental imports.
// Keep their text for users to review, but leave coverage unknown so the new runtime marks them stale.
if (hasTable(db, 'segment')) addColumnIfMissing(db, 'segment', 'summary_message_count', 'INTEGER')
},
},
]
}
@@ -5,7 +5,7 @@
* and summary generation loop — shared across CLI Web and Electron.
*/
import { getSegmentSummary, saveSegmentSummary, getChatSessionList, getSegmentMessages } from '@openchatlab/core'
import { getSegmentSummary, saveSegmentSummary, getChatSessionList, loadSegmentMessages } from '@openchatlab/core'
import type { DatabaseAdapter } from '@openchatlab/core'
import {
generateSessionSummary,
@@ -29,10 +29,8 @@ export interface SummaryServiceDeps {
function buildSummaryDeps(db: DatabaseAdapter, llmConfig: LlmConfig, deps: SummaryServiceDeps): SummaryDeps {
const piModel = deps.buildPiModel(llmConfig)
return {
loadMessages(segmentId, limit = 500) {
const data = getSegmentMessages(db, segmentId, limit)
if (!data) return null
return data.messages.map((m) => ({ senderName: m.senderName, content: m.content }))
loadMessages(segmentId, limit) {
return loadSegmentMessages(db, segmentId, limit)
},
saveSummary(segmentId, summary) {
saveSegmentSummary(db, segmentId, summary)
@@ -109,10 +107,8 @@ export function checkCanGenerate(
): Record<number, { canGenerate: boolean; reason?: string }> {
const db = adapter.ensureReadonly(sessionId)
const deps: Pick<SummaryDeps, 'loadMessages' | 't'> = {
loadMessages(segmentId, limit = 500) {
const data = getSegmentMessages(db, segmentId, limit)
if (!data) return null
return data.messages.map((m) => ({ senderName: m.senderName, content: m.content }))
loadMessages(segmentId, limit) {
return loadSegmentMessages(db, segmentId, limit)
},
t: (key: string) => key,
}
@@ -47,6 +47,7 @@ interface SessionItem {
messageCount: number
// 与 sessionApi 返回值保持一致,历史数据里 summary 可能不存在。
summary?: string | null
summaryMessageCount?: number | null
}
const sessions = ref<SessionItem[]>([])
const isLoading = ref(false)
@@ -122,10 +123,18 @@ const timeRange = computed(() => {
const canGenerateMap = ref<Record<number, { canGenerate: boolean; reason?: string }>>({})
const isChecking = ref(false)
function isSummaryStale(session: SessionItem): boolean {
return Boolean(session.summary) && session.summaryMessageCount !== session.messageCount
}
function hasFreshSummary(session: SessionItem): boolean {
return Boolean(session.summary) && !isSummaryStale(session)
}
// 待生成的会话(排除已有摘要的 + 消息太少的)
const pendingSessions = computed(() => {
return sessions.value.filter((s) => {
if (s.summary) return false
if (hasFreshSummary(s)) return false
const checkResult = canGenerateMap.value[s.id]
return checkResult?.canGenerate !== false
})
@@ -133,13 +142,17 @@ const pendingSessions = computed(() => {
// 已有摘要的会话数
const existingSummaryCount = computed(() => {
return sessions.value.filter((s) => s.summary).length
return sessions.value.filter(hasFreshSummary).length
})
const staleSummaryCount = computed(() => {
return sessions.value.filter(isSummaryStale).length
})
// 消息数量太少的会话数(无摘要但无法生成)
const tooFewMessagesCount = computed(() => {
return sessions.value.filter((s) => {
if (s.summary) return false
if (hasFreshSummary(s)) return false
const checkResult = canGenerateMap.value[s.id]
return checkResult?.canGenerate === false
}).length
@@ -199,7 +212,7 @@ async function fetchSessions() {
// 批量检查会话是否可以生成摘要
async function checkCanGenerate() {
const noSummaryIds = sessions.value.filter((s) => !s.summary).map((s) => s.id)
const noSummaryIds = sessions.value.filter((s) => !hasFreshSummary(s)).map((s) => s.id)
if (noSummaryIds.length === 0) return
isChecking.value = true
@@ -291,6 +304,7 @@ async function startGenerate() {
const idx = sessions.value.findIndex((s) => s.id === session.id)
if (idx !== -1) {
sessions.value[idx].summary = result.summary || ''
sessions.value[idx].summaryMessageCount = sessions.value[idx].messageCount
}
} else if (result.error && isTooFewMessagesError(result.error)) {
// 消息数量太少:标记为跳过
@@ -441,7 +455,7 @@ function close() {
<p>
{{ t('records.batchSummary.found', '找到') }} {{ sessions.length }}
{{ t('records.batchSummary.sessionsUnit', '个会话') }}
<template v-if="existingSummaryCount > 0 || tooFewMessagesCount > 0">
<template v-if="existingSummaryCount > 0 || staleSummaryCount > 0 || tooFewMessagesCount > 0">
<span class="text-gray-500">
(
<template v-if="existingSummaryCount > 0">
@@ -449,7 +463,15 @@ function close() {
{{ existingSummaryCount }} {{ t('records.batchSummary.hasSummary', '个已有摘要') }}
</span>
</template>
<template v-if="existingSummaryCount > 0 && tooFewMessagesCount > 0">,</template>
<template v-if="existingSummaryCount > 0 && (staleSummaryCount > 0 || tooFewMessagesCount > 0)">
,
</template>
<template v-if="staleSummaryCount > 0">
<span class="text-amber-600 dark:text-amber-400">
{{ staleSummaryCount }} {{ t('records.batchSummary.needsUpdate', '个待更新') }}
</span>
</template>
<template v-if="staleSummaryCount > 0 && tooFewMessagesCount > 0">,</template>
<template v-if="tooFewMessagesCount > 0">
<span class="text-gray-400">
{{ tooFewMessagesCount }} {{ t('records.batchSummary.tooFewMessages', '个消息太少') }}
@@ -190,6 +190,10 @@ function formatTime(ts: number): string {
return date.toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit' })
}
function isSummaryStale(session: ChatSessionItem): boolean {
return Boolean(session.summary) && session.summaryMessageCount !== session.messageCount
}
// 获取日期键
function getDateKey(ts: number): string {
const date = new Date(ts * 1000)
@@ -264,7 +268,11 @@ async function generateSummary(session: ChatSessionItem, event: Event) {
if (result.success && result.summary) {
const index = allSessions.value.findIndex((s) => s.id === session.id)
if (index !== -1) {
const updatedSession = { ...allSessions.value[index], summary: result.summary }
const updatedSession = {
...allSessions.value[index],
summary: result.summary,
summaryMessageCount: allSessions.value[index].messageCount,
}
allSessions.value[index] = updatedSession
emit('summary-updated', updatedSession)
}
@@ -432,6 +440,12 @@ watch(
<span class="ml-1">
{{ (flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary }}
</span>
<span
v-if="isSummaryStale((flatList[virtualItem.index] as { session: ChatSessionItem }).session)"
class="ml-1 font-medium text-amber-500 dark:text-amber-400"
>
{{ t('records.timeline.summaryStale') }}
</span>
</span>
<template #content>
<div class="max-w-sm whitespace-pre-wrap text-sm leading-relaxed font-normal">
@@ -445,7 +459,10 @@ watch(
</div>
<div
v-if="!(flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary"
v-if="
!(flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary ||
isSummaryStale((flatList[virtualItem.index] as { session: ChatSessionItem }).session)
"
class="mt-0.5 flex min-w-0 items-center"
>
<!-- 无摘要且消息数>=3:显示生成按钮 -->
@@ -462,7 +479,13 @@ watch(
class="h-3 w-3 shrink-0 animate-spin"
/>
<UIcon v-else name="i-heroicons-sparkles" class="h-3 w-3 shrink-0" />
<span class="truncate">{{ t('records.timeline.generateSummary') }}</span>
<span class="truncate">
{{
isSummaryStale((flatList[virtualItem.index] as { session: ChatSessionItem }).session)
? t('records.timeline.updateSummary')
: t('records.timeline.generateSummary')
}}
</span>
</span>
<!-- 消息数<3:显示提示 -->
+3
View File
@@ -32,6 +32,8 @@
"timeline": "Summaries",
"noSessions": "No summaries",
"generateSummary": "Summarize",
"updateSummary": "Update summary",
"summaryStale": "· Update needed",
"tooFewMessages": "Too few"
},
"summaryFailed": "Summary generation failed",
@@ -54,6 +56,7 @@
"custom": "Custom",
"found": "Found",
"hasSummary": "already summarized",
"needsUpdate": "need updates",
"tooFewMessages": "too few messages",
"pending": "Pending:",
"unit": "",
+3
View File
@@ -32,6 +32,8 @@
"timeline": "要約",
"noSessions": "要約がありません",
"generateSummary": "要約を作成",
"updateSummary": "要約を更新",
"summaryStale": "· 更新が必要",
"tooFewMessages": "メッセージが少なすぎます"
},
"summaryFailed": "要約の生成に失敗しました",
@@ -54,6 +56,7 @@
"custom": "カスタム",
"found": "見つかった",
"hasSummary": "件は要約済み",
"needsUpdate": "件は更新が必要",
"tooFewMessages": "件はメッセージ不足",
"pending": "未生成:",
"unit": "件",
+3
View File
@@ -32,6 +32,8 @@
"timeline": "摘要",
"noSessions": "暂无摘要",
"generateSummary": "生成摘要",
"updateSummary": "更新摘要",
"summaryStale": "· 待更新",
"tooFewMessages": "消息太少"
},
"summaryFailed": "摘要生成失败",
@@ -54,6 +56,7 @@
"custom": "自定义",
"found": "找到",
"hasSummary": "个已有摘要",
"needsUpdate": "个待更新",
"tooFewMessages": "个消息太少",
"pending": "待生成:",
"unit": "个",
+3
View File
@@ -32,6 +32,8 @@
"timeline": "摘要",
"noSessions": "暫無摘要",
"generateSummary": "產生摘要",
"updateSummary": "更新摘要",
"summaryStale": "· 待更新",
"tooFewMessages": "訊息太少"
},
"summaryFailed": "摘要產生失敗",
@@ -54,6 +56,7 @@
"custom": "自訂",
"found": "找到",
"hasSummary": "個已有摘要",
"needsUpdate": "個待更新",
"tooFewMessages": "個訊息太少",
"pending": "待產生:",
"unit": "個",
+6 -3
View File
@@ -88,7 +88,8 @@ export class FetchSessionIndexAdapter implements SessionIndexAdapter {
async getSessions(sessionId: string): Promise<ChatSessionItem[]> {
return getDataAdapter().pluginQuery<ChatSessionItem>(
sessionId,
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary, ${FIRST_MESSAGE_ID_SUBQUERY}
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary,
summary_message_count as summaryMessageCount, ${FIRST_MESSAGE_ID_SUBQUERY}
FROM segment ORDER BY start_ts ASC`,
[]
)
@@ -97,7 +98,8 @@ export class FetchSessionIndexAdapter implements SessionIndexAdapter {
async getByTimeRange(sessionId: string, startTs: number, endTs: number): Promise<ChatSessionItem[]> {
return getDataAdapter().pluginQuery<ChatSessionItem>(
sessionId,
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary, ${FIRST_MESSAGE_ID_SUBQUERY}
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary,
summary_message_count as summaryMessageCount, ${FIRST_MESSAGE_ID_SUBQUERY}
FROM segment WHERE start_ts >= ? AND end_ts <= ? ORDER BY start_ts ASC`,
[startTs, endTs]
)
@@ -106,7 +108,8 @@ export class FetchSessionIndexAdapter implements SessionIndexAdapter {
async getRecent(sessionId: string, limit: number): Promise<ChatSessionItem[]> {
return getDataAdapter().pluginQuery<ChatSessionItem>(
sessionId,
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary, ${FIRST_MESSAGE_ID_SUBQUERY}
`SELECT id, start_ts as startTs, end_ts as endTs, message_count as messageCount, summary,
summary_message_count as summaryMessageCount, ${FIRST_MESSAGE_ID_SUBQUERY}
FROM segment ORDER BY start_ts DESC LIMIT ?`,
[limit]
)
+1
View File
@@ -18,6 +18,7 @@ export interface ChatSessionItem {
messageCount: number
firstMessageId: number
summary?: string | null
summaryMessageCount?: number | null
}
export interface SummaryResult {