fix(home): serve today's starlanguage from daily guidance
The homepage was waiting on a full chart plus four extra engines, so the card timed out and showed the failure copy on every visit. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,8 +4,8 @@ import {
|
||||
composeDailyStarlanguageCard,
|
||||
dailyStarlanguageCacheKey,
|
||||
dailyStarlanguageCardSchema,
|
||||
dailyStarlanguageEvidence,
|
||||
dailyStarlanguagePrompt,
|
||||
evidenceFromDailyGuidance,
|
||||
parseDailyStarlanguageText,
|
||||
type DailyStarlanguageCard,
|
||||
type DailyStarlanguageEvidence,
|
||||
@@ -19,7 +19,7 @@ import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
import { getDailyStarlanguageAgent } from "@/mastra";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const maxDuration = 30;
|
||||
export const maxDuration = 20;
|
||||
|
||||
type Profile = GlobalBirthProfile & { birthTimeStatus?: string; timezoneId?: string };
|
||||
type BirthPayload = NonNullable<Awaited<ReturnType<typeof dailyProfilePayload>>>;
|
||||
@@ -35,7 +35,7 @@ type Generated =
|
||||
| { readonly kind: "failed"; readonly reason: "chart_unavailable" };
|
||||
|
||||
const jyotishApiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200";
|
||||
const engineTimeoutMs = 20_000;
|
||||
const engineTimeoutMs = 8_000;
|
||||
const agentTimeoutMs = 45_000;
|
||||
const cacheLimit = 500;
|
||||
|
||||
@@ -89,37 +89,17 @@ async function fetchEngine(path: string, body: Record<string, unknown>) {
|
||||
return await response.json() as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function chartPoints(chart: Record<string, unknown>) {
|
||||
const modules = chart.modules && typeof chart.modules === "object" ? chart.modules as Record<string, unknown> : {};
|
||||
const chartModule = modules.chart && typeof modules.chart === "object" ? modules.chart as Record<string, unknown> : chart;
|
||||
const planets = chartModule.planets && typeof chartModule.planets === "object" ? chartModule.planets : undefined;
|
||||
const ascendant = chartModule.ascendant && typeof chartModule.ascendant === "object" ? chartModule.ascendant : undefined;
|
||||
return planets && ascendant ? { planets, ascendant } : null;
|
||||
}
|
||||
|
||||
async function collectLayers(payload: BirthPayload, today: string) {
|
||||
const chart = await fetchEngine("/api/chart", payload).catch((error: unknown) => {
|
||||
console.warn("daily_starlanguage_chart_unavailable", error);
|
||||
async function loadDailyGuidance(payload: BirthPayload, today: string) {
|
||||
const packet = await fetchEngine("/api/daily_guidance", {
|
||||
...payload,
|
||||
date: today,
|
||||
reference_date: today,
|
||||
}).catch((error: unknown) => {
|
||||
console.warn("daily_starlanguage_guidance_unavailable", error);
|
||||
return null;
|
||||
});
|
||||
const points = chart === null ? null : chartPoints(chart);
|
||||
if (chart === null || !points) return null;
|
||||
const tomorrow = new Date(`${today}T00:00:00.000Z`);
|
||||
tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
|
||||
const withPoints = { ...payload, planets: points.planets, ascendant: points.ascendant };
|
||||
const [vimshottari, narayana, varga, transit] = await Promise.all([
|
||||
fetchEngine("/api/dasha", { ...withPoints, dasha: "vimshottari", today }).catch(() => null),
|
||||
fetchEngine("/api/dasha", { ...withPoints, dasha: "narayana", today }).catch(() => null),
|
||||
fetchEngine("/api/varga_full", { ...withPoints, divisions: ["D9", "D10"] }).catch(() => null),
|
||||
fetchEngine("/api/transit", {
|
||||
natal_planets: points.planets,
|
||||
ascendant: points.ascendant,
|
||||
start: today,
|
||||
end: tomorrow.toISOString().slice(0, 10),
|
||||
planets_to_check: ["Saturn", "Jupiter", "Rahu", "Ketu", "Mars", "Sun"],
|
||||
}).catch(() => null),
|
||||
]);
|
||||
return { chart, vimshottari, narayana, varga, transit };
|
||||
if (!packet || packet.success !== true) return null;
|
||||
return packet;
|
||||
}
|
||||
|
||||
async function polishWithAgent(evidence: DailyStarlanguageEvidence): Promise<DailyStarlanguageCard | null> {
|
||||
@@ -158,9 +138,10 @@ function scheduleAgentPolish(key: string, today: string, evidence: DailyStarlang
|
||||
}
|
||||
|
||||
async function generateCard(payload: BirthPayload, profile: Profile, today: string): Promise<Generated> {
|
||||
const layers = await collectLayers(payload, today);
|
||||
if (!layers) return { kind: "failed", reason: "chart_unavailable" };
|
||||
const evidence = dailyStarlanguageEvidence(layers, today, profile.birthTimeStatus === "confirmed");
|
||||
const packet = await loadDailyGuidance(payload, today);
|
||||
if (!packet) return { kind: "failed", reason: "chart_unavailable" };
|
||||
const evidence = evidenceFromDailyGuidance(packet, today, profile.birthTimeStatus === "confirmed");
|
||||
if (!evidence) return { kind: "failed", reason: "chart_unavailable" };
|
||||
return {
|
||||
kind: "card",
|
||||
card: composeDailyStarlanguageCard(evidence),
|
||||
|
||||
@@ -1297,6 +1297,20 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy p[aria-busy="true"] {
|
||||
color: var(--color-ink-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.product-entrypoint-copy p[aria-busy="true"] {
|
||||
animation: daily-starlanguage-pending 1.6s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes daily-starlanguage-pending {
|
||||
50% { opacity: .55; }
|
||||
}
|
||||
|
||||
.product-entrypoint-footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
@@ -1274,12 +1274,18 @@ export default function Home() {
|
||||
const dailyStarlanguageFingerprint = dailyStarlanguageProfileKey(profile);
|
||||
|
||||
const starterThemes = personalChartAvailable ? themes : generalGuidedJyotishTopics;
|
||||
const dailyStarlanguageQuestion = !personalChartAvailable
|
||||
? "请帮我看一下今天的运势,重点告诉我适合推进什么、需要注意什么。"
|
||||
: dailyStarlanguage.kind === "unavailable"
|
||||
? "从今日问起"
|
||||
: "深入看今日";
|
||||
const dailyStarlanguageTrend = dailyStarlanguage.kind === "ready"
|
||||
? dailyStarlanguage.card.trend
|
||||
: dailyStarlanguage.kind === "pending"
|
||||
? "正在结合你的星盘写今天的星语。"
|
||||
: "今天的星语没能生成。点这里从今日问起;不会用通用文案顶替。";
|
||||
? "正在写下今天的星语。"
|
||||
: "今天的星语还没写出来。";
|
||||
const dailyStarlanguageAction = dailyStarlanguage.kind === "ready" ? dailyStarlanguage.card.action : "";
|
||||
const dailyStarlanguageBusy = personalChartAvailable && dailyStarlanguage.kind === "pending";
|
||||
const onboardingPending = profileComplete && !onboarding && !onboardingError;
|
||||
const currentOnboardingMessage = onboardingJustCompleted
|
||||
? startGreeting || completedOnboardingMessage(profileDraft.name.trim())
|
||||
@@ -2448,9 +2454,7 @@ export default function Home() {
|
||||
|
||||
function draftDailyStarlanguageQuestion() {
|
||||
chooseSuggestedQuestion(
|
||||
personalChartAvailable
|
||||
? "深入看今日"
|
||||
: "请帮我看一下今天的运势,重点告诉我适合推进什么、需要注意什么。",
|
||||
dailyStarlanguageQuestion,
|
||||
"timing",
|
||||
personalChartAvailable ? "daily_starlanguage" : null,
|
||||
);
|
||||
@@ -3550,17 +3554,20 @@ export default function Home() {
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
aria-label={personalChartAvailable ? "深入看今日" : "查看今日运势"}
|
||||
aria-label={personalChartAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={draftDailyStarlanguageQuestion}
|
||||
/>
|
||||
<div className="product-entrypoint-copy">
|
||||
<h2 id="daily-starlanguage-title">{personalChartAvailable ? "今日星语" : "每日运势"}</h2>
|
||||
<p aria-busy={personalChartAvailable && dailyStarlanguage.kind === "pending"}>{personalChartAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}</p>
|
||||
<p
|
||||
aria-busy={dailyStarlanguageBusy}
|
||||
role={personalChartAvailable && dailyStarlanguage.kind !== "ready" ? "status" : undefined}
|
||||
>{personalChartAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}</p>
|
||||
</div>
|
||||
<div className="product-entrypoint-footer">
|
||||
<small>{personalChartAvailable ? dailyStarlanguageAction : "不支持的个人判断会明确说明,不会补造出生时间。"}</small>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{personalChartAvailable ? "深入看今日" : "查看今日运势"} <ArrowUpRight className="starter-arrow" /></span>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{personalChartAvailable ? dailyStarlanguageQuestion : "查看今日运势"} <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
<article className="birth-rectification-card product-entrypoint-card" aria-labelledby="birth-rectification-title">
|
||||
|
||||
@@ -11,6 +11,8 @@ export type DailyStarlanguageEvidence = {
|
||||
readonly birthTimeVerified: boolean;
|
||||
readonly ascendantSign: string | null;
|
||||
readonly moonSign: string | null;
|
||||
readonly transitMoonSign: string | null;
|
||||
readonly transitMoonHouse: number | null;
|
||||
readonly vimshottari: {
|
||||
readonly mahadasha: string | null;
|
||||
readonly antardasha: string | null;
|
||||
@@ -161,6 +163,8 @@ export function dailyStarlanguageEvidence(
|
||||
birthTimeVerified,
|
||||
ascendantSign: signOf(asRecord(chart)?.ascendant),
|
||||
moonSign: planetSign(chart, "Moon"),
|
||||
transitMoonSign: null,
|
||||
transitMoonHouse: null,
|
||||
vimshottari,
|
||||
narayanaSign: narayana,
|
||||
divisional,
|
||||
@@ -171,6 +175,42 @@ export function dailyStarlanguageEvidence(
|
||||
};
|
||||
}
|
||||
|
||||
export function evidenceFromDailyGuidance(
|
||||
packet: Record<string, unknown>,
|
||||
today: string,
|
||||
birthTimeVerified: boolean,
|
||||
): DailyStarlanguageEvidence | null {
|
||||
if (packet.success !== true) return null;
|
||||
const moonHouse = asCount(packet.moon_house);
|
||||
const transitMoonHouse = moonHouse !== null && moonHouse >= 1 && moonHouse <= 12 ? moonHouse : null;
|
||||
const mahadasha = asText(packet.mahadasha_lord);
|
||||
const transitMoonSign = asText(packet.moon_sign);
|
||||
if (!transitMoonHouse && !mahadasha && !transitMoonSign) return null;
|
||||
return {
|
||||
today: asText(packet.date) ?? today,
|
||||
birthTimeVerified,
|
||||
ascendantSign: asText(packet.ascendant_sign),
|
||||
moonSign: asText(packet.natal_moon_sign),
|
||||
transitMoonSign,
|
||||
transitMoonHouse,
|
||||
vimshottari: mahadasha
|
||||
? { mahadasha, antardasha: null, remainingDays: null, nakshatra: null }
|
||||
: null,
|
||||
narayanaSign: null,
|
||||
divisional: [],
|
||||
transit: transitMoonSign
|
||||
? { totalTriggers: transitMoonHouse ? 1 : 0, top: [`Moon → ${transitMoonSign}`] }
|
||||
: null,
|
||||
functionalBenefics: [],
|
||||
functionalMalefics: [],
|
||||
missingLayers: [
|
||||
mahadasha ? "" : "Vimshottari",
|
||||
"Narayana",
|
||||
"分盘",
|
||||
].filter((layer) => layer.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
export function dailyStarlanguagePrompt(evidence: DailyStarlanguageEvidence): string {
|
||||
return [
|
||||
`今天的日期:${evidence.today}`,
|
||||
@@ -279,8 +319,9 @@ function planetZh(name: string | null): string | null {
|
||||
|
||||
function moonHouse(evidence: DailyStarlanguageEvidence): number | null {
|
||||
if (!evidence.birthTimeVerified) return null;
|
||||
if (evidence.transitMoonHouse) return evidence.transitMoonHouse;
|
||||
const asc = evidence.ascendantSign;
|
||||
const moon = evidence.moonSign;
|
||||
const moon = evidence.transitMoonSign ?? evidence.moonSign;
|
||||
if (!asc || !moon) return null;
|
||||
const ascIndex = SIGNS.indexOf(asc as typeof SIGNS[number]);
|
||||
const moonIndex = SIGNS.indexOf(moon as typeof SIGNS[number]);
|
||||
@@ -292,20 +333,20 @@ export function composeDailyStarlanguageCard(evidence: DailyStarlanguageEvidence
|
||||
const house = moonHouse(evidence);
|
||||
const theme = house ? HOUSE_THEMES[house] : undefined;
|
||||
const mahadasha = planetZh(evidence.vimshottari?.mahadasha ?? null);
|
||||
const triggers = evidence.transit?.totalTriggers ?? 0;
|
||||
const hasTransitWindow = Boolean(evidence.transitMoonSign || (evidence.transit?.totalTriggers ?? 0) > 0);
|
||||
const trend = theme && mahadasha
|
||||
? `今天${theme.theme}的节奏更明显,${mahadasha}大运里适合先把一件小事做完。`
|
||||
: theme
|
||||
? `今天${theme.theme}的节奏更明显,适合把计划压到一件能完成的事。`
|
||||
: mahadasha
|
||||
? `今天按${mahadasha}大运的节奏推进,先把一件主事做小、做完。`
|
||||
: triggers > 0
|
||||
? "今天有可以观察的过境窗口,先按你当前节奏推进一件主事。"
|
||||
: hasTransitWindow
|
||||
? "今天月亮换了观察窗口,先按你当前节奏推进一件主事。"
|
||||
: "今天按你本命的节奏稳步推进,把计划压到一件主事。";
|
||||
const action = theme?.action ?? "把今天要做的事收成一件,做完再看下一件。";
|
||||
const caution = evidence.birthTimeVerified
|
||||
? (triggers > 0
|
||||
? "过境只是观察窗口,先别把它当成今天就会发生的结果。"
|
||||
? (hasTransitWindow
|
||||
? "这是今天的观察窗口,先别把它当成今天就会发生的结果。"
|
||||
: "大运描述的是阶段质地,不是今天的结果。")
|
||||
: "出生时间尚未确认,这里只看大方向,不依赖具体分钟。";
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user