import type { CacheRemoteChatMediaInput, ChatMediaLookupInput, ChatRequestOptions, ChatSendOptions, IChatRepository, UnlockPrivateMessageInput, UnlockedPrivateMessageLocalPatch, } from "@/data/repositories/interfaces"; import type { ChatHistoryResponse, ChatPreviewsResponse, ChatMessage, ChatSendResponse, UnlockHistoryResponse, UnlockPrivateResponse, } from "@/data/schemas/chat"; import { chatApi } from "@/data/services/api"; import { LocalChatMediaStorage, LocalChatStorage, type LocalChatMediaRow, } from "@/data/storage/chat"; import type { Result } from "@/utils/result"; import { ChatLocalMessageStore } from "./chat_local_message_store"; import { ChatMediaCacheCoordinator } from "./chat_media_cache_coordinator"; import { ChatRemoteDataSource } from "./chat_remote_data_source"; import { createLazySingleton } from "./lazy_singleton"; export class ChatRepository implements IChatRepository { constructor( private readonly remote: ChatRemoteDataSource, private readonly localMessages: ChatLocalMessageStore, private readonly mediaCache: ChatMediaCacheCoordinator, ) {} // ============ 远程操作 ============ /** 发送一条消息。 */ async sendMessage( characterId: string, message: string, options?: ChatSendOptions, ): Promise> { return this.remote.sendMessage(characterId, message, options); } /** 获取聊天历史,分页参数默认 limit=50, offset=0。 */ async getHistory( characterId: string, limit = 50, offset = 0, options?: ChatRequestOptions, ): Promise> { return this.remote.getHistory(characterId, limit, offset, options); } async getPreviews( options?: ChatRequestOptions, ): Promise> { return this.remote.getPreviews(options); } /** 解锁单条历史付费 / 私密消息。 */ async unlockPrivateMessage( input: UnlockPrivateMessageInput, options?: ChatRequestOptions, ): Promise> { return this.remote.unlockPrivateMessage(input, options); } /** 一键解锁历史锁定消息。 */ async unlockHistory( characterId: string, options?: ChatRequestOptions, ): Promise> { return this.remote.unlockHistory(characterId, options); } /** 把本地缓存中的单条锁定消息标记为已解锁。 */ async markPrivateMessageUnlockedInLocal( messageId: string, patch?: UnlockedPrivateMessageLocalPatch, cacheIdentity?: string, ): Promise> { return this.localMessages.markMessageUnlocked( messageId, patch, cacheIdentity, ); } // ============ 本地(Dexie)操作 ============ /** 把一条消息写入本地存储。 */ async saveMessageToLocal( message: ChatMessage, cacheIdentity?: string, ): Promise> { return this.localMessages.saveMessage(message, cacheIdentity); } /** * 批量覆盖写入:先清空本地存储,再写入新列表。 * 用于网络历史同步后的整批刷新。 */ async saveMessagesToLocal( messages: readonly ChatMessage[], cacheIdentity?: string, ): Promise> { return this.localMessages.saveMessages(messages, cacheIdentity); } /** 读取所有本地消息,按 dbId 升序。 */ async getLocalMessages( cacheIdentity?: string, ): Promise> { return this.localMessages.getMessages(cacheIdentity); } /** 清空本地消息。 */ async clearLocalMessages(cacheIdentity?: string): Promise> { return this.localMessages.clearMessages(cacheIdentity); } /** * 获取本地消息数量。 * Dexie 查询是异步操作,因此始终返回 Promise。 */ async getLocalMessageCount(cacheIdentity?: string): Promise> { return this.localMessages.getMessageCount(cacheIdentity); } // ============ 本地媒体缓存 ============ async getCachedMedia( input: ChatMediaLookupInput, ): Promise> { return this.mediaCache.getCachedMedia(input); } async cacheRemoteMedia( input: CacheRemoteChatMediaInput, cacheIdentity?: string, ): Promise> { return this.mediaCache.cacheRemoteMedia(input, cacheIdentity); } async prefetchMediaForMessages( messages: readonly ChatMessage[], characterId: string, cacheIdentity?: string, ): Promise> { return this.mediaCache.prefetchMediaForMessages( messages, characterId, cacheIdentity, ); } async prefetchMediaForSendResponse( response: ChatSendResponse, characterId: string, cacheIdentity?: string, ): Promise> { return this.mediaCache.prefetchMediaForSendResponse( response, characterId, cacheIdentity, ); } } /** 全局懒单例。 */ export const getChatRepository = createLazySingleton( () => new ChatRepository( new ChatRemoteDataSource(chatApi), new ChatLocalMessageStore(LocalChatStorage.getInstance()), new ChatMediaCacheCoordinator(LocalChatMediaStorage.getInstance()), ), );