Store calculation history on server with bazi input and chart snapshots.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-13 09:57:16 +08:00
parent fcf071cfaa
commit 123a5cce6d
17 changed files with 419 additions and 54 deletions
+77 -35
View File
@@ -1,41 +1,47 @@
import {
HISTORY_MAX_ITEMS,
HISTORY_STORAGE_KEY,
type CalcHistoryEntry,
} from "@/lib/history/types";
import type { CalcHistoryCreate, CalcHistoryEntry } from "@/lib/history/types";
export function loadHistory(): CalcHistoryEntry[] {
if (typeof window === "undefined") {
return [];
}
async function parseApiError(res: Response): Promise<string> {
try {
const raw = localStorage.getItem(HISTORY_STORAGE_KEY);
if (!raw) {
return [];
}
const parsed = JSON.parse(raw) as CalcHistoryEntry[];
return Array.isArray(parsed) ? parsed : [];
const data = (await res.json()) as { error?: string };
return data.error ?? `请求失败 (${res.status})`;
} catch {
return [];
return `请求失败 (${res.status})`;
}
}
export function saveHistoryEntry(
entry: Omit<CalcHistoryEntry, "id" | "createdAt">,
): CalcHistoryEntry {
const full: CalcHistoryEntry = {
...entry,
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
};
const list = [full, ...loadHistory()].slice(0, HISTORY_MAX_ITEMS);
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(list));
return full;
export async function loadHistory(): Promise<CalcHistoryEntry[]> {
const res = await fetch("/api/history", { cache: "no-store" });
if (!res.ok) {
throw new Error(await parseApiError(res));
}
const data = (await res.json()) as { items: CalcHistoryEntry[] };
return data.items ?? [];
}
export function deleteHistoryEntry(id: string): void {
const list = loadHistory().filter((e) => e.id !== id);
localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(list));
export async function saveHistoryEntry(
entry: CalcHistoryCreate,
): Promise<CalcHistoryEntry> {
const res = await fetch("/api/history", {
method: "POST",
cache: "no-store",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(entry),
});
if (!res.ok) {
throw new Error(await parseApiError(res));
}
const data = (await res.json()) as { entry: CalcHistoryEntry };
return data.entry;
}
export async function deleteHistoryEntry(id: string): Promise<void> {
const res = await fetch(`/api/history/${encodeURIComponent(id)}`, {
method: "DELETE",
cache: "no-store",
});
if (!res.ok) {
throw new Error(await parseApiError(res));
}
}
export function downloadMarkdown(content: string, filename: string) {
@@ -48,6 +54,33 @@ export function downloadMarkdown(content: string, filename: string) {
URL.revokeObjectURL(url);
}
function formatBaziInputLines(entry: CalcHistoryEntry): string[] {
if (!entry.baziInput) {
return [];
}
const { baziInput } = entry;
const gender = baziInput.gender === "male" ? "男" : "女";
const hour = baziInput.unknownHour ? "时辰不详" : baziInput.time;
return [
`- 出生地域:${baziInput.birthPlaceName}`,
`- 阳历生日:${baziInput.date} ${hour}`,
`- 性别:${gender}`,
];
}
function formatBaziChartLines(entry: CalcHistoryEntry): string[] {
if (!entry.baziChart) {
return [];
}
const { baziChart } = entry;
const { pillars } = baziChart;
return [
`- 农历:${baziChart.lunarDate}`,
`- 四柱:${pillars.year.ganZhi} ${pillars.month.ganZhi} ${pillars.day.ganZhi} ${pillars.time.ganZhi}`,
`- 真太阳时:${baziChart.trueSolarTime}`,
];
}
export function buildHistoryMarkdown(entry: CalcHistoryEntry): string {
const lines = [
`# ${entry.title}`,
@@ -56,11 +89,20 @@ export function buildHistoryMarkdown(entry: CalcHistoryEntry): string {
`- 时间:${new Date(entry.createdAt).toLocaleString("zh-CN")}`,
`- 问事:${entry.question}`,
"",
...Object.entries(entry.meta).map(([k, v]) => `- ${k}${v}`),
"",
"---",
"",
entry.completion,
...formatBaziInputLines(entry),
...formatBaziChartLines(entry),
...Object.entries(entry.meta)
.filter(([key]) => !["出生地域", "阳历生日", "农历", "性别"].includes(key))
.map(([k, v]) => `- ${k}${v}`),
];
if (entry.hexagram) {
lines.push(
`- 卦象:${entry.hexagram.guaTitle}`,
`- 卦辞:${entry.hexagram.guaResult}`,
);
}
lines.push("", "---", "", entry.completion);
return lines.join("\n");
}