fix(小宝预警): 等待缓存加载后再触发AI建议
This commit is contained in:
@@ -18,7 +18,7 @@ import { useWorkActivityStore } from '@/stores/useWorkActivityStore';
|
|||||||
import { useXiaobaoRiskStore } from '@/stores/useXiaobaoRiskStore';
|
import { useXiaobaoRiskStore } from '@/stores/useXiaobaoRiskStore';
|
||||||
import { flattenVersions } from '@/lib/derive';
|
import { flattenVersions } from '@/lib/derive';
|
||||||
import { calcXiaobaoVersionRisk, type XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
import { calcXiaobaoVersionRisk, type XiaobaoVersionRisk } from '@/lib/xiaobao-risk';
|
||||||
import { buildRiskInsightSignature, findLatestRiskInsightForVersion, findPreviousRiskSnapshot, getReusableInsight, requestRiskInsight, shouldRequestRiskInsightWithCooldown } from '@/lib/xiaobao-risk-ai';
|
import { buildRiskInsightSignature, findPreviousRiskSnapshot, getReusableInsight, requestRiskInsight, shouldRequestRiskInsightWithCacheGate } from '@/lib/xiaobao-risk-ai';
|
||||||
import { buildVersionDailyEvidence, buildXiaobaoWorkItems } from '@/lib/xiaobao-risk-evidence';
|
import { buildVersionDailyEvidence, buildXiaobaoWorkItems } from '@/lib/xiaobao-risk-evidence';
|
||||||
import { buildRiskSignature, findLatestDailySnapshot, shouldSaveRiskSnapshot } from '@/lib/xiaobao-risk-trend';
|
import { buildRiskSignature, findLatestDailySnapshot, shouldSaveRiskSnapshot } from '@/lib/xiaobao-risk-trend';
|
||||||
import { filterXiaobaoWarningVersions, sanitizeRiskInsight } from '@/lib/xiaobao-warning-view';
|
import { filterXiaobaoWarningVersions, sanitizeRiskInsight } from '@/lib/xiaobao-warning-view';
|
||||||
@@ -45,7 +45,7 @@ function XiaobaoWarningContent() {
|
|||||||
const { bugs, fetchBugs } = useBugStore();
|
const { bugs, fetchBugs } = useBugStore();
|
||||||
const { activities, fetchActivities } = useWorkActivityStore();
|
const { activities, fetchActivities } = useWorkActivityStore();
|
||||||
const { worklogs, fetchWorklogs } = useTaskWorklogStore();
|
const { worklogs, fetchWorklogs } = useTaskWorklogStore();
|
||||||
const { snapshots, insights, fetchRiskData, saveSnapshot, saveInsight } = useXiaobaoRiskStore();
|
const { snapshots, insights, riskDataLoaded, fetchRiskData, saveSnapshot, saveInsight } = useXiaobaoRiskStore();
|
||||||
const [selectedRiskId, setSelectedRiskId] = useState<string | null>(null);
|
const [selectedRiskId, setSelectedRiskId] = useState<string | null>(null);
|
||||||
const [filter, setFilter] = useState<RiskFilter>('all');
|
const [filter, setFilter] = useState<RiskFilter>('all');
|
||||||
const [calculationNow] = useState(() => new Date());
|
const [calculationNow] = useState(() => new Date());
|
||||||
@@ -142,10 +142,8 @@ function XiaobaoWarningContent() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
risks.forEach((risk) => {
|
risks.forEach((risk) => {
|
||||||
if (getReusableInsight(insights, risk)) return;
|
|
||||||
const previous = findPreviousRiskSnapshot(snapshots, risk.versionId, today);
|
const previous = findPreviousRiskSnapshot(snapshots, risk.versionId, today);
|
||||||
const latestInsight = findLatestRiskInsightForVersion(insights, risk.versionId);
|
if (!shouldRequestRiskInsightWithCacheGate(riskDataLoaded, insights, risk, previous)) return;
|
||||||
if (!shouldRequestRiskInsightWithCooldown(risk, previous, latestInsight)) return;
|
|
||||||
const signature = buildRiskInsightSignature(risk);
|
const signature = buildRiskInsightSignature(risk);
|
||||||
const key = `${risk.versionId}:${signature}`;
|
const key = `${risk.versionId}:${signature}`;
|
||||||
if (requestedInsightKeysRef.current.has(key)) return;
|
if (requestedInsightKeysRef.current.has(key)) return;
|
||||||
@@ -161,7 +159,7 @@ function XiaobaoWarningContent() {
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
});
|
});
|
||||||
}, [insights, risks, saveInsight, snapshots, today]);
|
}, [insights, riskDataLoaded, risks, saveInsight, snapshots, today]);
|
||||||
|
|
||||||
const risksWithInsight = useMemo(() => risks.map((risk) => {
|
const risksWithInsight = useMemo(() => risks.map((risk) => {
|
||||||
const cached = getReusableInsight(insights, risk);
|
const cached = getReusableInsight(insights, risk);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
findLatestRiskInsightForVersion,
|
findLatestRiskInsightForVersion,
|
||||||
findPreviousRiskSnapshot,
|
findPreviousRiskSnapshot,
|
||||||
shouldRequestRiskInsight,
|
shouldRequestRiskInsight,
|
||||||
|
shouldRequestRiskInsightWithCacheGate,
|
||||||
shouldRequestRiskInsightWithCooldown,
|
shouldRequestRiskInsightWithCooldown,
|
||||||
} from './xiaobao-risk-ai';
|
} from './xiaobao-risk-ai';
|
||||||
import type { XiaobaoRiskInsightCacheItem } from './xiaobao-risk-cache';
|
import type { XiaobaoRiskInsightCacheItem } from './xiaobao-risk-cache';
|
||||||
@@ -243,6 +244,41 @@ test('shouldRequestRiskInsightWithCooldown bypasses cooldown when risk level esc
|
|||||||
assert.equal(shouldRequestRiskInsightWithCooldown(current, snapshot({ riskScore: 50 }), previousInsight, new Date('2026-06-29T11:00:00.000Z')), true);
|
assert.equal(shouldRequestRiskInsightWithCooldown(current, snapshot({ riskScore: 50 }), previousInsight, new Date('2026-06-29T11:00:00.000Z')), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('shouldRequestRiskInsightWithCacheGate waits for cache loading before AI requests', () => {
|
||||||
|
const current = risk({ riskLevel: 'at_risk', riskScore: 82 });
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
shouldRequestRiskInsightWithCacheGate(false, [], current, snapshot({ riskScore: 45 }), new Date('2026-06-29T11:00:00.000Z')),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shouldRequestRiskInsightWithCacheGate reuses unchanged cached insight after cache loading', () => {
|
||||||
|
const current = risk({ riskLevel: 'at_risk', riskScore: 82 });
|
||||||
|
const cached = insight({
|
||||||
|
riskSignature: buildRiskInsightSignature(current),
|
||||||
|
generatedAt: '2026-06-29T02:00:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
shouldRequestRiskInsightWithCacheGate(true, [cached], current, snapshot({ riskScore: 45 }), new Date('2026-06-29T11:00:00.000Z')),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shouldRequestRiskInsightWithCacheGate allows changed risk facts after cache loading and cooldown', () => {
|
||||||
|
const current = risk({ riskLevel: 'at_risk', riskScore: 82 });
|
||||||
|
const previousInsight = insight({
|
||||||
|
riskSignature: buildRiskInsightSignature(risk({ riskLevel: 'at_risk', riskScore: 62 })),
|
||||||
|
generatedAt: '2026-06-29T02:00:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
shouldRequestRiskInsightWithCacheGate(true, [previousInsight], current, snapshot({ riskScore: 45 }), new Date('2026-06-29T11:00:00.000Z')),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('buildRiskInsightSignature uses all open bugs, not only critical bugs', () => {
|
test('buildRiskInsightSignature uses all open bugs, not only critical bugs', () => {
|
||||||
const base = buildRiskInsightSignature(risk({
|
const base = buildRiskInsightSignature(risk({
|
||||||
signals: { ...risk().signals, openBugCount: 1, criticalBugCount: 0 },
|
signals: { ...risk().signals, openBugCount: 1, criticalBugCount: 0 },
|
||||||
|
|||||||
@@ -90,6 +90,19 @@ export function shouldRequestRiskInsightWithCooldown(
|
|||||||
return nowTime - generatedAt >= RISK_INSIGHT_COOLDOWN_MS;
|
return nowTime - generatedAt >= RISK_INSIGHT_COOLDOWN_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shouldRequestRiskInsightWithCacheGate(
|
||||||
|
riskCacheLoaded: boolean,
|
||||||
|
cache: XiaobaoRiskInsightCacheItem[],
|
||||||
|
current: XiaobaoVersionRisk,
|
||||||
|
previous?: RiskInsightPrevious,
|
||||||
|
now: Date = new Date(),
|
||||||
|
): boolean {
|
||||||
|
if (!riskCacheLoaded) return false;
|
||||||
|
if (getReusableInsight(cache, current)) return false;
|
||||||
|
const latestInsight = findLatestRiskInsightForVersion(cache, current.versionId);
|
||||||
|
return shouldRequestRiskInsightWithCooldown(current, previous, latestInsight, now);
|
||||||
|
}
|
||||||
|
|
||||||
export function buildRiskInsightSignature(risk: XiaobaoVersionRisk): string {
|
export function buildRiskInsightSignature(risk: XiaobaoVersionRisk): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
versionId: risk.versionId,
|
versionId: risk.versionId,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { XiaobaoRiskSnapshot } from '@/lib/xiaobao-risk-trend';
|
|||||||
interface XiaobaoRiskState {
|
interface XiaobaoRiskState {
|
||||||
snapshots: XiaobaoRiskSnapshot[];
|
snapshots: XiaobaoRiskSnapshot[];
|
||||||
insights: XiaobaoRiskInsightCacheItem[];
|
insights: XiaobaoRiskInsightCacheItem[];
|
||||||
|
riskDataLoaded: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
fetchRiskData: () => Promise<void>;
|
fetchRiskData: () => Promise<void>;
|
||||||
saveSnapshot: (item: XiaobaoRiskSnapshot) => Promise<void>;
|
saveSnapshot: (item: XiaobaoRiskSnapshot) => Promise<void>;
|
||||||
@@ -41,13 +42,16 @@ let insightSaveQueue: Promise<void> = Promise.resolve();
|
|||||||
export const useXiaobaoRiskStore = create<XiaobaoRiskState>((set, get) => ({
|
export const useXiaobaoRiskStore = create<XiaobaoRiskState>((set, get) => ({
|
||||||
snapshots: [],
|
snapshots: [],
|
||||||
insights: [],
|
insights: [],
|
||||||
|
riskDataLoaded: false,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
|
|
||||||
fetchRiskData: async () => {
|
fetchRiskData: async () => {
|
||||||
|
set({ riskDataLoaded: false });
|
||||||
const [snapshots, insights] = await Promise.all([loadSnapshots(), loadInsights()]);
|
const [snapshots, insights] = await Promise.all([loadSnapshots(), loadInsights()]);
|
||||||
set({
|
set({
|
||||||
...(snapshots ? { snapshots } : {}),
|
...(snapshots ? { snapshots } : {}),
|
||||||
...(insights ? { insights } : {}),
|
...(insights ? { insights } : {}),
|
||||||
|
riskDataLoaded: snapshots !== null && insights !== null,
|
||||||
error: snapshots === null || insights === null ? '小宝预警缓存加载失败' : undefined,
|
error: snapshots === null || insights === null ? '小宝预警缓存加载失败' : undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user