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
+72 -11
View File
@@ -6,6 +6,7 @@ import { Download, Trash2 } from "lucide-react";
import PageShell from "@/components/page-shell";
import { Button } from "@/components/ui/button";
import { ZenCard } from "@/components/ui/zen-card";
import BaziChartDisplay from "@/components/modes/bazi-chart";
import Markdown from "react-markdown";
import {
buildHistoryMarkdown,
@@ -18,21 +19,46 @@ import { MODE_LABELS, type CalcHistoryEntry } from "@/lib/history/types";
export default function HistoryPageClient() {
const [items, setItems] = useState<CalcHistoryEntry[]>([]);
const [activeId, setActiveId] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const list = loadHistory();
setItems(list);
setActiveId(list[0]?.id ?? null);
let cancelled = false;
(async () => {
try {
const list = await loadHistory();
if (cancelled) {
return;
}
setItems(list);
setActiveId(list[0]?.id ?? null);
} catch (e) {
if (!cancelled) {
setError(e instanceof Error ? e.message : String(e));
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, []);
const active = items.find((e) => e.id === activeId) ?? null;
function handleDelete(id: string) {
deleteHistoryEntry(id);
const next = loadHistory();
setItems(next);
if (activeId === id) {
setActiveId(next[0]?.id ?? null);
async function handleDelete(id: string) {
try {
await deleteHistoryEntry(id);
const next = items.filter((item) => item.id !== id);
setItems(next);
if (activeId === id) {
setActiveId(next[0]?.id ?? null);
}
} catch (e) {
setError(e instanceof Error ? e.message : String(e));
}
}
@@ -41,11 +67,17 @@ export default function HistoryPageClient() {
<div className="mb-6 text-center">
<h1 className="text-2xl font-bold tracking-wide"></h1>
<p className="mt-2 text-sm text-muted-foreground">
</p>
</div>
{items.length === 0 ? (
{loading ? (
<ZenCard className="text-center text-sm text-muted-foreground">
</ZenCard>
) : error ? (
<ZenCard className="text-center text-sm text-destructive">{error}</ZenCard>
) : items.length === 0 ? (
<ZenCard className="text-center text-sm text-muted-foreground">
<p></p>
<Link href="/liuyao" className="mt-3 inline-block text-primary underline">
@@ -70,6 +102,14 @@ export default function HistoryPageClient() {
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
{item.question}
</p>
{item.baziChart && (
<p className="mt-1 font-mono text-xs text-muted-foreground">
{item.baziChart.pillars.year.ganZhi}{" "}
{item.baziChart.pillars.month.ganZhi}{" "}
{item.baziChart.pillars.day.ganZhi}{" "}
{item.baziChart.pillars.time.ganZhi}
</p>
)}
<p className="mt-1 text-xs text-muted-foreground">
{MODE_LABELS[item.mode]} ·{" "}
{new Date(item.createdAt).toLocaleString("zh-CN")}
@@ -87,6 +127,27 @@ export default function HistoryPageClient() {
{active.summary && (
<p className="text-sm text-muted-foreground">{active.summary}</p>
)}
{active.baziInput && (
<div className="rounded-md border bg-background/50 p-3 text-sm text-muted-foreground">
<p>
{active.baziInput.birthPlaceName} ·{" "}
{active.baziInput.date}{" "}
{active.baziInput.unknownHour
? "时辰不详"
: active.baziInput.time}{" "}
· {active.baziInput.gender === "male" ? "男" : "女"}
</p>
</div>
)}
{active.baziChart && <BaziChartDisplay chart={active.baziChart} />}
{active.hexagram && (
<div className="rounded-md border bg-background/50 p-3 text-sm">
<p className="font-medium">{active.hexagram.guaTitle}</p>
<p className="mt-1 text-muted-foreground">
{active.hexagram.guaResult}
</p>
</div>
)}
<div className="flex flex-wrap gap-2">
<Button
size="sm"