"use client"; import { ChatApi, chatApi } from "@/data/services/api"; import { ChatHistoryResponse, ChatMessage, ChatSendResponse, SendMessageRequest, UnlockHistoryResponse, UnlockPrivateRequest, UnlockPrivateResponse, } from "@/data/dto/chat"; import { Result } from "@/utils"; import type { IChatRepository } from "@/data/repositories/interfaces"; import { LocalChatStorage, LocalMessage } from "@/data/storage/chat"; export class ChatRepository implements IChatRepository { constructor( private readonly api: ChatApi, private readonly localStorage: LocalChatStorage, ) {} // ============ 远程操作 ============ /** 发送一条消息。 */ async sendMessage( message: string, options?: { image?: string; useWebSocket?: boolean }, ): Promise> { return Result.wrap(async () => { const request = SendMessageRequest.from({ message, image: options?.image ?? "", useWebSocket: options?.useWebSocket ?? false, }); return await this.api.sendMessage(request); }); } /** 获取聊天历史,分页参数默认 limit=50, offset=0。 */ async getHistory( limit = 50, offset = 0, ): Promise> { return Result.wrap(() => this.api.getHistory(limit, offset)); } /** 解锁私密消息。 */ async unlockPrivateMessage( messageId: string, ): Promise> { return Result.wrap(() => this.api.unlockPrivateMessage(UnlockPrivateRequest.from({ messageId })), ); } /** 一键解锁历史锁定消息。 */ async unlockHistory(): Promise> { return Result.wrap(() => this.api.unlockHistory()); } /** 把本地缓存中的私密消息标记为已解锁。 */ async markPrivateMessageUnlockedInLocal( messageId: string, content: string, ): Promise> { return Result.wrap(async () => { const localResult = await this.getLocalMessages(); if (Result.isErr(localResult)) { throw localResult.error; } let changed = false; const updatedMessages = localResult.data.map((message) => { if (message.id !== messageId) return message; changed = true; return ChatMessage.from({ ...message.toJson(), content, lockDetail: { ...message.lockDetail, locked: false, showContent: true, showUpgrade: false, hint: null, }, }); }); if (!changed) return; const saveResult = await this.saveMessagesToLocal(updatedMessages); if (Result.isErr(saveResult)) { throw saveResult.error; } }); } // ============ 本地(Dexie)操作 ============ /** 把一条消息写入本地存储。 */ async saveMessageToLocal(message: ChatMessage): Promise> { return this._mapLocalStorageResult( this.localStorage.saveMessage(this._chatToLocal(message)), ); } /** * 批量覆盖写入:先清空本地存储,再写入新列表。 * 保留 Dart 端的「先清后写」语义(用于同步场景的整批刷新)。 */ async saveMessagesToLocal( messages: readonly ChatMessage[], ): Promise> { const cleared = await this.localStorage.clearAll(); if (!cleared.success) { return Result.err(cleared.error); } return this._mapLocalStorageResult( this.localStorage.saveMessages(messages.map((m) => this._chatToLocal(m))), ); } /** 读取所有本地消息,按 dbId 升序。 */ async getLocalMessages(): Promise> { const result = await this.localStorage.getAllMessages(); if (!result.success) { return Result.err(result.error); } return Result.ok(result.data.map((lm) => this._localToChat(lm))); } /** 清空本地消息。 */ async clearLocalMessages(): Promise> { return this._mapLocalStorageResult(this.localStorage.clearAll()); } /** * 获取本地消息数量。 * 替代 Dart 的同步 `int get localMessageCount`——Dexie 异步 API 决定必须 async。 */ async getLocalMessageCount(): Promise> { return this._mapLocalStorageResult(this.localStorage.getMessageCount()); } // ============ 私有助手 ============ /** ChatMessage → LocalMessage:丢弃 wire 字段,注入 storage 层的 sessionId=""。 */ private _chatToLocal(message: ChatMessage): LocalMessage { return LocalMessage.from({ id: message.id, role: message.role, type: message.type, content: message.content, createdAt: message.createdAt, audioUrl: message.audioUrl, image: message.image, lockDetail: message.lockDetail, sessionId: "", }); } /** LocalMessage → ChatMessage:丢弃 storage 层的 sessionId。 */ private _localToChat(local: LocalMessage): ChatMessage { return ChatMessage.from({ id: local.id, role: local.role, type: local.type, content: local.content, createdAt: local.createdAt, audioUrl: local.audioUrl, image: local.image, lockDetail: local.lockDetail, }); } /** * 把 storage 层 `Result` 透传:仓库与 storage 现在共享同一全局 * `Result`(来自 `@/utils/result`),不需要任何形状转换。 * 该方法保留仅为未来万一又出现差异化时方便快速插入适配。 */ private async _mapLocalStorageResult( promise: Promise>, ): Promise> { return await promise; } } /** 全局单例。 */ export const chatRepository = new ChatRepository( chatApi, LocalChatStorage.getInstance(), );