fix(小宝预警): 保证建议不为空并显示更新中

This commit is contained in:
Script Generator
2026-06-30 14:55:13 +08:00
parent 1daa2f7ec4
commit b365ce65a3
7 changed files with 356 additions and 17 deletions

View File

@@ -2,13 +2,14 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { CalendarClock, ShieldCheck, Sparkles, TriangleAlert } from 'lucide-react';
import { CalendarClock, Loader2, ShieldCheck, Sparkles, TriangleAlert } from 'lucide-react';
import { RouteGuard, useHasPermission } from '@/components/auth/Guard';
import { XiaobaoWarningCard } from '@/components/xiaobao-warning/XiaobaoWarningCard';
import { useXiaobaoWarningRisks } from '@/hooks/useXiaobaoWarningRisks';
import type { XiaobaoRiskLevel, XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
import { buildRiskInsightSignature, findPreviousRiskSnapshot, getReusableInsight, requestRiskInsight, shouldRequestRiskInsightWithCacheGate } from '@/lib/xiaobao-risk-ai';
import { buildRiskInsightSignature, findPreviousRiskSnapshot, requestRiskInsight, shouldRequestRiskInsightWithCacheGate } from '@/lib/xiaobao-risk-ai';
import { buildRiskSignature, findLatestDailySnapshot, shouldSaveRiskSnapshot } from '@/lib/xiaobao-risk-trend';
import { attachXiaobaoRiskSuggestion, buildXiaobaoRiskInsightPendingKey } from '@/lib/xiaobao-risk-suggestion';
import { filterXiaobaoRiskWarnings, formatRemainingWork, sanitizeRiskInsight } from '@/lib/xiaobao-warning-view';
import { formatDateTime } from '@/lib/format';
@@ -41,7 +42,18 @@ export default function XiaobaoWarningPage() {
function XiaobaoWarningContent() {
const router = useRouter();
const canManage = useHasPermission('xiaobao.warning:manage');
const { risks, snapshots, insights, riskDataLoaded, saveSnapshot, saveInsight, today } = useXiaobaoWarningRisks({ loadRiskCache: true });
const {
risks,
snapshots,
insights,
pendingInsightKeys,
riskDataLoaded,
saveSnapshot,
saveInsight,
beginInsightUpdate,
finishInsightUpdate,
today,
} = useXiaobaoWarningRisks({ loadRiskCache: true });
const [selectedRiskId, setSelectedRiskId] = useState<string | null>(null);
const [selectedProductId, setSelectedProductId] = useState('');
const [selectedProjectId, setSelectedProjectId] = useState('');
@@ -67,26 +79,40 @@ function XiaobaoWarningContent() {
const previous = findPreviousRiskSnapshot(snapshots, risk.versionId, today);
if (!shouldRequestRiskInsightWithCacheGate(riskDataLoaded, insights, risk, previous)) return;
const signature = buildRiskInsightSignature(risk);
const key = `${risk.versionId}:${signature}`;
const key = buildXiaobaoRiskInsightPendingKey(risk);
if (pendingInsightKeys.includes(key)) return;
if (requestedInsightKeysRef.current.has(key)) return;
requestedInsightKeysRef.current.add(key);
beginInsightUpdate(key);
requestRiskInsight(risk).then((response) => {
if (!response.ok) return;
saveInsight({
return saveInsight({
versionId: risk.versionId,
riskSignature: signature,
insight: sanitizeRiskInsight(response.result),
generatedAt: new Date().toISOString(),
providerInfo: { model: response.meta.model },
}).catch(() => {});
}).catch(() => {});
}).catch(() => {}).finally(() => {
finishInsightUpdate(key);
});
});
}, [insights, riskDataLoaded, risks, saveInsight, snapshots, today]);
}, [
beginInsightUpdate,
finishInsightUpdate,
insights,
pendingInsightKeys,
riskDataLoaded,
risks,
saveInsight,
snapshots,
today,
]);
const risksWithInsight = useMemo(() => risks.map((risk) => {
const cached = getReusableInsight(insights, risk);
return cached ? { ...risk, aiInsight: sanitizeRiskInsight(cached.insight) } : risk;
}), [insights, risks]);
const risksWithInsight = useMemo(() => risks.map((risk) => attachXiaobaoRiskSuggestion(risk, {
insights,
pendingInsightKeys,
})), [insights, pendingInsightKeys, risks]);
const warningRisks = useMemo(() => filterXiaobaoRiskWarnings(risksWithInsight), [risksWithInsight]);
const productOptions = useMemo(() => buildProductOptions(warningRisks), [warningRisks]);
@@ -261,6 +287,7 @@ function XiaobaoWarningDetailPanel({ risk, onNavigate }: { risk: XiaobaoVersionR
</div>
<Section title="小宝建议">
{risk.aiInsightUpdating && <InsightUpdatingNotice source={risk.aiInsightSource} />}
{risk.aiInsight ? (
<div className="space-y-3">
<div className="rounded-lg border border-[var(--line)] bg-[var(--bg-card)] p-4">
@@ -357,6 +384,18 @@ function XiaobaoWarningDetailPanel({ risk, onNavigate }: { risk: XiaobaoVersionR
);
}
function InsightUpdatingNotice({ source }: { source?: XiaobaoVersionRisk['aiInsightSource'] }) {
const detail = source === 'previous_ai'
? '当前先显示上一条建议,生成完成后会自动替换。'
: '当前先显示规则建议,生成完成后会自动替换。';
return (
<div className="flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] leading-5 text-amber-800">
<Loader2 className="mt-0.5 h-3.5 w-3.5 shrink-0 animate-spin" />
<span>{detail}</span>
</div>
);
}
function RiskLevelBadge({ level }: { level: XiaobaoRiskLevel }) {
return (
<span className={`inline-flex shrink-0 rounded-full border px-2 py-0.5 text-[11px] font-medium ${RISK_LEVEL_STYLE[level]}`}>