Files
Jyotisha/frontend/src/lib/starter-greeting.ts
T
Jesse_ChenandClaude Fable 5.1 ff0427cf08
Independent Staging Quality Gate / validate (push) Failing after 6m23s
Independent Staging Quality Gate / publish (push) Skipped
feat(home): 首页开场语改成一行问候,占位符改「想聊什么都可以」
开场语从「称呼行 + 追问句」收成 claude.ai 式的单行问候:时段池、
星期池、回访池按顺序拼成一个数组,按 variantSelection 确定性取一条;
名字为空时跳过带称呼的条目。副标题行与 .starter-salutation 下线,
h1 降到 clamp(24px, 2.8vw, 30px) / line-height 1.25。追问改由输入框
占位符承担。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-17 15:54:20 +00:00

87 lines
3.1 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.
*
* 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": ["夜猫子,晚上好", "还没睡呀,{名字}", "晚安前再聊一会"],
};
/** 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()}`;
}