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; 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); }