Files
cozsweet-frontend-nextjs/src/data/repositories/chat_repository.ts
T

184 lines
5.2 KiB
TypeScript

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<Result<ChatSendResponse>> {
return this.remote.sendMessage(characterId, message, options);
}
/** 获取聊天历史,分页参数默认 limit=50, offset=0。 */
async getHistory(
characterId: string,
limit = 50,
offset = 0,
options?: ChatRequestOptions,
): Promise<Result<ChatHistoryResponse>> {
return this.remote.getHistory(characterId, limit, offset, options);
}
async getPreviews(
options?: ChatRequestOptions,
): Promise<Result<ChatPreviewsResponse>> {
return this.remote.getPreviews(options);
}
/** 解锁单条历史付费 / 私密消息。 */
async unlockPrivateMessage(
input: UnlockPrivateMessageInput,
options?: ChatRequestOptions,
): Promise<Result<UnlockPrivateResponse>> {
return this.remote.unlockPrivateMessage(input, options);
}
/** 一键解锁历史锁定消息。 */
async unlockHistory(
characterId: string,
options?: ChatRequestOptions,
): Promise<Result<UnlockHistoryResponse>> {
return this.remote.unlockHistory(characterId, options);
}
/** 把本地缓存中的单条锁定消息标记为已解锁。 */
async markPrivateMessageUnlockedInLocal(
messageId: string,
patch?: UnlockedPrivateMessageLocalPatch,
cacheIdentity?: string,
): Promise<Result<void>> {
return this.localMessages.markMessageUnlocked(
messageId,
patch,
cacheIdentity,
);
}
// ============ 本地(Dexie)操作 ============
/** 把一条消息写入本地存储。 */
async saveMessageToLocal(
message: ChatMessage,
cacheIdentity?: string,
): Promise<Result<void>> {
return this.localMessages.saveMessage(message, cacheIdentity);
}
/**
* 批量覆盖写入:先清空本地存储,再写入新列表。
* 保留 Dart 端的「先清后写」语义(用于同步场景的整批刷新)。
*/
async saveMessagesToLocal(
messages: readonly ChatMessage[],
cacheIdentity?: string,
): Promise<Result<void>> {
return this.localMessages.saveMessages(messages, cacheIdentity);
}
/** 读取所有本地消息,按 dbId 升序。 */
async getLocalMessages(
cacheIdentity?: string,
): Promise<Result<readonly ChatMessage[]>> {
return this.localMessages.getMessages(cacheIdentity);
}
/** 清空本地消息。 */
async clearLocalMessages(cacheIdentity?: string): Promise<Result<void>> {
return this.localMessages.clearMessages(cacheIdentity);
}
/**
* 获取本地消息数量。
* 替代 Dart 的同步 `int get localMessageCount`——Dexie 异步 API 决定必须 async。
*/
async getLocalMessageCount(cacheIdentity?: string): Promise<Result<number>> {
return this.localMessages.getMessageCount(cacheIdentity);
}
// ============ 本地媒体缓存 ============
async getCachedMedia(
input: ChatMediaLookupInput,
): Promise<Result<LocalChatMediaRow | null>> {
return this.mediaCache.getCachedMedia(input);
}
async cacheRemoteMedia(
input: CacheRemoteChatMediaInput,
cacheIdentity?: string,
): Promise<Result<LocalChatMediaRow>> {
return this.mediaCache.cacheRemoteMedia(input, cacheIdentity);
}
async prefetchMediaForMessages(
messages: readonly ChatMessage[],
characterId: string,
cacheIdentity?: string,
): Promise<Result<void>> {
return this.mediaCache.prefetchMediaForMessages(
messages,
characterId,
cacheIdentity,
);
}
async prefetchMediaForSendResponse(
response: ChatSendResponse,
characterId: string,
cacheIdentity?: string,
): Promise<Result<void>> {
return this.mediaCache.prefetchMediaForSendResponse(
response,
characterId,
cacheIdentity,
);
}
}
/** 全局懒单例。 */
export const getChatRepository = createLazySingleton<IChatRepository>(
() =>
new ChatRepository(
new ChatRemoteDataSource(chatApi),
new ChatLocalMessageStore(LocalChatStorage.getInstance()),
new ChatMediaCacheCoordinator(LocalChatMediaStorage.getInstance()),
),
);