2026-03-23 13:40:48 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 会话列表数据源抽象:游标、常规页码、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<ConversationListPage>;
|
|
|
|
|
|
/** 至少成功 loadNext 过一次且仍有下一页时为 true */
|
|
|
|
|
|
readonly hasMore: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 04:07:48 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 远端会话列表(offset 与 cursor 共用实现)
|
|
|
|
|
|
* 均通过 messageService.fetchRemoteConversationListPage 拉取并落库,行为一致。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class NetworkRemoteConversationListPagedSource implements IConversationListPagedSource {
|
2026-03-23 13:40:48 +08:00
|
|
|
|
private nextPage = 1;
|
|
|
|
|
|
private nextCursor: string | null = null;
|
|
|
|
|
|
private hasMoreAfterLastLoad = false;
|
|
|
|
|
|
private loadedOnce = false;
|
|
|
|
|
|
private readonly pageSize: number;
|
2026-03-25 04:07:48 +08:00
|
|
|
|
private readonly mode: RemoteConversationListSourceKind;
|
2026-03-23 13:40:48 +08:00
|
|
|
|
|
2026-03-25 04:07:48 +08:00
|
|
|
|
constructor(
|
|
|
|
|
|
mode: RemoteConversationListSourceKind,
|
|
|
|
|
|
pageSize: number = CONVERSATION_LIST_PAGE_SIZE
|
|
|
|
|
|
) {
|
|
|
|
|
|
this.mode = mode;
|
2026-03-23 13:40:48 +08:00
|
|
|
|
this.pageSize = pageSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
restart(): void {
|
2026-03-25 04:07:48 +08:00
|
|
|
|
this.nextPage = 1;
|
2026-03-23 13:40:48 +08:00
|
|
|
|
this.nextCursor = null;
|
|
|
|
|
|
this.hasMoreAfterLastLoad = false;
|
|
|
|
|
|
this.loadedOnce = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get hasMore(): boolean {
|
|
|
|
|
|
return this.loadedOnce && this.hasMoreAfterLastLoad;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadNext(): Promise<ConversationListPage> {
|
2026-03-25 04:07:48 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-03-23 13:40:48 +08:00
|
|
|
|
this.loadedOnce = true;
|
2026-03-25 04:07:48 +08:00
|
|
|
|
return { items: result.items, hasMore: result.hasMore };
|
2026-03-23 13:40:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** SQLite 会话列表缓存(单批,无后续页) */
|
|
|
|
|
|
export class SqliteConversationListPagedSource implements IConversationListPagedSource {
|
|
|
|
|
|
private consumed = false;
|
|
|
|
|
|
|
|
|
|
|
|
restart(): void {
|
|
|
|
|
|
this.consumed = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
get hasMore(): boolean {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async loadNext(): Promise<ConversationListPage> {
|
|
|
|
|
|
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 };
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 04:07:48 +08:00
|
|
|
|
/** 远端会话列表分页策略 */
|
2026-03-23 13:40:48 +08:00
|
|
|
|
export type RemoteConversationListSourceKind = 'cursor' | 'offset';
|
|
|
|
|
|
|
|
|
|
|
|
export function createRemoteConversationListSource(
|
|
|
|
|
|
kind: RemoteConversationListSourceKind,
|
|
|
|
|
|
pageSize: number = CONVERSATION_LIST_PAGE_SIZE
|
|
|
|
|
|
): IConversationListPagedSource {
|
2026-03-25 04:07:48 +08:00
|
|
|
|
return new NetworkRemoteConversationListPagedSource(kind, pageSize);
|
2026-03-23 13:40:48 +08:00
|
|
|
|
}
|