/** * 会话列表数据源抽象:游标、常规页码、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; } /** 远端常规分页:GET /conversations?page=&page_size=(始终请求网络,避免走 getConversations 的本地短路) */ export class NetworkOffsetConversationListPagedSource implements IConversationListPagedSource { private nextPage = 1; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; constructor(pageSize: number = CONVERSATION_LIST_PAGE_SIZE) { this.pageSize = pageSize; } restart(): void { this.nextPage = 1; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const response = await messageService.getConversations(this.nextPage, this.pageSize, true); const items = response.list || []; const totalPages = Math.max(0, response.total_pages ?? 0); const currentPage = response.page ?? this.nextPage; this.hasMoreAfterLastLoad = totalPages > 0 && currentPage < totalPages; this.nextPage = currentPage + 1; this.loadedOnce = true; return { items, hasMore: this.hasMoreAfterLastLoad }; } } /** 远端游标接口 */ export class NetworkCursorConversationListPagedSource implements IConversationListPagedSource { private nextCursor: string | null = null; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; constructor(pageSize: number = CONVERSATION_LIST_PAGE_SIZE) { this.pageSize = pageSize; } restart(): void { this.nextCursor = null; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const response = await messageService.getConversationsCursor( this.nextCursor == null ? { page_size: this.pageSize } : { cursor: this.nextCursor, page_size: this.pageSize } ); const items = response.list || []; this.nextCursor = response.next_cursor ?? null; this.hasMoreAfterLastLoad = Boolean(response.has_more && response.next_cursor != null); this.loadedOnce = true; return { items, hasMore: this.hasMoreAfterLastLoad }; } } /** 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 }; } } } /** 未注入 remote 时,按类型创建默认远端源 */ export type RemoteConversationListSourceKind = 'cursor' | 'offset'; export function createRemoteConversationListSource( kind: RemoteConversationListSourceKind, pageSize: number = CONVERSATION_LIST_PAGE_SIZE ): IConversationListPagedSource { return kind === 'offset' ? new NetworkOffsetConversationListPagedSource(pageSize) : new NetworkCursorConversationListPagedSource(pageSize); }