fix: decouple AI completion from history save failures and improve history API errors
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import RegionSelect, {
|
||||
} from "@/components/shared/region-select";
|
||||
import { calculateBazi, type BaziChart } from "@/lib/calc/bazi";
|
||||
import { streamAiCompletion } from "@/lib/ai/client-stream";
|
||||
import { saveHistoryEntry } from "@/lib/history/storage";
|
||||
import { saveHistoryEntrySafe } from "@/lib/history/storage";
|
||||
|
||||
export default function BaziForm() {
|
||||
const [date, setDate] = useState(todaySolarYmd());
|
||||
@@ -29,6 +29,7 @@ export default function BaziForm() {
|
||||
const [chart, setChart] = useState<BaziChart | null>(null);
|
||||
const [completion, setCompletion] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [warning, setWarning] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const location = useRegionLocation(provinceCode, cityCode);
|
||||
@@ -77,6 +78,7 @@ export default function BaziForm() {
|
||||
}
|
||||
|
||||
setError("");
|
||||
setWarning("");
|
||||
setCompletion("");
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -92,7 +94,7 @@ export default function BaziForm() {
|
||||
},
|
||||
setCompletion,
|
||||
);
|
||||
await saveHistoryEntry({
|
||||
const saveResult = await saveHistoryEntrySafe({
|
||||
mode: "bazi",
|
||||
title: "生辰八字解读",
|
||||
question,
|
||||
@@ -115,6 +117,9 @@ export default function BaziForm() {
|
||||
四柱: `${activeChart.pillars.year.ganZhi} ${activeChart.pillars.month.ganZhi} ${activeChart.pillars.day.ganZhi} ${activeChart.pillars.time.ganZhi}`,
|
||||
},
|
||||
});
|
||||
if (!saveResult.ok) {
|
||||
setWarning(saveResult.error);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
@@ -137,6 +142,7 @@ export default function BaziForm() {
|
||||
isLoading={isLoading}
|
||||
onCompletion={handleAnalyze}
|
||||
error={error}
|
||||
warning={warning}
|
||||
emptyHint="排盘后点击「AI 测算」获取解读"
|
||||
downloadFilename={completion ? "生辰八字解读.md" : undefined}
|
||||
downloadPreamble={downloadPreamble}
|
||||
|
||||
@@ -23,7 +23,7 @@ import RegionSelect, {
|
||||
import { calculateBazi, type BaziChart } from "@/lib/calc/bazi";
|
||||
import type { GuaResult } from "@/lib/calc/hexagram";
|
||||
import { streamAiCompletion } from "@/lib/ai/client-stream";
|
||||
import { saveHistoryEntry } from "@/lib/history/storage";
|
||||
import { saveHistoryEntrySafe } from "@/lib/history/storage";
|
||||
|
||||
export default function CombinedForm() {
|
||||
const [birthDate, setBirthDate] = useState(todaySolarYmd());
|
||||
@@ -45,6 +45,7 @@ export default function CombinedForm() {
|
||||
const [chart, setChart] = useState<BaziChart | null>(null);
|
||||
const [completion, setCompletion] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [warning, setWarning] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const birthLocation = useRegionLocation(birthProvince, birthCity);
|
||||
@@ -112,6 +113,7 @@ export default function CombinedForm() {
|
||||
}
|
||||
|
||||
setError("");
|
||||
setWarning("");
|
||||
setCompletion("");
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -155,7 +157,7 @@ export default function CombinedForm() {
|
||||
},
|
||||
setCompletion,
|
||||
);
|
||||
await saveHistoryEntry({
|
||||
const saveResult = await saveHistoryEntrySafe({
|
||||
mode: "combined",
|
||||
title: "综合测算解读",
|
||||
question,
|
||||
@@ -189,6 +191,9 @@ export default function CombinedForm() {
|
||||
六爻: withHexagram && guaData ? guaData.result.guaTitle : "无",
|
||||
},
|
||||
});
|
||||
if (!saveResult.ok) {
|
||||
setWarning(saveResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
@@ -211,6 +216,7 @@ export default function CombinedForm() {
|
||||
isLoading={isLoading}
|
||||
onCompletion={handleAnalyze}
|
||||
error={error}
|
||||
warning={warning}
|
||||
emptyHint="填写完整信息后,点击「综合测算」"
|
||||
downloadFilename={completion ? "综合测算解读.md" : undefined}
|
||||
downloadPreamble={downloadPreamble}
|
||||
|
||||
@@ -17,7 +17,7 @@ import RegionSelect, {
|
||||
useRegionLocation,
|
||||
} from "@/components/shared/region-select";
|
||||
import { streamAiCompletion } from "@/lib/ai/client-stream";
|
||||
import { saveHistoryEntry } from "@/lib/history/storage";
|
||||
import { saveHistoryEntrySafe } from "@/lib/history/storage";
|
||||
import type { GuaResult } from "@/lib/calc/hexagram";
|
||||
import todayJson from "@/lib/data/today.json";
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function LiuyaoForm() {
|
||||
const [guaData, setGuaData] = useState<GuaResult | null>(null);
|
||||
const [completion, setCompletion] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [warning, setWarning] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const location = useRegionLocation(provinceCode, cityCode);
|
||||
@@ -65,6 +66,7 @@ export default function LiuyaoForm() {
|
||||
}
|
||||
|
||||
setError("");
|
||||
setWarning("");
|
||||
setCompletion("");
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -86,7 +88,7 @@ export default function LiuyaoForm() {
|
||||
},
|
||||
setCompletion,
|
||||
);
|
||||
await saveHistoryEntry({
|
||||
const saveResult = await saveHistoryEntrySafe({
|
||||
mode: "liuyao",
|
||||
title: guaData!.result.guaTitle,
|
||||
question,
|
||||
@@ -104,6 +106,9 @@ export default function LiuyaoForm() {
|
||||
卦象: guaData!.result.guaTitle,
|
||||
},
|
||||
});
|
||||
if (!saveResult.ok) {
|
||||
setWarning(saveResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
@@ -122,6 +127,7 @@ export default function LiuyaoForm() {
|
||||
setGuaData(null);
|
||||
setCompletion("");
|
||||
setError("");
|
||||
setWarning("");
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@@ -129,6 +135,7 @@ export default function LiuyaoForm() {
|
||||
setGuaData(data);
|
||||
setCompletion("");
|
||||
setError("");
|
||||
setWarning("");
|
||||
}
|
||||
|
||||
const downloadPreamble =
|
||||
@@ -145,6 +152,7 @@ export default function LiuyaoForm() {
|
||||
isLoading={isLoading}
|
||||
onCompletion={handleAnalyze}
|
||||
error={error}
|
||||
warning={warning}
|
||||
emptyHint="完成起卦后,点击「AI 解读」"
|
||||
downloadFilename={
|
||||
completion && guaData
|
||||
|
||||
@@ -11,6 +11,7 @@ function ResultAI({
|
||||
isLoading,
|
||||
onCompletion,
|
||||
error,
|
||||
warning,
|
||||
panel = false,
|
||||
emptyHint = "完成左侧填写并起卦/排盘后,点击「AI 解读」",
|
||||
downloadFilename,
|
||||
@@ -20,6 +21,7 @@ function ResultAI({
|
||||
isLoading: boolean;
|
||||
onCompletion: () => void;
|
||||
error: string;
|
||||
warning?: string;
|
||||
panel?: boolean;
|
||||
emptyHint?: string;
|
||||
downloadFilename?: string;
|
||||
@@ -94,9 +96,17 @@ function ResultAI({
|
||||
<p className="mt-1 whitespace-pre-wrap">{error}</p>
|
||||
</div>
|
||||
) : completion ? (
|
||||
<Markdown className="prose max-w-none text-sm dark:prose-invert prose-p:leading-relaxed">
|
||||
{completion}
|
||||
</Markdown>
|
||||
<>
|
||||
{warning && (
|
||||
<div className="mb-3 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-sm text-amber-700 dark:text-amber-300">
|
||||
<p className="font-medium">解读已完成,但历史未保存</p>
|
||||
<p className="mt-1 whitespace-pre-wrap">{warning}</p>
|
||||
</div>
|
||||
)}
|
||||
<Markdown className="prose max-w-none text-sm dark:prose-invert prose-p:leading-relaxed">
|
||||
{completion}
|
||||
</Markdown>
|
||||
</>
|
||||
) : isLoading ? (
|
||||
<p className="text-sm text-muted-foreground">正在等待 AI 响应...</p>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user