dba0245cb1
Server Actions + createStreamableValue kept failing in production; fetch-based text stream avoids RSC serialization issues and shows readable error messages. Co-authored-by: Cursor <cursoragent@cursor.com>
146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
import {
|
|
calculateBazi,
|
|
formatBaziForPrompt,
|
|
} from "@/lib/calc/bazi";
|
|
import {
|
|
formatLiuyaoTimingForPrompt,
|
|
formatTimingForPrompt,
|
|
getTimingInfoWithLongitude,
|
|
} from "@/lib/calc/timing";
|
|
import {
|
|
extractChangeDetails,
|
|
extractZhangMingRen,
|
|
readGuaMarkdown,
|
|
} from "@/lib/content/zhouyi";
|
|
import {
|
|
BAZI_SYSTEM_PROMPT,
|
|
COMBINED_SYSTEM_PROMPT,
|
|
LIUYAO_SYSTEM_PROMPT,
|
|
} from "@/lib/prompts";
|
|
import type { AiRequestBody } from "@/lib/ai/types";
|
|
|
|
export async function buildAiMessages(
|
|
body: AiRequestBody,
|
|
): Promise<{ system: string; user: string }> {
|
|
switch (body.mode) {
|
|
case "liuyao": {
|
|
const input = body.payload;
|
|
const { timing, trueSolarTime } = getTimingInfoWithLongitude(
|
|
input.calcDate,
|
|
input.calcTime,
|
|
input.longitude,
|
|
);
|
|
const timingText = formatLiuyaoTimingForPrompt(
|
|
timing,
|
|
trueSolarTime,
|
|
input.locationName,
|
|
input.longitude,
|
|
);
|
|
|
|
let guaDetailText = "";
|
|
try {
|
|
const guaDetail = await readGuaMarkdown(input.guaMark);
|
|
const explain = extractZhangMingRen(guaDetail) ?? "";
|
|
const changeList = extractChangeDetails(
|
|
guaDetail,
|
|
input.guaChange,
|
|
input.guaTitle,
|
|
);
|
|
guaDetailText = [explain, changeList.join("\n")]
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
} catch {
|
|
guaDetailText = "";
|
|
}
|
|
|
|
return {
|
|
system: LIUYAO_SYSTEM_PROMPT,
|
|
user: `${timingText}
|
|
|
|
【卦象】
|
|
${input.guaTitle} ${input.guaResult} ${input.guaChange}
|
|
|
|
【问事】
|
|
${input.question}
|
|
|
|
${guaDetailText}`,
|
|
};
|
|
}
|
|
case "bazi": {
|
|
const { input, question, birthPlaceName } = body.payload;
|
|
const chart = calculateBazi(input);
|
|
const chartText = formatBaziForPrompt(chart);
|
|
return {
|
|
system: BAZI_SYSTEM_PROMPT,
|
|
user: `【出生时空】
|
|
出生地:${birthPlaceName}
|
|
|
|
【排盘信息】
|
|
${chartText}
|
|
|
|
【问事】
|
|
${question}`,
|
|
};
|
|
}
|
|
case "combined": {
|
|
const input = body.payload;
|
|
const chart = calculateBazi(input.birth);
|
|
const chartText = formatBaziForPrompt(chart);
|
|
const { timing, trueSolarTime } = getTimingInfoWithLongitude(
|
|
input.calcDate,
|
|
input.calcTime,
|
|
input.currentLongitude,
|
|
);
|
|
const timingText = [
|
|
formatTimingForPrompt(
|
|
timing,
|
|
input.currentPlaceName,
|
|
input.currentLongitude,
|
|
),
|
|
`真太阳时:${trueSolarTime}`,
|
|
].join("\n");
|
|
|
|
let hexagramText = "";
|
|
if (input.hexagram) {
|
|
const { guaMark, guaTitle, guaResult, guaChange } = input.hexagram;
|
|
try {
|
|
const guaDetail = await readGuaMarkdown(guaMark);
|
|
const explain = extractZhangMingRen(guaDetail) ?? "";
|
|
const changeList = extractChangeDetails(
|
|
guaDetail,
|
|
guaChange,
|
|
guaTitle,
|
|
);
|
|
hexagramText = [
|
|
"",
|
|
"【卦象 · 可选六爻】",
|
|
`${guaTitle} ${guaResult} ${guaChange}`,
|
|
explain,
|
|
changeList.join("\n"),
|
|
].join("\n");
|
|
} catch {
|
|
hexagramText = [
|
|
"",
|
|
"【卦象 · 可选六爻】",
|
|
`${input.hexagram.guaTitle} ${input.hexagram.guaResult} ${input.hexagram.guaChange}`,
|
|
].join("\n");
|
|
}
|
|
}
|
|
|
|
return {
|
|
system: COMBINED_SYSTEM_PROMPT,
|
|
user: `【人和 · 八字排盘】
|
|
出生地:${input.birthPlaceName}
|
|
${chartText}
|
|
|
|
${timingText}
|
|
${hexagramText}
|
|
|
|
【问事】
|
|
${input.question}`,
|
|
};
|
|
}
|
|
}
|
|
throw new Error("未知 AI 模式");
|
|
}
|