refactor(chat): simplify paid message lock state

This commit is contained in:
2026-07-13 15:20:07 +08:00
parent fd631168c8
commit 4682b4bf3f
17 changed files with 86 additions and 190 deletions
@@ -3,7 +3,7 @@
* ChatInsufficientCreditsBanner 积分不足横幅 * ChatInsufficientCreditsBanner 积分不足横幅
* *
* 何时显示: * 何时显示:
* - 后端返回 cannotSendReason="insufficient_credits" * - 后端返回 canSendMessage=false
* *
* 视觉规格(与设计稿对齐): * 视觉规格(与设计稿对齐):
* - 粉→品红渐变背景(与 splash 渐变一致) * - 粉→品红渐变背景(与 splash 渐变一致)
-8
View File
@@ -24,11 +24,7 @@ export interface SentencePayload {
export interface PaywallStatusPayload { export interface PaywallStatusPayload {
locked: boolean; locked: boolean;
showContent: boolean;
showUpgrade: boolean;
reason: string | null; reason: string | null;
hint: string | null;
detail: Record<string, unknown> | null;
} }
export class ChatWebSocket { export class ChatWebSocket {
@@ -178,11 +174,7 @@ export class ChatWebSocket {
const lockDetail = payload.data?.lockDetail; const lockDetail = payload.data?.lockDetail;
this.onPaywallStatus?.({ this.onPaywallStatus?.({
locked: lockDetail?.locked ?? false, locked: lockDetail?.locked ?? false,
showContent: lockDetail?.showContent ?? true,
showUpgrade: lockDetail?.showUpgrade ?? false,
reason: lockDetail?.reason ?? null, reason: lockDetail?.reason ?? null,
hint: lockDetail?.hint ?? null,
detail: lockDetail?.detail ?? null,
}); });
break; break;
} }
@@ -15,11 +15,7 @@ describe("UnlockPrivateResponse", () => {
shortfallCredits: 0, shortfallCredits: 0,
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
}); });
@@ -49,8 +45,7 @@ describe("UnlockPrivateResponse", () => {
}, },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true, reason: null,
showUpgrade: false,
}, },
}); });
@@ -73,13 +68,7 @@ describe("UnlockPrivateResponse", () => {
shortfallCredits: 5, shortfallCredits: 5,
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false, reason: "voice_message",
showUpgrade: true,
reason: "insufficient_balance",
hint: "Insufficient credits.",
detail: {
messageId: "msg_001",
},
}, },
}); });
@@ -89,7 +78,10 @@ describe("UnlockPrivateResponse", () => {
expect(response.creditsCharged).toBe(0); expect(response.creditsCharged).toBe(0);
expect(response.requiredCredits).toBe(10); expect(response.requiredCredits).toBe(10);
expect(response.shortfallCredits).toBe(5); expect(response.shortfallCredits).toBe(5);
expect(response.lockDetail.showUpgrade).toBe(true); expect(response.lockDetail).toEqual({
locked: true,
reason: "voice_message",
});
}); });
it("defaults nullable content to an empty string", () => { it("defaults nullable content to an empty string", () => {
@@ -100,11 +92,7 @@ describe("UnlockPrivateResponse", () => {
reason: "insufficient_balance", reason: "insufficient_balance",
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false, reason: "private_message",
showUpgrade: true,
reason: "insufficient_balance",
hint: null,
detail: null,
}, },
}); });
@@ -123,11 +111,7 @@ describe("UnlockPrivateResponse", () => {
shortfallCredits: null, shortfallCredits: null,
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false, reason: "image_paywall",
showUpgrade: true,
reason: "insufficient_balance",
hint: null,
detail: null,
}, },
}); });
@@ -147,11 +131,26 @@ describe("UnlockPrivateResponse", () => {
expect(response.lockDetail).toEqual({ expect(response.lockDetail).toEqual({
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null, });
detail: null, });
it("strips legacy lock detail fields while preserving lock state and type", () => {
const response = UnlockPrivateResponse.fromJson({
unlocked: false,
lockDetail: {
locked: true,
reason: "private_message",
showContent: false,
showUpgrade: true,
hint: "Legacy hint",
detail: { messageId: "legacy-message" },
},
});
expect(response.lockDetail).toEqual({
locked: true,
reason: "private_message",
}); });
}); });
}); });
@@ -17,7 +17,6 @@ export class ChatSendResponse {
declare readonly image: ChatImageData; declare readonly image: ChatImageData;
declare readonly lockDetail: ChatLockDetailData; declare readonly lockDetail: ChatLockDetailData;
declare readonly canSendMessage: boolean; declare readonly canSendMessage: boolean;
// declare readonly cannotSendReason: string | null;
declare readonly creditBalance: number; declare readonly creditBalance: number;
declare readonly creditsCharged: number; declare readonly creditsCharged: number;
declare readonly requiredCredits: number; declare readonly requiredCredits: number;
@@ -23,11 +23,7 @@ function makeMessage(
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
...overrides, ...overrides,
}); });
@@ -45,11 +41,7 @@ function makeResponse(
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
...overrides, ...overrides,
}); });
@@ -28,13 +28,8 @@ export class ChatLocalMessageStore {
audioUrl: normalizeUnlockedAudioUrl(patch.audioUrl, message.audioUrl), audioUrl: normalizeUnlockedAudioUrl(patch.audioUrl, message.audioUrl),
image: patch.image ?? message.image, image: patch.image ?? message.image,
lockDetail: patch.lockDetail ?? { lockDetail: patch.lockDetail ?? {
...message.lockDetail,
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
}); });
}); });
-10
View File
@@ -5,21 +5,15 @@ import { z } from "zod";
import { import {
booleanOrFalse, booleanOrFalse,
booleanOrTrue,
schemaOr, schemaOr,
stringOrNull, stringOrNull,
unknownRecordOrNull,
} from "../nullable-defaults"; } from "../nullable-defaults";
const CHAT_IMAGE_DEFAULTS = { type: null, url: null } as const; const CHAT_IMAGE_DEFAULTS = { type: null, url: null } as const;
const CHAT_LOCK_DETAIL_DEFAULTS = { const CHAT_LOCK_DETAIL_DEFAULTS = {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
} as const; } as const;
export const ChatImageSchema = schemaOr( export const ChatImageSchema = schemaOr(
@@ -33,11 +27,7 @@ export const ChatImageSchema = schemaOr(
export const ChatLockDetailSchema = schemaOr( export const ChatLockDetailSchema = schemaOr(
z.object({ z.object({
locked: booleanOrFalse, locked: booleanOrFalse,
showContent: booleanOrTrue,
showUpgrade: booleanOrFalse,
reason: stringOrNull, reason: stringOrNull,
hint: stringOrNull,
detail: unknownRecordOrNull,
}), }),
CHAT_LOCK_DETAIL_DEFAULTS, CHAT_LOCK_DETAIL_DEFAULTS,
); );
@@ -9,7 +9,6 @@ import {
numberOrLazy, numberOrLazy,
numberOrZero, numberOrZero,
stringOrEmpty, stringOrEmpty,
stringOrNull,
} from "../nullable-defaults"; } from "../nullable-defaults";
import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads"; import { ChatImageSchema, ChatLockDetailSchema } from "./chat_payloads";
@@ -23,7 +22,6 @@ export const ChatSendResponseSchema = z.object({
image: ChatImageSchema, image: ChatImageSchema,
lockDetail: ChatLockDetailSchema, lockDetail: ChatLockDetailSchema,
canSendMessage: booleanOrTrue, canSendMessage: booleanOrTrue,
cannotSendReason: stringOrNull,
creditBalance: numberOrZero, creditBalance: numberOrZero,
creditsCharged: numberOrZero, creditsCharged: numberOrZero,
requiredCredits: numberOrZero, requiredCredits: numberOrZero,
@@ -21,11 +21,7 @@ function makeResponse(
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
...overrides, ...overrides,
}); });
@@ -40,7 +36,6 @@ function makeChatState(overrides: Partial<ChatState> = {}): ChatState {
upgradePromptVisible: false, upgradePromptVisible: false,
upgradeReason: null, upgradeReason: null,
canSendMessage: true, canSendMessage: true,
cannotSendReason: null,
creditBalance: 0, creditBalance: 0,
creditsCharged: 0, creditsCharged: 0,
requiredCredits: 0, requiredCredits: 0,
@@ -73,11 +68,7 @@ describe("sendResponseToUiMessage", () => {
audioUrl: "", audioUrl: "",
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false,
showUpgrade: true,
reason: "voice_message", reason: "voice_message",
hint: "这语气好撩呀,让人有点心跳加速呢…",
detail: { messageId: "msg-1" },
}, },
}), }),
); );
@@ -87,9 +78,7 @@ describe("sendResponseToUiMessage", () => {
expect(message.locked).toBe(true); expect(message.locked).toBe(true);
expect(message.lockReason).toBe("voice_message"); expect(message.lockReason).toBe("voice_message");
expect(message.lockedPrivate).toBeUndefined(); expect(message.lockedPrivate).toBeUndefined();
expect(message.privateMessageHint).toBe( expect(message.privateMessageHint).toBeUndefined();
"这语气好撩呀,让人有点心跳加速呢…",
);
}); });
it("maps locked private text messages as private messages", () => { it("maps locked private text messages as private messages", () => {
@@ -97,11 +86,7 @@ describe("sendResponseToUiMessage", () => {
makeResponse({ makeResponse({
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false,
showUpgrade: true,
reason: "private_message", reason: "private_message",
hint: "Elio has a private message for you.",
detail: { messageId: "msg-1" },
}, },
}), }),
); );
@@ -111,9 +96,7 @@ describe("sendResponseToUiMessage", () => {
expect(message.lockReason).toBe("private_message"); expect(message.lockReason).toBe("private_message");
expect(message.isPrivate).toBe(true); expect(message.isPrivate).toBe(true);
expect(message.lockedPrivate).toBe(true); expect(message.lockedPrivate).toBe(true);
expect(message.privateMessageHint).toBe( expect(message.privateMessageHint).toBeUndefined();
"Elio has a private message for you.",
);
}); });
it("keeps paywalled image messages visible but locked", () => { it("keeps paywalled image messages visible but locked", () => {
@@ -125,11 +108,7 @@ describe("sendResponseToUiMessage", () => {
}, },
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false,
showUpgrade: true,
reason: "image", reason: "image",
hint: "Activate VIP to view the full photo.",
detail: { imageId: "image-1" },
}, },
}), }),
); );
@@ -141,6 +120,25 @@ describe("sendResponseToUiMessage", () => {
expect(message.lockReason).toBe("image"); expect(message.lockReason).toBe("image");
expect(message.lockedPrivate).toBeUndefined(); expect(message.lockedPrivate).toBeUndefined();
}); });
it("does not mark a message as paywalled when its lock reason is unlocked", () => {
const message = sendResponseToUiMessage(
makeResponse({
image: {
type: "jpg",
url: "https://example.com/unlocked-photo.jpg",
},
lockDetail: {
locked: false,
reason: "image_paywall",
},
}),
);
expect(message.locked).toBe(false);
expect(message.lockReason).toBe("image_paywall");
expect(message.imagePaywalled).toBeUndefined();
});
}); });
describe("applyHttpSendOutput", () => { describe("applyHttpSendOutput", () => {
@@ -160,22 +158,13 @@ describe("applyHttpSendOutput", () => {
reply: "", reply: "",
messageId: "", messageId: "",
canSendMessage: false, canSendMessage: false,
cannotSendReason: "insufficient_credits",
creditBalance: 0, creditBalance: 0,
creditsCharged: 0, creditsCharged: 0,
requiredCredits: 2, requiredCredits: 2,
shortfallCredits: 2, shortfallCredits: 2,
lockDetail: { lockDetail: {
locked: true, locked: false,
showContent: false, reason: null,
showUpgrade: true,
reason: "insufficient_credits",
hint: "Insufficient credits.",
detail: {
requiredCredits: 2,
currentCredits: 0,
shortfallCredits: 2,
},
}, },
}), }),
reply: null, reply: null,
@@ -186,7 +175,6 @@ describe("applyHttpSendOutput", () => {
expect(nextState.upgradePromptVisible).toBe(true); expect(nextState.upgradePromptVisible).toBe(true);
expect(nextState.upgradeReason).toBe("insufficient_credits"); expect(nextState.upgradeReason).toBe("insufficient_credits");
expect(nextState.canSendMessage).toBe(false); expect(nextState.canSendMessage).toBe(false);
expect(nextState.cannotSendReason).toBe("insufficient_credits");
expect(nextState.requiredCredits).toBe(2); expect(nextState.requiredCredits).toBe(2);
expect(nextState.shortfallCredits).toBe(2); expect(nextState.shortfallCredits).toBe(2);
}); });
@@ -205,7 +193,6 @@ describe("applyHttpSendOutput", () => {
makeResponse({ makeResponse({
reply: "This one went through, but you need credits next.", reply: "This one went through, but you need credits next.",
canSendMessage: false, canSendMessage: false,
cannotSendReason: "insufficient_credits",
creditBalance: 0, creditBalance: 0,
requiredCredits: 2, requiredCredits: 2,
shortfallCredits: 2, shortfallCredits: 2,
@@ -216,7 +203,6 @@ describe("applyHttpSendOutput", () => {
response: makeResponse({ response: makeResponse({
reply: "This one went through, but you need credits next.", reply: "This one went through, but you need credits next.",
canSendMessage: false, canSendMessage: false,
cannotSendReason: "insufficient_credits",
creditBalance: 0, creditBalance: 0,
requiredCredits: 2, requiredCredits: 2,
shortfallCredits: 2, shortfallCredits: 2,
@@ -248,11 +234,7 @@ describe("localMessagesToUi", () => {
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false,
showUpgrade: true,
reason: "voice_message", reason: "voice_message",
hint: "A locked voice message is waiting.",
detail: { messageId: "voice-1" },
}, },
}, },
]); ]);
@@ -262,7 +244,7 @@ describe("localMessagesToUi", () => {
expect(message.locked).toBe(true); expect(message.locked).toBe(true);
expect(message.lockReason).toBe("voice_message"); expect(message.lockReason).toBe("voice_message");
expect(message.lockedPrivate).toBeUndefined(); expect(message.lockedPrivate).toBeUndefined();
expect(message.privateMessageHint).toBe("A locked voice message is waiting."); expect(message.privateMessageHint).toBeUndefined();
}); });
it("hides text content for image history messages", () => { it("hides text content for image history messages", () => {
@@ -280,11 +262,7 @@ describe("localMessagesToUi", () => {
}, },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
}, },
]); ]);
@@ -48,11 +48,7 @@ function makeChatSendResponse(): ChatSendResponse {
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
}); });
} }
@@ -71,11 +67,7 @@ function makeUnlockPrivateResponse(
shortfallCredits: 0, shortfallCredits: 0,
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true,
showUpgrade: false,
reason: null, reason: null,
hint: null,
detail: null,
}, },
...overrides, ...overrides,
}); });
@@ -870,11 +862,7 @@ describe("chatMachine transitions", () => {
shortfallCredits: 7, shortfallCredits: 7,
lockDetail: { lockDetail: {
locked: true, locked: true,
showContent: false, reason: "voice_message",
showUpgrade: true,
reason: "insufficient_balance",
hint: "Insufficient credits.",
detail: { messageId: "msg-voice-locked" },
}, },
}), }),
}, },
@@ -938,19 +926,10 @@ describe("chatMachine transitions", () => {
timestamp: Date.now(), timestamp: Date.now(),
image: { type: null, url: null }, image: { type: null, url: null },
lockDetail: { lockDetail: {
locked: true, locked: false,
showContent: false, reason: null,
showUpgrade: true,
reason: "insufficient_credits",
hint: "Insufficient credits.",
detail: {
requiredCredits: 2,
currentCredits: 0,
shortfallCredits: 2,
},
}, },
canSendMessage: false, canSendMessage: false,
cannotSendReason: "insufficient_credits",
creditBalance: 0, creditBalance: 0,
requiredCredits: 2, requiredCredits: 2,
shortfallCredits: 2, shortfallCredits: 2,
@@ -970,7 +949,6 @@ describe("chatMachine transitions", () => {
expect(actor.getSnapshot().context.upgradePromptVisible).toBe(false); expect(actor.getSnapshot().context.upgradePromptVisible).toBe(false);
expect(actor.getSnapshot().context.upgradeReason).toBeNull(); expect(actor.getSnapshot().context.upgradeReason).toBeNull();
expect(actor.getSnapshot().context.canSendMessage).toBe(true); expect(actor.getSnapshot().context.canSendMessage).toBe(true);
expect(actor.getSnapshot().context.cannotSendReason).toBeNull();
actor.stop(); actor.stop();
}); });
@@ -59,8 +59,7 @@ describe("chat promotion", () => {
}, },
lockDetail: { lockDetail: {
locked: false, locked: false,
showContent: true, reason: null,
showUpgrade: false,
}, },
}), }),
}); });
+1 -1
View File
@@ -23,7 +23,7 @@
* *
* 发送能力: * 发送能力:
* - 前端不再处理本地消息额度;游客 / 注册用户均以后端响应为准。 * - 前端不再处理本地消息额度;游客 / 注册用户均以后端响应为准。
* - 后端返回 `cannotSendReason="insufficient_credits"` 时展示充值引导。 * - 后端返回 `canSendMessage=false` 时展示积分不足充值引导。
* *
*/ */
+9 -26
View File
@@ -1,4 +1,5 @@
import { type ChatSendResponse, type UiMessage } from "@/data/dto/chat"; import { type ChatSendResponse, type UiMessage } from "@/data/dto/chat";
import type { ChatLockDetailData } from "@/data/schemas/chat";
import { todayString } from "@/utils"; import { todayString } from "@/utils";
/** /**
@@ -14,14 +15,7 @@ export function localMessagesToUi(
createdAt: string; createdAt: string;
audioUrl?: string | null; audioUrl?: string | null;
image?: { type: string | null; url: string | null }; image?: { type: string | null; url: string | null };
lockDetail?: { lockDetail?: ChatLockDetailData;
locked: boolean;
showContent: boolean;
showUpgrade: boolean;
reason: string | null;
hint: string | null;
detail: Record<string, unknown> | null;
};
}>, }>,
): UiMessage[] { ): UiMessage[] {
return records.map((m) => ({ return records.map((m) => ({
@@ -72,30 +66,19 @@ function getAiMessageDisplayContent(input: {
} }
function deriveUiLockFields( function deriveUiLockFields(
lockDetail: lockDetail: ChatLockDetailData | undefined,
| {
locked: boolean;
showContent: boolean;
showUpgrade: boolean;
reason: string | null;
hint: string | null;
}
| undefined,
imageUrl?: string | null, imageUrl?: string | null,
): Partial<UiMessage> { ): Partial<UiMessage> {
const lockReason = lockDetail?.reason ?? null; const lockReason = lockDetail?.reason ?? null;
const isPrivateMessage = lockReason === "private_message"; const isLocked = lockDetail?.locked === true;
const isVoiceMessage = lockReason === "voice_message"; const isPrivateMessage = isLocked && lockReason === "private_message";
const isLockedImage =
isLocked && (lockReason === "image_paywall" || lockReason === "image");
return { return {
...(imageUrl ? { imageUrl } : {}), ...(imageUrl ? { imageUrl } : {}),
...(imageUrl && lockDetail?.showUpgrade ? { imagePaywalled: true } : {}), ...(imageUrl && isLockedImage ? { imagePaywalled: true } : {}),
...(lockDetail ? { locked: lockDetail.locked, lockReason } : {}), ...(lockDetail ? { locked: lockDetail.locked, lockReason } : {}),
...(isPrivateMessage ? { isPrivate: true } : {}), ...(isPrivateMessage ? { isPrivate: true } : {}),
...(isPrivateMessage && lockDetail?.locked ...(isPrivateMessage ? { lockedPrivate: true } : {}),
? { lockedPrivate: true }
: {}),
...((isPrivateMessage || isVoiceMessage) && lockDetail?.hint
? { privateMessageHint: lockDetail.hint }
: {}),
}; };
} }
+8 -5
View File
@@ -119,7 +119,6 @@ export const clearUpgradePromptAction = chatAssign(() => ({
upgradePromptVisible: false, upgradePromptVisible: false,
upgradeReason: null, upgradeReason: null,
canSendMessage: true, canSendMessage: true,
cannotSendReason: null,
requiredCredits: 0, requiredCredits: 0,
shortfallCredits: 0, shortfallCredits: 0,
})); }));
@@ -173,7 +172,6 @@ async function sendMessageViaHttp(content: string): Promise<{
void chatRepo.prefetchMediaForSendResponse(result.data); void chatRepo.prefetchMediaForSendResponse(result.data);
const isInsufficientCredits = const isInsufficientCredits =
result.data.canSendMessage === false && result.data.canSendMessage === false &&
result.data.cannotSendReason === "insufficient_credits" &&
!hasRenderableSendResponse(result.data); !hasRenderableSendResponse(result.data);
return { return {
response: result.data, response: result.data,
@@ -183,12 +181,17 @@ async function sendMessageViaHttp(content: string): Promise<{
} }
function hasRenderableSendResponse(response: ChatSendResponse): boolean { function hasRenderableSendResponse(response: ChatSendResponse): boolean {
const isLockedPaidMessage =
response.lockDetail.locked &&
(response.lockDetail.reason === "private_message" ||
response.lockDetail.reason === "voice_message" ||
response.lockDetail.reason === "image_paywall" ||
response.lockDetail.reason === "image");
return ( return (
response.reply.trim().length > 0 || response.reply.trim().length > 0 ||
response.audioUrl.trim().length > 0 || response.audioUrl.trim().length > 0 ||
Boolean(response.image.url) || Boolean(response.image.url) ||
response.lockDetail.reason === "private_message" || isLockedPaidMessage
response.lockDetail.reason === "voice_message" ||
response.lockDetail.reason === "image_paywall"
); );
} }
+1 -18
View File
@@ -34,9 +34,7 @@ export function applyHttpSendOutput(
): Partial<ChatState> { ): Partial<ChatState> {
const { response, reply } = output; const { response, reply } = output;
const isInsufficientCredits = const isInsufficientCredits = response.canSendMessage === false;
response.canSendMessage === false &&
response.cannotSendReason === "insufficient_credits";
const upgradeReason: ChatUpgradeReason | null = isInsufficientCredits const upgradeReason: ChatUpgradeReason | null = isInsufficientCredits
? "insufficient_credits" ? "insufficient_credits"
: null; : null;
@@ -65,19 +63,6 @@ export function applyHttpSendOutput(
}; };
} }
if (
response.lockDetail.showUpgrade &&
response.lockDetail.reason === "private_message"
) {
return {
messages: [...context.messages, reply],
...finishPendingReply(context),
upgradePromptVisible: false,
upgradeReason: null,
...sendCapabilityState,
};
}
return { return {
messages: [...context.messages, reply], messages: [...context.messages, reply],
...finishPendingReply(context), ...finishPendingReply(context),
@@ -92,7 +77,6 @@ function getSendCapabilityState(
): Pick< ): Pick<
ChatState, ChatState,
| "canSendMessage" | "canSendMessage"
| "cannotSendReason"
| "creditBalance" | "creditBalance"
| "creditsCharged" | "creditsCharged"
| "requiredCredits" | "requiredCredits"
@@ -100,7 +84,6 @@ function getSendCapabilityState(
> { > {
return { return {
canSendMessage: response.canSendMessage, canSendMessage: response.canSendMessage,
cannotSendReason: response.cannotSendReason,
creditBalance: response.creditBalance, creditBalance: response.creditBalance,
creditsCharged: response.creditsCharged, creditsCharged: response.creditsCharged,
requiredCredits: response.requiredCredits, requiredCredits: response.requiredCredits,
-2
View File
@@ -27,7 +27,6 @@ export interface ChatState {
upgradePromptVisible: boolean; upgradePromptVisible: boolean;
upgradeReason: ChatUpgradeReason | null; upgradeReason: ChatUpgradeReason | null;
canSendMessage: boolean; canSendMessage: boolean;
cannotSendReason: string | null;
creditBalance: number; creditBalance: number;
creditsCharged: number; creditsCharged: number;
requiredCredits: number; requiredCredits: number;
@@ -63,7 +62,6 @@ export const initialState: ChatState = {
upgradePromptVisible: false, upgradePromptVisible: false,
upgradeReason: null, upgradeReason: null,
canSendMessage: true, canSendMessage: true,
cannotSendReason: null,
creditBalance: 0, creditBalance: 0,
creditsCharged: 0, creditsCharged: 0,
requiredCredits: 0, requiredCredits: 0,
+11 -2
View File
@@ -51,8 +51,7 @@ export function applySingleUnlockOutput(
locked: output.response.lockDetail.locked, locked: output.response.lockDetail.locked,
lockReason: output.response.lockDetail.reason, lockReason: output.response.lockDetail.reason,
imagePaywalled: resolvedImageUrl imagePaywalled: resolvedImageUrl
? output.response.lockDetail.locked && ? isLockedImage(output.response.lockDetail)
output.response.lockDetail.showUpgrade
: undefined, : undefined,
lockedPrivate: false, lockedPrivate: false,
privateMessageHint: null, privateMessageHint: null,
@@ -60,6 +59,16 @@ export function applySingleUnlockOutput(
}); });
} }
function isLockedImage(lockDetail: {
locked: boolean;
reason: string | null;
}): boolean {
return (
lockDetail.locked &&
(lockDetail.reason === "image_paywall" || lockDetail.reason === "image")
);
}
function getUnlockedAudioUrl( function getUnlockedAudioUrl(
message: UiMessage, message: UiMessage,
response: UnlockPrivateResponse, response: UnlockPrivateResponse,