fix(chat): avoid overwriting user messages on unlock

This commit is contained in:
2026-07-01 10:22:11 +08:00
parent 5d9ed7e027
commit 9c45c7eb69
2 changed files with 77 additions and 1 deletions
@@ -529,6 +529,71 @@ describe("chatMachine transitions", () => {
actor.stop(); actor.stop();
}); });
it("does not overwrite a user message when it shares the private message id", async () => {
const actor = createActor(
createTestChatMachine({
historyMessages: [
{
id: "msg-shared-id",
content: "User original question",
isFromAI: false,
date: "2026-06-29",
},
{
id: "msg-shared-id",
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "private_message",
lockedPrivate: true,
privateMessageHint: "A private message is waiting.",
},
],
unlockMessageOutput: {
messageId: "msg-shared-id",
response: makeUnlockPrivateResponse({
content: "This is the unlocked private message.",
}),
},
}),
).start();
actor.send({ type: "ChatUserLogin", token: "token" });
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
actor.send({
type: "ChatUnlockMessageRequested",
messageId: "msg-shared-id",
kind: "private",
});
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
expect(actor.getSnapshot().context.messages).toMatchObject([
{
id: "msg-shared-id",
content: "User original question",
isFromAI: false,
},
{
id: "msg-shared-id",
content: "This is the unlocked private message.",
isFromAI: true,
locked: false,
lockReason: null,
lockedPrivate: false,
privateMessageHint: null,
},
]);
actor.stop();
});
it("requests payment when single message unlock lacks credits", async () => { it("requests payment when single message unlock lacks credits", async () => {
const actor = createActor( const actor = createActor(
createTestChatMachine({ createTestChatMachine({
+12 -1
View File
@@ -16,7 +16,7 @@ export function applySingleUnlockOutput(
if (!output.response.unlocked) return [...messages]; if (!output.response.unlocked) return [...messages];
return messages.map((message) => { return messages.map((message) => {
if (message.id !== output.messageId) return message; if (!shouldApplySingleUnlock(message, output.messageId)) return message;
const nextContent = const nextContent =
output.response.content.length > 0 output.response.content.length > 0
@@ -38,6 +38,17 @@ export function applySingleUnlockOutput(
}); });
} }
function shouldApplySingleUnlock(message: UiMessage, messageId: string): boolean {
if (message.id !== messageId) return false;
if (!message.isFromAI) return false;
if (message.locked !== true) return false;
return (
message.imagePaywalled === true ||
message.lockReason === "private_message" ||
message.lockReason === "voice_message"
);
}
export function shouldAutoUnlockHistory(messages: readonly UiMessage[]): boolean { export function shouldAutoUnlockHistory(messages: readonly UiMessage[]): boolean {
return countLockedHistoryMessages(messages) === 1; return countLockedHistoryMessages(messages) === 1;
} }