fix(小宝预警): 保证建议不为空并显示更新中
This commit is contained in:
@@ -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]}`}>
|
||||
|
||||
Reference in New Issue
Block a user