Files
Jyotisha/frontend/src/lib/starter-greeting.ts
T
Jesse_ChenandClaude Opus 5.5 dc45fde613
Independent Staging Quality Gate / validate (push) Successful in 12m47s
Independent Staging Quality Gate / publish (push) Successful in 3m47s
feat(home): warm older-sister greeting voice; daily line says why, plainly
Late night no longer remarks on being awake (产品 2026-10-01); every daypart,
weekday and returning line reworded in the same caring voice. Daily line:
each house sentence names where the Moon is in plain words, then one concrete
sentence; no stacked hedges. Sign-fallback moods rewritten the same way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-01 19:34:16 +08:00

90 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
type GreetingPeriod = "morning" | "noon" | "afternoon" | "evening" | "late-night";
/**
* One line, two to five words, no follow-up question. Since 2026-10-01 the voice
* is a warm older sister's (知心姐姐): it greets and cares, never comments on the
* user (no 夜猫子 / 还没睡呀).
*
* The homepage opening used to be a salutation plus a question ("这么晚还醒着,
* {名字}。直接说说最在意的问题吧。") — an office voice that answered for the user
* before they had said anything. Asking is the composer placeholder's job now;
* the greeting only greets. No metaphors (星 / 月 / 陪), no emoji, no period.
*/
const NAME_TOKEN = "{名字}";
const periodGreetings: Record<GreetingPeriod, readonly string[]> = {
morning: ["早安", "早安,{名字}", "早呀,今天慢慢来"],
noon: ["中午好,记得吃饭", "午安,{名字}"],
afternoon: ["下午好,歇口气吧", "下午好,{名字}"],
evening: ["今天辛苦了", "晚上好,{名字}", "晚上好,歇一歇"],
// Late night never remarks on being awake (产品 2026-10-01: 「你管别人睡不睡」).
"late-night": ["这会儿很安静", "夜里好,{名字}", "慢慢说,{名字}"],
};
/** Keyed by `Date#getDay()`. Days with nothing worth saying stay out of the pool. */
const weekdayGreetings: Readonly<Record<number, readonly string[]>> = {
0: ["周日,慢一点"],
1: ["周一了,别太累"],
5: ["周五了,松口气"],
6: ["周末好好歇歇"],
};
/** Only reachable once the account has at least one session behind it. */
const returningGreetings: readonly string[] = [
"又见面了,{名字}",
"你来啦,{名字}",
"欢迎回来",
"最近还好吗,{名字}?",
];
function greetingPeriod(hour: number): GreetingPeriod {
if (hour >= 5 && hour < 11) return "morning";
if (hour >= 11 && hour < 14) return "noon";
if (hour >= 14 && hour < 18) return "afternoon";
if (hour >= 18 && hour < 23) return "evening";
return "late-night";
}
/**
* Every line this moment can produce, in pool order: daypart, weekday, returning.
* A blank name drops the lines that would print an empty one; the daypart pool
* always keeps a nameless line, so the result is never empty.
*/
function starterGreetingPool(name: string, now: Date, hasSessions: boolean): readonly string[] {
const templates = [
...periodGreetings[greetingPeriod(now.getHours())],
...(weekdayGreetings[now.getDay()] ?? []),
...(hasSessions ? returningGreetings : []),
];
return name ? templates : templates.filter((template) => !template.includes(NAME_TOKEN));
}
export type StartGreetingParts = {
readonly text: string;
};
export function createStartGreetingParts(
name: string,
now = new Date(),
variantSelection = Math.random(),
hasSessions = false,
): StartGreetingParts {
const displayName = name.trim();
const pool = starterGreetingPool(displayName, now, hasSessions);
const index = Math.min(Math.max(Math.floor(variantSelection * pool.length), 0), pool.length - 1);
return { text: pool[index]!.replaceAll(NAME_TOKEN, displayName) };
}
export function createStartGreeting(
name: string,
now = new Date(),
variantSelection = Math.random(),
hasSessions = false,
): string {
return createStartGreetingParts(name, now, variantSelection, hasSessions).text;
}
export function createOnboardingFallbackGreeting(name: string): string {
return `欢迎,${name.trim()}`;
}