Files
memind/rain-service/time-range.mjs
T
john d2d463b1db
Memind CI / Test, build, and release guards (push) Successful in 5m5s
feat(rain): add date preset picker and reduce time clarification prompts.
Default to last 3 days in UI and backend, only ask when time is truly ambiguous, and treat「做成页面」as HTML report pages without follow-up questions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 16:54:11 +08:00

153 lines
4.5 KiB
JavaScript
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.
import { parseTimeScope } from '../temporal-recall-service/time-parser.mjs';
export const RAIN_DEFAULT_DAYS = 3;
const TZ_OFFSET_MIN = Number(process.env.TEMPORAL_RECALL_TZ_OFFSET_MIN ?? 480);
/** @param {Date} anchor @param {number} deltaDays */
function addDays(anchor, deltaDays) {
const shifted = new Date(anchor.getTime() + TZ_OFFSET_MIN * 60_000);
const ms =
Date.UTC(
shifted.getUTCFullYear(),
shifted.getUTCMonth(),
shifted.getUTCDate() + deltaDays,
0,
0,
0,
0,
) - TZ_OFFSET_MIN * 60_000;
return new Date(ms);
}
/** @param {Date} anchor */
function startOfDay(anchor) {
const p = new Date(anchor.getTime() + TZ_OFFSET_MIN * 60_000);
const ms =
Date.UTC(p.getUTCFullYear(), p.getUTCMonth(), p.getUTCDate(), 0, 0, 0, 0) -
TZ_OFFSET_MIN * 60_000;
return new Date(ms);
}
/** @param {Date} anchor */
function endOfDay(anchor) {
const p = new Date(anchor.getTime() + TZ_OFFSET_MIN * 60_000);
const ms =
Date.UTC(p.getUTCFullYear(), p.getUTCMonth(), p.getUTCDate() + 1, 0, 0, 0, 0) -
TZ_OFFSET_MIN * 60_000;
return new Date(ms);
}
/** @param {Date} now */
function buildDefault3dRange(now) {
return {
start: addDays(now, -RAIN_DEFAULT_DAYS).toISOString(),
end: endOfDay(now).toISOString(),
};
}
export const RAIN_TIME_PRESET_IDS = ['today', 'yesterday', '3d', '7d'];
/**
* @param {string} presetId
* @param {Date} [now]
*/
export function resolveRainTimeRangeFromPreset(presetId, now = new Date()) {
const id = String(presetId ?? '3d').trim();
if (id === 'today') {
return {
range: { start: startOfDay(now).toISOString(), end: endOfDay(now).toISOString() },
source: 'ui_preset',
label: '今天',
};
}
if (id === 'yesterday') {
const day = addDays(now, -1);
return {
range: { start: startOfDay(day).toISOString(), end: endOfDay(day).toISOString() },
source: 'ui_preset',
label: '昨天',
};
}
if (id === '7d') {
return {
range: { start: addDays(now, -7).toISOString(), end: endOfDay(now).toISOString() },
source: 'ui_preset',
label: '近7天',
};
}
return {
range: buildDefault3dRange(now),
source: 'ui_preset',
label: `${RAIN_DEFAULT_DAYS}`,
};
}
/** Only truly unknowable time phrases trigger clarification; others fall back to default 3d. */
const STRICT_AMBIGUOUS_TIME_PATTERNS = [
/不太确定.{0,8}时间/u,
/说不清.{0,6}(?:哪|什么).{0,4}(?:天|时间)/u,
];
const EXPLICIT_TIME_HINT =
/昨天|昨日|今天|今日|明天|明日|前天|后天|上周|这周|本周|这个月|本月|上个月|\d{1,2}\s*月|\d{1,2}\s*[:]\d{2}|最近\s*\d+\s*天|最近一周|最近1周|\d{4}-\d{2}-\d{2}/u;
/**
* @param {string} query
* @param {Date} [now]
*/
export function resolveRainTimeRange(query, now = new Date()) {
const text = String(query ?? '').trim();
for (const pattern of STRICT_AMBIGUOUS_TIME_PATTERNS) {
if (pattern.test(text)) {
return {
needsClarification: true,
clarificationQuestion:
'请说明要分析 MeInput 输入记录的起止时间(例如「9月2日 18:00 到 9月4日 10:00」,或「昨天全天」)。若你不补充,我将默认使用近 3 天。',
range: null,
source: 'ambiguous',
label: null,
};
}
}
if (!EXPLICIT_TIME_HINT.test(text)) {
return {
needsClarification: false,
range: buildDefault3dRange(now),
source: 'default_3d',
label: `${RAIN_DEFAULT_DAYS}`,
};
}
const scope = parseTimeScope(text, now);
const isDefaultFallback =
scope.rule_hits?.includes('time:default_week') ||
scope.rule_hits?.includes('time:recent_fuzzy');
if (isDefaultFallback && /最近|近期|这几天|前几天|那段(?:时间|日子)/u.test(text) && !/最近\s*\d+\s*天/u.test(text)) {
return {
needsClarification: false,
range: buildDefault3dRange(now),
source: 'default_3d',
label: `${RAIN_DEFAULT_DAYS}`,
};
}
return {
needsClarification: false,
range: scope.mention_range,
source: 'user_explicit',
label: scope.relative_label,
};
}
export function formatRainTimeRangeLabel(range, meta = {}) {
if (!range?.start || !range?.end) return meta.label ?? '未指定';
const start = range.start.slice(0, 16).replace('T', ' ');
const end = range.end.slice(0, 16).replace('T', ' ');
const suffix = meta.source === 'default_3d' ? '(默认近3天)' : '';
return `${start} ~ ${end}${suffix}`;
}