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

194 lines
5.7 KiB
TypeScript

"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<Result<ChatSendResponse>> {
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<Result<ChatHistoryResponse>> {
return Result.wrap(() => this.api.getHistory(limit, offset));
}
/** 解锁私密消息。 */
async unlockPrivateMessage(
messageId: string,
): Promise<Result<UnlockPrivateResponse>> {
return Result.wrap(() =>
this.api.unlockPrivateMessage(UnlockPrivateRequest.from({ messageId })),
);
}
/** 一键解锁历史锁定消息。 */
async unlockHistory(): Promise<Result<UnlockHistoryResponse>> {
return Result.wrap(() => this.api.unlockHistory());
}
/** 把本地缓存中的私密消息标记为已解锁。 */
async markPrivateMessageUnlockedInLocal(
messageId: string,
content: string,
): Promise<Result<void>> {
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<Result<void>> {
return this._mapLocalStorageResult(
this.localStorage.saveMessage(this._chatToLocal(message)),
);
}
/**
* 批量覆盖写入:先清空本地存储,再写入新列表。
* 保留 Dart 端的「先清后写」语义(用于同步场景的整批刷新)。
*/
async saveMessagesToLocal(
messages: readonly ChatMessage[],
): Promise<Result<void>> {
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<Result<ChatMessage[]>> {
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<Result<void>> {
return this._mapLocalStorageResult(this.localStorage.clearAll());
}
/**
* 获取本地消息数量。
* 替代 Dart 的同步 `int get localMessageCount`——Dexie 异步 API 决定必须 async。
*/
async getLocalMessageCount(): Promise<Result<number>> {
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<T>` 透传:仓库与 storage 现在共享同一全局
* `Result<T>`(来自 `@/utils/result`),不需要任何形状转换。
* 该方法保留仅为未来万一又出现差异化时方便快速插入适配。
*/
private async _mapLocalStorageResult<T>(
promise: Promise<Result<T>>,
): Promise<Result<T>> {
return await promise;
}
}
/** 全局单例。 */
export const chatRepository = new ChatRepository(
chatApi,
LocalChatStorage.getInstance(),
);