Implement three divination modes, learn pages, and PM2 deploy on port 3130.

Add liuyao/bazi/combined flows with shared calc and AI infrastructure, 64-gua learn routes, and update Ubuntu PM2 deployment docs for port 3130.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-10 20:19:49 +08:00
parent d141623070
commit fff77dac3f
41 changed files with 2590 additions and 385 deletions
+16 -88
View File
@@ -1,23 +1,8 @@
"use server";
import { streamText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { createStreamableValue } from "ai/rsc";
import { ERROR_PREFIX } from "@/lib/constant";
import {
extractChangeDetails,
extractZhangMingRen,
readGuaMarkdown,
} from "@/lib/content/zhouyi";
const model =
process.env.OPENAI_MODEL ?? "huihui_ai/gemma-4-abliterated:e4b";
const openai = createOpenAI({
baseURL: process.env.OPENAI_BASE_URL ?? "https://op.bz121.com/v1",
});
const STREAM_INTERVAL = 60;
const MAX_SIZE = 6;
import { getLiuyaoAnswer } from "@/app/actions/liuyao";
/** 兼容旧版 divination 组件签名 */
export async function getAnswer(
prompt: string,
guaMark: string,
@@ -25,76 +10,19 @@ export async function getAnswer(
guaResult: string,
guaChange: string,
) {
console.log(prompt, guaTitle, guaResult, guaChange);
const stream = createStreamableValue();
try {
const guaDetail = await readGuaMarkdown(guaMark);
const explain = extractZhangMingRen(guaDetail) ?? "";
const changeList = extractChangeDetails(guaDetail, guaChange, guaTitle);
const now = new Date();
const calcDate = now.toISOString().slice(0, 10);
const calcTime = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}`;
const { fullStream } = streamText({
temperature: 0.5,
model: openai(model),
messages: [
{
role: "system",
content: `你是一位精通《周易》的AI助手,根据用户提供的卦象和问题,提供准确的卦象解读和实用建议
任务要求:逻辑清晰,语气得当
1. 解读卦象:分析主卦、变爻及变卦,解读整体趋势和吉凶
2. 关联问题:针对用户问题,结合卦象信息,提供具体分析
3. 提供建议:根据卦象启示,给出切实可行的建议,帮助用户解决实际问题`,
},
{
role: "user",
content: `我摇到的卦象:${guaTitle} ${guaResult} ${guaChange}
我的问题:${prompt}
${explain}
${changeList.join("\n")}`,
},
],
maxRetries: 0,
});
let buffer = "";
let done = false;
const intervalId = setInterval(() => {
if (done && buffer.length === 0) {
clearInterval(intervalId);
stream.done();
return;
}
if (buffer.length <= MAX_SIZE) {
stream.update(buffer);
buffer = "";
} else {
const chunk = buffer.slice(0, MAX_SIZE);
buffer = buffer.slice(MAX_SIZE);
stream.update(chunk);
}
}, STREAM_INTERVAL);
(async () => {
for await (const part of fullStream) {
switch (part.type) {
case "text-delta":
buffer += part.textDelta;
break;
case "error":
const err = part.error as any;
stream.update(ERROR_PREFIX + (err.message ?? err.toString()));
break;
}
}
})()
.catch(console.error)
.finally(() => {
done = true;
});
return { data: stream.value };
} catch (err: any) {
stream.done();
return { error: err.message ?? err };
}
return getLiuyaoAnswer({
question: prompt,
calcDate,
calcTime,
locationName: "未指定",
longitude: 120,
guaMark,
guaTitle,
guaResult,
guaChange,
});
}