feat(chat): prepend greeting message when chat history is empty
On first chat open (or when both local and network return empty), the chat screen would render completely blank, which feels cold and broken. Prepend a single AI persona greeting as the first message so the first impression is warm. Trigger: - Final returned messages array is empty - Either network succeeds with empty result, or network fails and local is also empty Design notes: - Greeting is NOT persisted to local/network — pure UI-layer fallback. Once the user sends a real message, network has content and the greeting no longer appears. - If the user closes the app without ever sending a message and reopens, the greeting reappears (intentional: "warm first frame on each cold start"). - Rendered as an AI bubble (isFromAI: true) so the visual style matches the AI persona.
This commit is contained in:
@@ -32,7 +32,7 @@ import { Result } from "@/utils/result";
|
||||
// Constants
|
||||
// ============================================================
|
||||
/** 翻历史每页大小(仅 `loadMoreHistoryActor` 用) */
|
||||
export const PAGE_SIZE = 20;
|
||||
export const PAGE_SIZE = 50;
|
||||
|
||||
// ============================================================
|
||||
// Repository injection
|
||||
@@ -137,6 +137,25 @@ export async function readAndSyncHistory(): Promise<{
|
||||
/** 真实从 network 读到的消息数(调试用) */
|
||||
networkCount: number;
|
||||
}> {
|
||||
/**
|
||||
* 空历史首屏欢迎语(AI persona 首条消息)。
|
||||
* 触发条件:最终返回的 messages 为空(network 空 + local 空 / 或 network 失败且 local 空)。
|
||||
*
|
||||
* 不主动持久化到 local —— 这是纯 UI 层"首屏不空"的兜底:
|
||||
* - 用户一旦发了消息,network 上有真实消息 → 后续 readAndSyncHistory
|
||||
* 返回的 messages 非空,不会再触发本条
|
||||
* - 用户从未发过消息就关掉 app 再开,仍然没 network 消息 → 会再次显示本条
|
||||
* ("每次冷启动给个温暖的起点" —— 若要"只显示一次",需额外存 local)
|
||||
*/
|
||||
const greetingMessage: UiMessage = {
|
||||
content:
|
||||
"You're here! Facebook is so restrictive, " +
|
||||
"there were things I couldn't say there. " +
|
||||
"Finally I can relax. How was your day out?",
|
||||
isFromAI: true,
|
||||
date: formatDate(),
|
||||
};
|
||||
|
||||
// 1. 读 local
|
||||
const localResult = await chatRepo.getLocalMessages();
|
||||
const localMessages =
|
||||
@@ -155,10 +174,19 @@ export async function readAndSyncHistory(): Promise<{
|
||||
"[chat-machine] loadHistory NETWORK FAILED",
|
||||
networkResult.success ? null : (networkResult as { error: unknown }).error,
|
||||
);
|
||||
|
||||
// 空历史 → fallback 也走 welcome(不让屏幕空空如也)
|
||||
const fallbackMessages =
|
||||
localMessages.length === 0 ? [greetingMessage] : localMessages;
|
||||
if (fallbackMessages[0] === greetingMessage) {
|
||||
console.log(
|
||||
"[chat-machine] loadHistory EMPTY → prepend greeting (network-failed path)",
|
||||
);
|
||||
}
|
||||
return {
|
||||
messages: localMessages, // 退而求其次:返 local(不然屏幕空)
|
||||
messages: fallbackMessages,
|
||||
hasMore: false,
|
||||
newOffset: 0,
|
||||
newOffset: fallbackMessages.length,
|
||||
localOverwritten: false,
|
||||
localCount: localMessages.length,
|
||||
networkCount: 0,
|
||||
@@ -169,17 +197,24 @@ export async function readAndSyncHistory(): Promise<{
|
||||
count: networkUi.length,
|
||||
});
|
||||
|
||||
// 3. 用 network 覆盖 local("再用网络数据覆盖本地数据"这句的字面实现)
|
||||
// 3. 用 network 覆盖 local
|
||||
const saveResult = await chatRepo.saveMessagesToLocal(networkResult.data.messages);
|
||||
const localOverwritten = Result.isOk(saveResult);
|
||||
console.log("[chat-machine] loadHistory SAVE TO LOCAL DONE", {
|
||||
localOverwritten,
|
||||
});
|
||||
|
||||
// 空历史 → prepend welcome(network 权威为空 = 用户从未发过消息)
|
||||
const finalMessages =
|
||||
networkUi.length === 0 ? [greetingMessage] : networkUi;
|
||||
if (finalMessages[0] === greetingMessage) {
|
||||
console.log("[chat-machine] loadHistory EMPTY → prepend greeting");
|
||||
}
|
||||
|
||||
return {
|
||||
messages: networkUi, // authorititative
|
||||
hasMore: networkUi.length >= PAGE_SIZE,
|
||||
newOffset: networkUi.length,
|
||||
messages: finalMessages, // authoritative(empty 时塞 greeting)
|
||||
hasMore: finalMessages.length >= PAGE_SIZE,
|
||||
newOffset: finalMessages.length,
|
||||
localOverwritten,
|
||||
localCount: localMessages.length,
|
||||
networkCount: networkUi.length,
|
||||
|
||||
Reference in New Issue
Block a user