import { fromPromise } from "xstate"; import { loadChatRepository } from "@/data/repositories/chat_repository_loader"; import { resolveChatConversationKey } from "@/data/repositories/chat_cache_identity"; import { Logger } from "@/utils/logger"; import { Result } from "@/utils/result"; import { isAbortError } from "@/utils/abort"; import { readAndSyncHistory } from "../../chat-history-sync"; import { type UnlockMessageRequest, type UnlockMessageOutput, } from "../../helper/unlock"; const log = new Logger("StoresChatChatUnlockFlow"); type UiMessage = import("@/stores/chat/ui-message").UiMessage; export interface UnlockHistoryOutput { unlocked: boolean; reason: string; shortfallCredits: number; messages: UiMessage[]; } export const unlockHistoryActor = fromPromise< UnlockHistoryOutput, { characterId: string } >(async ({ input, signal }) => { const chatRepo = await loadChatRepository(); const unlockResult = await chatRepo.unlockHistory(input.characterId, { signal, }); if (Result.isErr(unlockResult)) { if (isAbortError(unlockResult.error)) throw unlockResult.error; log.error("[chat-machine] unlockHistoryActor failed", { error: unlockResult.error, }); throw unlockResult.error; } signal.throwIfAborted(); const history = await readAndSyncHistory(input.characterId, signal); return { unlocked: unlockResult.data.unlocked, reason: unlockResult.data.reason, shortfallCredits: unlockResult.data.shortfallCredits, messages: history.messages, }; }); export const unlockMessageActor = fromPromise< UnlockMessageOutput, UnlockMessageRequest & { characterId: string } >(async ({ input, signal }) => { const chatRepo = await loadChatRepository(); const cacheIdentityResult = await resolveChatConversationKey( input.characterId, ); const unlockResult = await chatRepo.unlockPrivateMessage( { characterId: input.characterId, ...(input.messageId ? { messageId: input.messageId } : {}), ...(input.lockType ? { lockType: input.lockType } : {}), ...(input.clientLockId ? { clientLockId: input.clientLockId } : {}), }, { signal }, ); if (Result.isErr(unlockResult)) { if (isAbortError(unlockResult.error)) throw unlockResult.error; log.error("[chat-machine] unlockMessageActor failed", { messageId: input.messageId, clientLockId: input.clientLockId, error: unlockResult.error, }); throw unlockResult.error; } signal.throwIfAborted(); if ( unlockResult.data.unlocked && input.messageId && !input.clientLockId && Result.isOk(cacheIdentityResult) ) { const markResult = await chatRepo.markPrivateMessageUnlockedInLocal( input.messageId, { content: unlockResult.data.content, audioUrl: unlockResult.data.audioUrl, ...(unlockResult.data.image.url ? { image: unlockResult.data.image } : {}), lockDetail: { locked: false, reason: null }, }, cacheIdentityResult.data, ); if (Result.isErr(markResult)) { log.warn("[chat-machine] mark unlocked local message failed", { messageId: input.messageId, error: markResult.error, }); } } return { displayMessageId: input.displayMessageId, request: input, response: unlockResult.data, }; });