/** * 会话列表数据源抽象:游标、常规页码、SQLite 等实现同一契约,MessageManager 只依赖接口。 */ import { ConversationResponse } from '../types/dto'; import { messageService } from '../services/messageService'; import { getConversationListCache } from '../services/database'; export const CONVERSATION_LIST_PAGE_SIZE = 20; /** 单次拉取结果(一页或一批) */ export interface ConversationListPage { items: ConversationResponse[]; /** 在当前源上是否还能再 loadNext 一页 */ hasMore: boolean; } /** * 分页式会话列表源:restart 后开始新序列;首次 loadNext 为第一页,之后为后续页。 */ export interface IConversationListPagedSource { restart(): void; loadNext(): Promise; /** 至少成功 loadNext 过一次且仍有下一页时为 true */ readonly hasMore: boolean; } /** * 远端会话列表(offset 与 cursor 共用实现) * 均通过 messageService.fetchRemoteConversationListPage 拉取并落库,行为一致。 */ export class NetworkRemoteConversationListPagedSource implements IConversationListPagedSource { private nextPage = 1; private nextCursor: string | null = null; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; private readonly mode: RemoteConversationListSourceKind; constructor( mode: RemoteConversationListSourceKind, pageSize: number = CONVERSATION_LIST_PAGE_SIZE ) { this.mode = mode; this.pageSize = pageSize; } restart(): void { this.nextPage = 1; this.nextCursor = null; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const result = await messageService.fetchRemoteConversationListPage({ mode: this.mode, pageSize: this.pageSize, ...(this.mode === 'offset' ? { page: this.nextPage } : { cursor: this.nextCursor }), }); this.hasMoreAfterLastLoad = result.hasMore; if (this.mode === 'offset') { this.nextPage = result.nextPage ?? this.nextPage + 1; } else { this.nextCursor = result.nextCursor ?? null; } this.loadedOnce = true; return { items: result.items, hasMore: result.hasMore }; } } /** SQLite 会话列表缓存(单批,无后续页) */ export class SqliteConversationListPagedSource implements IConversationListPagedSource { private consumed = false; restart(): void { this.consumed = false; } get hasMore(): boolean { return false; } async loadNext(): Promise { if (this.consumed) { return { items: [], hasMore: false }; } this.consumed = true; try { const items = await getConversationListCache(); return { items, hasMore: false }; } catch (e) { console.warn('[SqliteConversationListPagedSource] 读取会话列表缓存失败:', e); return { items: [], hasMore: false }; } } } /** 远端会话列表分页策略 */ export type RemoteConversationListSourceKind = 'cursor' | 'offset'; export function createRemoteConversationListSource( kind: RemoteConversationListSourceKind, pageSize: number = CONVERSATION_LIST_PAGE_SIZE ): IConversationListPagedSource { return new NetworkRemoteConversationListPagedSource(kind, pageSize); }