feat(chat): support ai photo viewing
This commit is contained in:
@@ -26,6 +26,14 @@ export type ChatEvent =
|
||||
| { type: "ChatSendMessage"; content: string }
|
||||
| { type: "ChatSendImage"; imageBase64: string }
|
||||
| { type: "ChatLoadMoreHistory" }
|
||||
| { type: "ChatImageReceived"; imageUrl: string }
|
||||
| {
|
||||
type: "ChatPaywallStatusReceived";
|
||||
paywallTriggered: boolean;
|
||||
showUpgrade: boolean;
|
||||
imageType: string | null;
|
||||
imageUrl: string | null;
|
||||
}
|
||||
// WebSocket / AI 推句(由 chat 机器内部 actor 派回 —— "机器自驱动")
|
||||
| {
|
||||
type: "ChatAISentenceReceived";
|
||||
|
||||
@@ -151,6 +151,18 @@ export const chatWebSocketActor = fromCallback<ChatEvent, { token: string }>(
|
||||
done: p.done,
|
||||
});
|
||||
};
|
||||
ws.onImage = (imageUrl) => {
|
||||
sendBack({ type: "ChatImageReceived", imageUrl });
|
||||
};
|
||||
ws.onPaywallStatus = (p) => {
|
||||
sendBack({
|
||||
type: "ChatPaywallStatusReceived",
|
||||
paywallTriggered: p.paywallTriggered,
|
||||
showUpgrade: p.showUpgrade,
|
||||
imageType: p.imageType,
|
||||
imageUrl: p.imageUrl,
|
||||
});
|
||||
};
|
||||
ws.onError = (msg) => {
|
||||
log.error("[chat-machine] WS error", { errorMessage: msg });
|
||||
sendBack({ type: "ChatWebSocketError", errorMessage: msg });
|
||||
|
||||
@@ -56,12 +56,14 @@ export function localMessagesToUi(
|
||||
content: string;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
imageUrl?: string | null;
|
||||
}>,
|
||||
): UiMessage[] {
|
||||
return records.map((m) => ({
|
||||
content: m.content,
|
||||
isFromAI: m.role === "assistant",
|
||||
date: messageDateFromCreatedAt(m.createdAt),
|
||||
...(m.imageUrl ? { imageUrl: m.imageUrl } : {}),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ export function sendResponseToUiMessage(response: ChatSendResponse): UiMessage {
|
||||
content: response.reply,
|
||||
isFromAI: true,
|
||||
date: todayString(new Date(response.timestamp)),
|
||||
...(response.imageUrl ? { imageUrl: response.imageUrl } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,6 +123,16 @@ export function applyHttpSendOutput(
|
||||
};
|
||||
}
|
||||
|
||||
if (response.showUpgrade) {
|
||||
return {
|
||||
messages: [...context.messages, reply],
|
||||
isReplyingAI: false,
|
||||
paywallTriggered: true,
|
||||
paywallReason: "photo_paywall",
|
||||
paywallDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
messages: [...context.messages, reply],
|
||||
isReplyingAI: false,
|
||||
|
||||
@@ -261,6 +261,54 @@ export const chatMachine = setup({
|
||||
return { messages, isReplyingAI: false };
|
||||
}),
|
||||
|
||||
appendAIImage: assign(({ context, event }) => {
|
||||
if (event.type !== "ChatImageReceived") return {};
|
||||
const messages = [...context.messages];
|
||||
const last = messages[messages.length - 1];
|
||||
|
||||
if (last?.isFromAI && !last.imageUrl) {
|
||||
messages[messages.length - 1] = {
|
||||
...last,
|
||||
imageUrl: event.imageUrl,
|
||||
};
|
||||
} else {
|
||||
messages.push({
|
||||
content: "",
|
||||
isFromAI: true,
|
||||
date: todayString(),
|
||||
imageUrl: event.imageUrl,
|
||||
});
|
||||
}
|
||||
|
||||
log.debug("[chat-machine] appendAIImage", {
|
||||
messagesCount: messages.length,
|
||||
hasMergedIntoLastMessage: Boolean(last?.isFromAI && !last.imageUrl),
|
||||
});
|
||||
|
||||
return {
|
||||
messages,
|
||||
paywallTriggered: false,
|
||||
paywallReason: null,
|
||||
paywallDetail: null,
|
||||
};
|
||||
}),
|
||||
|
||||
applyPaywallStatus: assign(({ event }) => {
|
||||
if (event.type !== "ChatPaywallStatusReceived") return {};
|
||||
if (!event.showUpgrade) {
|
||||
return {
|
||||
paywallTriggered: false,
|
||||
paywallReason: null,
|
||||
paywallDetail: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
paywallTriggered: true,
|
||||
paywallReason: "photo_paywall",
|
||||
paywallDetail: null,
|
||||
};
|
||||
}),
|
||||
|
||||
incrementQuotaExceeded: assign(({ context }) => ({
|
||||
quotaExceededTrigger: context.quotaExceededTrigger + 1,
|
||||
})),
|
||||
@@ -546,6 +594,8 @@ export const chatMachine = setup({
|
||||
actions: "startUserSession",
|
||||
},
|
||||
ChatAISentenceReceived: { actions: "appendOrUpdateAISentence" },
|
||||
ChatImageReceived: { actions: "appendAIImage" },
|
||||
ChatPaywallStatusReceived: { actions: "applyPaywallStatus" },
|
||||
ChatWebSocketError: { actions: "appendSocketErrorMessage" },
|
||||
ChatWebSocketConnected: { actions: "setWsConnected" },
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface ChatState {
|
||||
guestTotalQuota: number;
|
||||
quotaExceededTrigger: number;
|
||||
paywallTriggered: boolean;
|
||||
paywallReason: "daily_limit" | null;
|
||||
paywallReason: "daily_limit" | "photo_paywall" | null;
|
||||
paywallDetail: { usedToday: number; limit: number } | null;
|
||||
isLoadingMore: boolean;
|
||||
hasMore: boolean;
|
||||
|
||||
Reference in New Issue
Block a user