Redesign UI with zen cards, split AI panel, and PWA install support.

Learn uses 64-gua card grid; liuyao/bazi/combined use two input cards plus sticky right AI panel; add manifest, service worker, and install prompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-10 23:24:55 +08:00
parent 206673fd90
commit 6265e56a7f
20 changed files with 682 additions and 423 deletions
+60 -62
View File
@@ -4,6 +4,8 @@ import { useState } from "react";
import { BrainCircuit } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { ZenCard } from "@/components/ui/zen-card";
import { ModeWorkspace } from "@/components/layout/mode-workspace";
import ResultAI from "@/components/result-ai";
import BaziChartDisplay from "@/components/modes/bazi-chart";
import DateTimePicker, { nowDateString } from "@/components/shared/datetime-picker";
@@ -25,7 +27,6 @@ export default function BaziForm() {
const [completion, setCompletion] = useState("");
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [showAi, setShowAi] = useState(false);
const location = useRegionLocation(provinceCode, cityCode);
@@ -54,19 +55,28 @@ export default function BaziForm() {
}
setError("");
setChart(calculateBazi(input));
setShowAi(false);
setCompletion("");
}
async function handleAnalyze() {
const input = buildInput();
if (!input || !chart) {
if (!input) {
setError("请选择出生地域");
return;
}
if (!question.trim()) {
setError("请输入问事");
return;
}
const activeChart = chart ?? calculateBazi(input);
if (!chart) {
setChart(activeChart);
}
setError("");
setCompletion("");
setIsLoading(true);
setShowAi(true);
try {
await streamAiCompletion(
{
@@ -87,8 +97,19 @@ export default function BaziForm() {
}
return (
<div className="flex flex-1 flex-col gap-4 px-4 py-6">
<div className="mx-auto w-full max-w-lg space-y-4">
<ModeWorkspace
aiPanel={
<ResultAI
panel
completion={completion}
isLoading={isLoading}
onCompletion={handleAnalyze}
error={error}
emptyHint="排盘后点击「AI 测算」获取解读"
/>
}
>
<ZenCard title="出生 · 命局" subtitle="四柱排盘所需信息">
<DateTimePicker
label="出生日期 / 时间"
date={date}
@@ -105,24 +126,19 @@ export default function BaziForm() {
/>
</label>
<div className="space-y-1">
<label className="text-sm font-medium"></label>
<div className="flex gap-4">
{(["male", "female"] as const).map((g) => (
<label key={g} className="flex items-center gap-2 text-sm">
<input
type="radio"
name="gender"
checked={gender === g}
onChange={() => setGender(g)}
/>
{g === "male" ? "男" : "女"}
</label>
))}
</div>
<div className="flex gap-4">
{(["male", "female"] as const).map((g) => (
<label key={g} className="flex items-center gap-2 text-sm">
<input
type="radio"
name="gender"
checked={gender === g}
onChange={() => setGender(g)}
/>
{g === "male" ? "男" : "女"}
</label>
))}
</div>
<RegionSelect
label="出生地域"
provinceCode={provinceCode}
@@ -130,48 +146,30 @@ export default function BaziForm() {
onProvinceChange={setProvinceCode}
onCityChange={setCityCode}
/>
</ZenCard>
<div className="space-y-1">
<label className="text-sm font-medium"></label>
<Textarea
placeholder="事业、婚姻、健康等..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={3}
/>
</div>
{error && !showAi && (
<ZenCard title="问事 · 排盘" subtitle="所求与命盘结果">
<Textarea
placeholder="事业、婚姻、健康等..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
rows={3}
className="border-border/60 bg-background/50"
/>
{error && !completion && (
<p className="text-sm text-destructive">{error}</p>
)}
<Button onClick={handleCalculate} className="w-full">
</Button>
</div>
{chart && (
<div className="mx-auto w-full max-w-lg space-y-4">
<BaziChartDisplay chart={chart} />
{!showAi && (
<Button onClick={handleAnalyze} className="w-full">
<BrainCircuit size={16} className="mr-1" />
</Button>
)}
<div className="flex gap-2">
<Button variant="outline" onClick={handleCalculate} className="flex-1">
</Button>
<Button onClick={handleAnalyze} disabled={isLoading} className="flex-1">
<BrainCircuit size={16} className="mr-1" />
AI
</Button>
</div>
)}
{showAi && (
<div className="mx-auto w-full max-w-lg pt-2">
<ResultAI
completion={completion}
isLoading={isLoading}
onCompletion={handleAnalyze}
error={error}
/>
</div>
)}
</div>
{chart && <BaziChartDisplay chart={chart} />}
</ZenCard>
</ModeWorkspace>
);
}