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:
@@ -0,0 +1,77 @@
|
||||
"use server";
|
||||
|
||||
import { streamText } from "ai";
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { createStreamableValue } from "ai/rsc";
|
||||
import { ERROR_PREFIX } from "@/lib/constant";
|
||||
|
||||
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;
|
||||
|
||||
export async function streamAIResponse(
|
||||
system: string,
|
||||
user: string,
|
||||
): Promise<{ data?: ReturnType<typeof createStreamableValue<string>>["value"]; error?: string }> {
|
||||
const stream = createStreamableValue<string>();
|
||||
|
||||
try {
|
||||
const { fullStream } = streamText({
|
||||
temperature: 0.5,
|
||||
model: openai(model),
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: user },
|
||||
],
|
||||
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 { message?: string };
|
||||
stream.update(ERROR_PREFIX + (err.message ?? String(part.error)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
.catch(console.error)
|
||||
.finally(() => {
|
||||
done = true;
|
||||
});
|
||||
|
||||
return { data: stream.value };
|
||||
} catch (err) {
|
||||
stream.done();
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return { error: message };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { Solar } from "lunar-javascript";
|
||||
import {
|
||||
adjustToTrueSolarTime,
|
||||
formatDateTime,
|
||||
parseDateTime,
|
||||
} from "@/lib/calc/time";
|
||||
|
||||
export interface BaziInput {
|
||||
date: string;
|
||||
time: string;
|
||||
gender: "male" | "female";
|
||||
longitude: number;
|
||||
unknownHour?: boolean;
|
||||
}
|
||||
|
||||
export interface PillarInfo {
|
||||
ganZhi: string;
|
||||
shiShenGan: string;
|
||||
shiShenZhi: string[];
|
||||
hideGan: string[];
|
||||
naYin: string;
|
||||
}
|
||||
|
||||
export interface DaYunInfo {
|
||||
ganZhi: string;
|
||||
startAge: number;
|
||||
}
|
||||
|
||||
export interface BaziChart {
|
||||
birthTime: string;
|
||||
trueSolarTime: string;
|
||||
unknownHour: boolean;
|
||||
lunarDate: string;
|
||||
pillars: {
|
||||
year: PillarInfo;
|
||||
month: PillarInfo;
|
||||
day: PillarInfo;
|
||||
time: PillarInfo;
|
||||
};
|
||||
daYun: {
|
||||
startYear: number;
|
||||
startMonth: number;
|
||||
startDay: number;
|
||||
items: DaYunInfo[];
|
||||
};
|
||||
liuNian: { year: number; ganZhi: string }[];
|
||||
shenSha: {
|
||||
ji: string[];
|
||||
xiong: string[];
|
||||
};
|
||||
}
|
||||
|
||||
function buildPillar(
|
||||
ganZhi: string,
|
||||
shiShenGan: string,
|
||||
shiShenZhi: string[],
|
||||
hideGan: string[],
|
||||
naYin: string,
|
||||
): PillarInfo {
|
||||
return { ganZhi, shiShenGan, shiShenZhi, hideGan, naYin };
|
||||
}
|
||||
|
||||
export function calculateBazi(input: BaziInput): BaziChart {
|
||||
const localTime = parseDateTime(input.date, input.time);
|
||||
const trueSolar = adjustToTrueSolarTime(localTime, input.longitude);
|
||||
|
||||
const solar = Solar.fromYmdHms(
|
||||
trueSolar.getFullYear(),
|
||||
trueSolar.getMonth() + 1,
|
||||
trueSolar.getDate(),
|
||||
input.unknownHour ? 12 : trueSolar.getHours(),
|
||||
input.unknownHour ? 0 : trueSolar.getMinutes(),
|
||||
0,
|
||||
);
|
||||
|
||||
const lunar = solar.getLunar();
|
||||
const ec = lunar.getEightChar();
|
||||
const genderCode = input.gender === "male" ? 1 : 0;
|
||||
const yun = ec.getYun(genderCode);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const liuNian: { year: number; ganZhi: string }[] = [];
|
||||
for (let year = currentYear - 2; year <= currentYear + 3; year++) {
|
||||
const yearSolar = Solar.fromYmdHms(year, 6, 1, 12, 0, 0);
|
||||
liuNian.push({
|
||||
year,
|
||||
ganZhi: yearSolar.getLunar().getYearInGanZhi(),
|
||||
});
|
||||
}
|
||||
|
||||
const daYunList = yun.getDaYun();
|
||||
const daYunItems: DaYunInfo[] = [];
|
||||
let age = yun.getStartYear();
|
||||
for (const dy of daYunList) {
|
||||
const gz = dy.getGanZhi();
|
||||
if (!gz) {
|
||||
continue;
|
||||
}
|
||||
daYunItems.push({ ganZhi: gz, startAge: age });
|
||||
age += 10;
|
||||
if (daYunItems.length >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
birthTime: formatDateTime(localTime),
|
||||
trueSolarTime: formatDateTime(trueSolar),
|
||||
unknownHour: !!input.unknownHour,
|
||||
lunarDate: lunar.toString(),
|
||||
pillars: {
|
||||
year: buildPillar(
|
||||
ec.getYear(),
|
||||
ec.getYearShiShenGan(),
|
||||
ec.getYearShiShenZhi(),
|
||||
ec.getYearHideGan(),
|
||||
ec.getYearNaYin(),
|
||||
),
|
||||
month: buildPillar(
|
||||
ec.getMonth(),
|
||||
ec.getMonthShiShenGan(),
|
||||
ec.getMonthShiShenZhi(),
|
||||
ec.getMonthHideGan(),
|
||||
ec.getMonthNaYin(),
|
||||
),
|
||||
day: buildPillar(
|
||||
ec.getDay(),
|
||||
ec.getDayShiShenGan(),
|
||||
ec.getDayShiShenZhi(),
|
||||
ec.getDayHideGan(),
|
||||
ec.getDayNaYin(),
|
||||
),
|
||||
time: buildPillar(
|
||||
input.unknownHour ? "不详" : ec.getTime(),
|
||||
input.unknownHour ? "—" : ec.getTimeShiShenGan(),
|
||||
input.unknownHour ? [] : ec.getTimeShiShenZhi(),
|
||||
input.unknownHour ? [] : ec.getTimeHideGan(),
|
||||
input.unknownHour ? "—" : ec.getTimeNaYin(),
|
||||
),
|
||||
},
|
||||
daYun: {
|
||||
startYear: yun.getStartYear(),
|
||||
startMonth: yun.getStartMonth(),
|
||||
startDay: yun.getStartDay(),
|
||||
items: daYunItems,
|
||||
},
|
||||
liuNian,
|
||||
shenSha: {
|
||||
ji: lunar.getDayJiShen(),
|
||||
xiong: lunar.getDayXiongSha(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function formatBaziForPrompt(chart: BaziChart): string {
|
||||
const p = chart.pillars;
|
||||
const lines = [
|
||||
`出生时间:${chart.birthTime}`,
|
||||
`真太阳时:${chart.trueSolarTime}${chart.unknownHour ? "(时辰不详,按中午排盘)" : ""}`,
|
||||
`农历:${chart.lunarDate}`,
|
||||
"",
|
||||
"【四柱】",
|
||||
`年柱:${p.year.ganZhi}(天干十神:${p.year.shiShenGan},地支十神:${p.year.shiShenZhi.join("、")},藏干:${p.year.hideGan.join("、")},纳音:${p.year.naYin})`,
|
||||
`月柱:${p.month.ganZhi}(天干十神:${p.month.shiShenGan},地支十神:${p.month.shiShenZhi.join("、")},藏干:${p.month.hideGan.join("、")},纳音:${p.month.naYin})`,
|
||||
`日柱:${p.day.ganZhi}(天干十神:${p.day.shiShenGan},地支十神:${p.day.shiShenZhi.join("、")},藏干:${p.day.hideGan.join("、")},纳音:${p.day.naYin})`,
|
||||
`时柱:${p.time.ganZhi}(天干十神:${p.time.shiShenGan},地支十神:${p.time.shiShenZhi.join("、") || "—"},藏干:${p.time.hideGan.join("、") || "—"},纳音:${p.time.naYin})`,
|
||||
"",
|
||||
`【大运】起运 ${chart.daYun.startYear} 年 ${chart.daYun.startMonth} 月 ${chart.daYun.startDay} 天`,
|
||||
chart.daYun.items.map((d) => `${d.startAge}岁起 ${d.ganZhi}`).join(" → "),
|
||||
"",
|
||||
`【流年】${chart.liuNian.map((l) => `${l.year}(${l.ganZhi})`).join("、")}`,
|
||||
"",
|
||||
`【神煞】吉神:${chart.shenSha.ji.join("、") || "无"};凶煞:${chart.shenSha.xiong.join("、") || "无"}`,
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import guaIndexData from "@/lib/data/gua-index.json";
|
||||
import guaListData from "@/lib/data/gua-list.json";
|
||||
import type { HexagramObj } from "@/components/hexagram";
|
||||
import type { ResultObj } from "@/components/result";
|
||||
|
||||
const GUA_DICT1 = ["坤", "震", "坎", "兑", "艮", "离", "巽", "乾"];
|
||||
const GUA_DICT2 = ["地", "雷", "水", "泽", "山", "火", "风", "天"];
|
||||
const CHANGE_YANG = ["初九", "九二", "九三", "九四", "九五", "上九"];
|
||||
const CHANGE_YIN = ["初六", "六二", "六三", "六四", "六五", "上六"];
|
||||
|
||||
/** 三钱法:正面数 0~3 → 爻象 */
|
||||
export function frontCountToLine(frontCount: number): Omit<HexagramObj, "separate"> {
|
||||
return {
|
||||
change: frontCount === 0 || frontCount === 3,
|
||||
yang: frontCount >= 2,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildHexagramList(frontCounts: number[]): HexagramObj[] {
|
||||
return frontCounts.map((count, index) => ({
|
||||
...frontCountToLine(count),
|
||||
separate: index === 3,
|
||||
}));
|
||||
}
|
||||
|
||||
export function computeGuaResult(list: HexagramObj[]): ResultObj | null {
|
||||
if (list.length !== 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const changeList: string[] = [];
|
||||
list.forEach((value, index) => {
|
||||
if (!value.change) {
|
||||
return;
|
||||
}
|
||||
changeList.push(value.yang ? CHANGE_YANG[index] : CHANGE_YIN[index]);
|
||||
});
|
||||
|
||||
const upIndex =
|
||||
(list[5].yang ? 4 : 0) + (list[4].yang ? 2 : 0) + (list[3].yang ? 1 : 0);
|
||||
const downIndex =
|
||||
(list[2].yang ? 4 : 0) + (list[1].yang ? 2 : 0) + (list[0].yang ? 1 : 0);
|
||||
|
||||
const guaIndex = guaIndexData[upIndex][downIndex] - 1;
|
||||
const guaName1 = guaListData[guaIndex];
|
||||
|
||||
let guaName2: string;
|
||||
if (upIndex === downIndex) {
|
||||
guaName2 = GUA_DICT1[upIndex] + "为" + GUA_DICT2[upIndex];
|
||||
} else {
|
||||
guaName2 = GUA_DICT2[upIndex] + GUA_DICT2[downIndex] + guaName1;
|
||||
}
|
||||
|
||||
const guaDesc = GUA_DICT1[upIndex] + "上" + GUA_DICT1[downIndex] + "下";
|
||||
|
||||
return {
|
||||
guaMark: `${(guaIndex + 1).toString().padStart(2, "0")}.${guaName2}`,
|
||||
guaTitle: `周易第${guaIndex + 1}卦`,
|
||||
guaResult: `${guaName1}卦(${guaName2})_${guaDesc}`,
|
||||
guaChange:
|
||||
changeList.length === 0 ? "无变爻" : `变爻: ${changeList.toString()}`,
|
||||
};
|
||||
}
|
||||
|
||||
export const YAO_LABELS = ["初爻", "二爻", "三爻", "四爻", "五爻", "上爻"];
|
||||
|
||||
export const COIN_OPTIONS = [
|
||||
{ count: 0, label: "老阴", desc: "0 正 · 变爻 ✕" },
|
||||
{ count: 1, label: "少阴", desc: "1 正" },
|
||||
{ count: 2, label: "少阳", desc: "2 正" },
|
||||
{ count: 3, label: "老阳", desc: "3 正 · 变爻 ○" },
|
||||
];
|
||||
|
||||
export interface GuaResult {
|
||||
list: HexagramObj[];
|
||||
result: ResultObj;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/** 中国标准时间基准经度(UTC+8) */
|
||||
export const CHINA_STANDARD_MERIDIAN = 120;
|
||||
|
||||
/**
|
||||
* 根据出生地经度校正真太阳时。
|
||||
* 每差 1 度经度,时间差约 4 分钟。
|
||||
*/
|
||||
export function adjustToTrueSolarTime(
|
||||
date: Date,
|
||||
longitude: number,
|
||||
standardMeridian = CHINA_STANDARD_MERIDIAN,
|
||||
): Date {
|
||||
const offsetMinutes = (longitude - standardMeridian) * 4;
|
||||
return new Date(date.getTime() + offsetMinutes * 60 * 1000);
|
||||
}
|
||||
|
||||
export function formatDateTime(date: Date): string {
|
||||
const pad = (n: number) => n.toString().padStart(2, "0");
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
export function parseDateTime(
|
||||
dateStr: string,
|
||||
timeStr: string,
|
||||
): Date {
|
||||
const [year, month, day] = dateStr.split("-").map(Number);
|
||||
const [hour, minute] = timeStr.split(":").map(Number);
|
||||
return new Date(year, month - 1, day, hour, minute, 0);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Solar } from "lunar-javascript";
|
||||
import {
|
||||
adjustToTrueSolarTime,
|
||||
formatDateTime,
|
||||
parseDateTime,
|
||||
} from "@/lib/calc/time";
|
||||
|
||||
export interface TimingInfo {
|
||||
solarTime: string;
|
||||
lunarDate: string;
|
||||
yearGanZhi: string;
|
||||
monthGanZhi: string;
|
||||
dayGanZhi: string;
|
||||
timeGanZhi: string;
|
||||
prevJieQi: string;
|
||||
nextJieQi: string;
|
||||
}
|
||||
|
||||
export function getTimingInfo(dateTime: Date): TimingInfo {
|
||||
const solar = Solar.fromYmdHms(
|
||||
dateTime.getFullYear(),
|
||||
dateTime.getMonth() + 1,
|
||||
dateTime.getDate(),
|
||||
dateTime.getHours(),
|
||||
dateTime.getMinutes(),
|
||||
0,
|
||||
);
|
||||
const lunar = solar.getLunar();
|
||||
|
||||
return {
|
||||
solarTime: formatDateTime(dateTime),
|
||||
lunarDate: lunar.toString(),
|
||||
yearGanZhi: lunar.getYearInGanZhi(),
|
||||
monthGanZhi: lunar.getMonthInGanZhi(),
|
||||
dayGanZhi: lunar.getDayInGanZhi(),
|
||||
timeGanZhi: lunar.getTimeInGanZhi(),
|
||||
prevJieQi: lunar.getPrevJieQi()?.getName() ?? "—",
|
||||
nextJieQi: lunar.getNextJieQi()?.getName() ?? "—",
|
||||
};
|
||||
}
|
||||
|
||||
export function getTimingInfoFromStrings(
|
||||
date: string,
|
||||
time: string,
|
||||
): TimingInfo {
|
||||
return getTimingInfo(parseDateTime(date, time));
|
||||
}
|
||||
|
||||
export function getTimingInfoWithLongitude(
|
||||
date: string,
|
||||
time: string,
|
||||
longitude: number,
|
||||
): { timing: TimingInfo; trueSolarTime: string } {
|
||||
const local = parseDateTime(date, time);
|
||||
const trueSolar = adjustToTrueSolarTime(local, longitude);
|
||||
return {
|
||||
timing: getTimingInfo(trueSolar),
|
||||
trueSolarTime: formatDateTime(trueSolar),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatLiuyaoTimingForPrompt(
|
||||
timing: TimingInfo,
|
||||
trueSolarTime: string,
|
||||
locationName: string,
|
||||
longitude: number,
|
||||
): string {
|
||||
return [
|
||||
"【起卦时空 · 天时】",
|
||||
`起卦时刻:${timing.solarTime}`,
|
||||
`真太阳时:${trueSolarTime}`,
|
||||
`农历:${timing.lunarDate}`,
|
||||
`年柱:${timing.yearGanZhi},月柱:${timing.monthGanZhi},日柱:${timing.dayGanZhi},时柱:${timing.timeGanZhi}`,
|
||||
`节气:上一节气 ${timing.prevJieQi},下一节气 ${timing.nextJieQi}`,
|
||||
"",
|
||||
"【起卦地域 · 地利】",
|
||||
`位置:${locationName}`,
|
||||
`经度:${longitude}°`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function formatTimingForPrompt(
|
||||
timing: TimingInfo,
|
||||
locationName: string,
|
||||
longitude: number,
|
||||
): string {
|
||||
return [
|
||||
"【天时】",
|
||||
`测算时刻:${timing.solarTime}`,
|
||||
`农历:${timing.lunarDate}`,
|
||||
`年柱:${timing.yearGanZhi},月柱:${timing.monthGanZhi},日柱:${timing.dayGanZhi},时柱:${timing.timeGanZhi}`,
|
||||
`节气:上一节气 ${timing.prevJieQi},下一节气 ${timing.nextJieQi}`,
|
||||
"",
|
||||
"【地利】",
|
||||
`当前位置:${locationName}`,
|
||||
`经度:${longitude}°`,
|
||||
].join("\n");
|
||||
}
|
||||
+51
-2
@@ -1,13 +1,54 @@
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const CONTENT_ROOT = path.join(process.cwd(), "content", "zhouyi", "docs");
|
||||
const DOCS_ROOT = path.join(process.cwd(), "content", "zhouyi", "docs");
|
||||
const OTHER_ROOT = path.join(DOCS_ROOT, "other");
|
||||
|
||||
export type LearnVariant = "traditional" | "simplified";
|
||||
|
||||
function getVariantRoot(variant: LearnVariant): string {
|
||||
return variant === "traditional" ? DOCS_ROOT : OTHER_ROOT;
|
||||
}
|
||||
|
||||
export async function listGuaMarks(
|
||||
variant: LearnVariant = "traditional",
|
||||
): Promise<string[]> {
|
||||
const dir = getVariantRoot(variant);
|
||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((entry) => entry.isDirectory() && /^\d{2}\./.test(entry.name))
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
}
|
||||
|
||||
export async function readGuaMarkdown(guaMark: string): Promise<string> {
|
||||
const filePath = path.join(CONTENT_ROOT, guaMark, "index.md");
|
||||
const filePath = path.join(DOCS_ROOT, guaMark, "index.md");
|
||||
return fs.readFile(filePath, "utf-8");
|
||||
}
|
||||
|
||||
export async function readLearnMarkdown(
|
||||
guaMark: string,
|
||||
variant: LearnVariant = "traditional",
|
||||
): Promise<string> {
|
||||
const root = getVariantRoot(variant);
|
||||
const filePath =
|
||||
guaMark === "index"
|
||||
? path.join(root, "index.md")
|
||||
: path.join(root, guaMark, "index.md");
|
||||
return fs.readFile(filePath, "utf-8");
|
||||
}
|
||||
|
||||
export function stripFrontmatter(content: string): string {
|
||||
if (!content.startsWith("---")) {
|
||||
return content;
|
||||
}
|
||||
const end = content.indexOf("---", 3);
|
||||
if (end === -1) {
|
||||
return content;
|
||||
}
|
||||
return content.slice(end + 3).trimStart();
|
||||
}
|
||||
|
||||
export function extractZhangMingRen(guaDetail: string): string | undefined {
|
||||
return guaDetail
|
||||
.match(/(\*\*台灣張銘仁[\s\S]*?)(?=周易第\d+卦)/)?.[1]
|
||||
@@ -39,3 +80,11 @@ export function extractChangeDetails(
|
||||
|
||||
return changeList;
|
||||
}
|
||||
|
||||
export function getGuaNumber(guaMark: string): number {
|
||||
return parseInt(guaMark.split(".")[0], 10);
|
||||
}
|
||||
|
||||
export function getGuaName(guaMark: string): string {
|
||||
return guaMark.split(".").slice(1).join(".");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"110000": {
|
||||
"name": "北京市",
|
||||
"longitude": 116.4074,
|
||||
"children": {
|
||||
"110101": { "name": "东城区", "longitude": 116.4164 },
|
||||
"110105": { "name": "朝阳区", "longitude": 116.4434 },
|
||||
"110108": { "name": "海淀区", "longitude": 116.2983 }
|
||||
}
|
||||
},
|
||||
"310000": {
|
||||
"name": "上海市",
|
||||
"longitude": 121.4737,
|
||||
"children": {
|
||||
"310101": { "name": "黄浦区", "longitude": 121.4903 },
|
||||
"310115": { "name": "浦东新区", "longitude": 121.5447 },
|
||||
"310104": { "name": "徐汇区", "longitude": 121.4365 }
|
||||
}
|
||||
},
|
||||
"440000": {
|
||||
"name": "广东省",
|
||||
"longitude": 113.2665,
|
||||
"children": {
|
||||
"440100": { "name": "广州市", "longitude": 113.2644 },
|
||||
"440300": { "name": "深圳市", "longitude": 114.0579 },
|
||||
"440600": { "name": "佛山市", "longitude": 113.1214 }
|
||||
}
|
||||
},
|
||||
"330000": {
|
||||
"name": "浙江省",
|
||||
"longitude": 120.1536,
|
||||
"children": {
|
||||
"330100": { "name": "杭州市", "longitude": 120.1551 },
|
||||
"330200": { "name": "宁波市", "longitude": 121.5503 },
|
||||
"330300": { "name": "温州市", "longitude": 120.6994 }
|
||||
}
|
||||
},
|
||||
"320000": {
|
||||
"name": "江苏省",
|
||||
"longitude": 118.7969,
|
||||
"children": {
|
||||
"320100": { "name": "南京市", "longitude": 118.7969 },
|
||||
"320500": { "name": "苏州市", "longitude": 120.5853 },
|
||||
"320200": { "name": "无锡市", "longitude": 120.3119 }
|
||||
}
|
||||
},
|
||||
"510000": {
|
||||
"name": "四川省",
|
||||
"longitude": 104.0665,
|
||||
"children": {
|
||||
"510100": { "name": "成都市", "longitude": 104.0665 },
|
||||
"510700": { "name": "绵阳市", "longitude": 104.6796 }
|
||||
}
|
||||
},
|
||||
"420000": {
|
||||
"name": "湖北省",
|
||||
"longitude": 114.3419,
|
||||
"children": {
|
||||
"420100": { "name": "武汉市", "longitude": 114.3055 },
|
||||
"420500": { "name": "宜昌市", "longitude": 111.2865 }
|
||||
}
|
||||
},
|
||||
"610000": {
|
||||
"name": "陕西省",
|
||||
"longitude": 108.9398,
|
||||
"children": {
|
||||
"610100": { "name": "西安市", "longitude": 108.9398 },
|
||||
"610300": { "name": "宝鸡市", "longitude": 107.2376 }
|
||||
}
|
||||
},
|
||||
"370000": {
|
||||
"name": "山东省",
|
||||
"longitude": 117.0009,
|
||||
"children": {
|
||||
"370100": { "name": "济南市", "longitude": 117.1205 },
|
||||
"370200": { "name": "青岛市", "longitude": 120.3826 }
|
||||
}
|
||||
},
|
||||
"430000": {
|
||||
"name": "湖南省",
|
||||
"longitude": 112.9834,
|
||||
"children": {
|
||||
"430100": { "name": "长沙市", "longitude": 112.9388 },
|
||||
"430200": { "name": "株洲市", "longitude": 113.1340 }
|
||||
}
|
||||
},
|
||||
"500000": {
|
||||
"name": "重庆市",
|
||||
"longitude": 106.5516,
|
||||
"children": {
|
||||
"500103": { "name": "渝中区", "longitude": 106.5629 },
|
||||
"500112": { "name": "渝北区", "longitude": 106.6304 }
|
||||
}
|
||||
},
|
||||
"350000": {
|
||||
"name": "福建省",
|
||||
"longitude": 119.2965,
|
||||
"children": {
|
||||
"350100": { "name": "福州市", "longitude": 119.2965 },
|
||||
"350200": { "name": "厦门市", "longitude": 118.0894 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import regionsData from "@/lib/data/regions.json";
|
||||
|
||||
export interface RegionNode {
|
||||
name: string;
|
||||
longitude: number;
|
||||
children?: Record<string, RegionNode>;
|
||||
}
|
||||
|
||||
export type RegionsData = Record<string, RegionNode>;
|
||||
|
||||
export const regions = regionsData as RegionsData;
|
||||
|
||||
export function getProvinces(): { code: string; name: string }[] {
|
||||
return Object.entries(regions).map(([code, node]) => ({
|
||||
code,
|
||||
name: node.name,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getCities(provinceCode: string): { code: string; name: string }[] {
|
||||
const province = regions[provinceCode];
|
||||
if (!province?.children) {
|
||||
return [];
|
||||
}
|
||||
return Object.entries(province.children).map(([code, node]) => ({
|
||||
code,
|
||||
name: node.name,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getRegionLocation(
|
||||
provinceCode: string,
|
||||
cityCode: string,
|
||||
): { name: string; longitude: number } | null {
|
||||
const province = regions[provinceCode];
|
||||
if (!province) {
|
||||
return null;
|
||||
}
|
||||
const city = province.children?.[cityCode];
|
||||
if (city) {
|
||||
return {
|
||||
name: `${province.name}${city.name}`,
|
||||
longitude: city.longitude,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: province.name,
|
||||
longitude: province.longitude,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export const LIUYAO_SYSTEM_PROMPT = `你是一位精通《周易》六爻的 AI 解读师,根据用户提供的卦象、起卦时空和问事,给出准确的卦象解读和实用建议。
|
||||
|
||||
任务要求:逻辑清晰,语气得当
|
||||
1. 结合起卦时辰(节气、日柱、时辰)与地域,分析天时地利对卦象的影响
|
||||
2. 解读主卦、变爻及变卦,说明整体趋势和吉凶
|
||||
3. 针对用户问事,结合卦象给出具体分析和可行建议`;
|
||||
|
||||
export const BAZI_SYSTEM_PROMPT = `你是一位精通子平八字的命理分析师,根据用户提供的排盘信息和问题,给出专业、清晰的命理解读。
|
||||
|
||||
任务要求:
|
||||
1. 分析命局格局与五行喜忌
|
||||
2. 解读十神组合含义
|
||||
3. 结合大运流年分析趋势
|
||||
4. 提示相关神煞吉凶
|
||||
5. 针对用户具体问题给出切实可行的建议
|
||||
语气得当,逻辑清晰,避免绝对化断言。`;
|
||||
|
||||
export const COMBINED_SYSTEM_PROMPT = `你是一位融合天时、地利、人和的综合命理顾问,精通子平八字与周易六爻。
|
||||
|
||||
任务要求:
|
||||
1. 综合天时(节气、日柱时辰)、地利(地域经度方位)、人和(八字命局)进行分析
|
||||
2. 若用户提供卦象,结合卦辞与变爻补充解读
|
||||
3. 针对用户问事给出具体、可操作的指引
|
||||
语气得当,逻辑清晰,各维度分析要有机融合而非简单罗列。`;
|
||||
Reference in New Issue
Block a user