a6d4473ef0
The home screen renders all ten domains from the consultation registry, but the Agent only ever wrote three of them; the other seven were static registry prompts dressed up as personalized starting points. The payload now has to cover every domain in registry order, validated as a set rather than per item, so a short or misordered answer is rejected whole instead of silently leaving cards on static copy. The greeting went the other way. Nothing has rendered it since the hero note was removed, so it leaves the schema, the prompt, and the client contract rather than costing tokens for text no one reads. Ten questions take much longer to generate than three, so the route, the server generation budget, and the client request deadline all grow together, and the cache version bump forces existing payloads to be regenerated once under the new shape. Co-authored-by: Cursor <cursoragent@cursor.com>
52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
import { z } from "zod";
|
|
import { consultationDomainSchema, consultationDomainIds } from "./consultation-domain-registry";
|
|
import { generalGuidedJyotishTopics } from "./guided-jyotish-topics";
|
|
|
|
const detachedStarterQuestionPattern = /印度占星|一般如何|通常(?:会)?(?:看|观察|分析|理解|包含)|哪些(?:因素|证据层)|如何划分/;
|
|
|
|
const userCenteredStarterQuestionSchema = z.string()
|
|
.trim()
|
|
.min(4)
|
|
.max(80)
|
|
.refine((text) => text.includes("我"), "starter_question_must_be_first_person")
|
|
.refine((text) => !detachedStarterQuestionPattern.test(text), "starter_question_must_not_be_encyclopedic");
|
|
|
|
// Every domain the home screen renders needs a question, in registry order, so the
|
|
// page never has to fall back to a static prompt for part of the grid.
|
|
export const onboardingSuggestionThemes = consultationDomainIds;
|
|
|
|
const onboardingSchema = z.object({
|
|
suggestions: z.array(z.object({
|
|
theme: consultationDomainSchema,
|
|
text: userCenteredStarterQuestionSchema,
|
|
})).refine(
|
|
(items) => items.length === onboardingSuggestionThemes.length
|
|
&& items.every((item, index) => item.theme === onboardingSuggestionThemes[index]),
|
|
"suggestions_must_cover_every_domain_in_order",
|
|
),
|
|
});
|
|
|
|
export type OnboardingPayload = z.infer<typeof onboardingSchema>;
|
|
|
|
export const fallbackOnboardingPayload: OnboardingPayload = {
|
|
suggestions: generalGuidedJyotishTopics.map((topic) => ({ theme: topic.id, text: topic.prompt })),
|
|
};
|
|
|
|
class OnboardingJsonError extends Error {
|
|
readonly name = "OnboardingJsonError";
|
|
}
|
|
|
|
export function parseOnboardingPayload(value: unknown): OnboardingPayload | null {
|
|
const parsed = onboardingSchema.safeParse(value);
|
|
return parsed.success ? parsed.data : null;
|
|
}
|
|
|
|
export function parseOnboardingText(text: string): OnboardingPayload | null {
|
|
const normalized = text.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "");
|
|
const start = normalized.indexOf("{");
|
|
const end = normalized.lastIndexOf("}");
|
|
if (start < 0 || end <= start) throw new OnboardingJsonError("onboarding_json_missing");
|
|
const parsed: unknown = JSON.parse(normalized.slice(start, end + 1));
|
|
return parseOnboardingPayload(parsed);
|
|
}
|