fix(sessions): title rectification chats with the result time

This commit is contained in:
jesse-ux
2026-09-25 01:56:57 +08:00
parent 514951d708
commit 88fe67df9b
17 changed files with 592 additions and 118 deletions
+3 -1
View File
@@ -81,6 +81,8 @@ function clipTitle(value: string, maxChars = 14): string {
return characters.length > maxChars ? `${characters.slice(0, maxChars).join("")}…` : value;
}
// Daily rhythm only. Rectification titles are the result time, written by
// public.rectification_session_title when the case result changes (BUG-1001 / D3).
function datedSessionTitle(at: Date, category: string): string {
const { month, day } = shanghaiDateParts(at);
return `${category} · ${month}月${day}日`;
@@ -99,7 +101,7 @@ export function resolveSessionTitle(
return datedSessionTitle(at, "今日节奏");
}
if (options.entrypoint === "birth_time_rectification" || isRectificationHandoffQuestion(question)) {
return datedSessionTitle(at, "生时校正");
return "生时校正";
}
const normalized = question.replace(/\s+/g, " ").trim().replace(/[??!!。.,,;;::]+$/u, "");
if (!normalized) return "新对话";
@@ -1,43 +0,0 @@
/** Product UI dates are wall-clock in China. Repair uses this zone, never UTC. */
export const RECTIFICATION_TITLE_TIMEZONE = "Asia/Shanghai";
export const RECTIFICATION_DATED_TITLE =
/^(\d{1,2})月(\d{1,2})日\s*·\s*生时校正(?:\s+\d{2}:\d{2})?$/;
export function datedRectificationTitle(
createdAt: Date,
timeZone = RECTIFICATION_TITLE_TIMEZONE,
): string {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone,
month: "numeric",
day: "numeric",
}).formatToParts(createdAt);
const month = Number(parts.find((part) => part.type === "month")?.value);
const day = Number(parts.find((part) => part.type === "day")?.value);
if (!Number.isInteger(month) || !Number.isInteger(day)) {
return "生时校正";
}
return `${month}月${day}日 · 生时校正`;
}
export function repairedRectificationTitle(row: {
title: string;
createdAt: Date | null;
}): string | null {
const matched = row.title.match(RECTIFICATION_DATED_TITLE);
if (!matched) return null;
if (!row.createdAt || Number.isNaN(row.createdAt.getTime())) {
return "生时校正";
}
const next = datedRectificationTitle(row.createdAt);
const nextMatch = next.match(RECTIFICATION_DATED_TITLE);
if (
nextMatch
&& Number(matched[1]) === Number(nextMatch[1])
&& Number(matched[2]) === Number(nextMatch[2])
) {
return null;
}
return next;
}
+4 -1
View File
@@ -10,7 +10,10 @@ function clipHan(value: string, maxChars: number): string {
}
const BIRTH_STAMP = /\d{4}年\d{1,2}月(?:\d{1,2}日)?|\d{1,2}:\d{2}/;
const DATED_ENTRY_TITLE = /^(?:(?:生时校正|今日节奏)\s*·\s*\d{1,2}月\d{1,2}日|\d{1,2}月\d{1,2}日\s*·\s*(?:今日节奏|生时校正))(?:\s+\d{2}:\d{2})?$/;
// Same automatic shapes as public.rectification_session_title_is_automatic.
// The range dash is U+2013. A hyphen range is not automatic. The parity test
// reads the SQL predicate from the migration and compares answers (BUG-1001).
const DATED_ENTRY_TITLE = /^(?:(?:生时校正|今日节奏)\s*·\s*[0-9]{1,2}月[0-9]{1,2}日|[0-9]{1,2}月[0-9]{1,2}日\s*·\s*(?:今日节奏|生时校正))(?:\s+[0-9]{2}:[0-9]{2})?$|^生时校正$|^生时校正\s*·\s*[0-9]{2}:[0-9]{2}$|^生时校正\s*·\s*[0-9]{2}:[0-9]{2}\u2013[0-9]{2}:[0-9]{2}$/;
export function sanitizeSessionTitle(raw: string): string | null {
if (/[\r\n]/.test(raw)) return null;