fix: speed up rectification start

This commit is contained in:
Jesse_Chen
2026-07-25 22:11:13 +08:00
parent 3e56389aeb
commit 031c275324
5 changed files with 192 additions and 74 deletions
+23 -19
View File
@@ -2087,10 +2087,11 @@ export default function Home() {
: resumableSession ?? createSession(modelCatalog.defaultModelId, "birth_time_rectification");
const reusingRectificationSession = canReuseSourceRectificationSession
|| resumableSession !== null;
const localHandoff = rectificationQuestionHandoff.current.peek();
const requestedQuestion = pendingConsultationQuestion
?? (reusingRectificationSession
? null
: rectificationQuestionHandoff.current.peek()?.question)
: localHandoff?.question)
?? null;
rectificationOpenInFlight.current = true;
setDraft("");
@@ -2116,7 +2117,9 @@ export default function Home() {
try {
let turn: ConversationalRectificationTurn;
if (!resumeTarget) {
const durable = await durableRectificationQuestionHandoff.current.load();
const durable = requestedQuestion !== null || localHandoff !== null
? await durableRectificationQuestionHandoff.current.load()
: null;
if (durable && durable.status !== "consumed") {
turn = durable.turn;
} else {
@@ -2226,26 +2229,27 @@ export default function Home() {
? rectificationSession.updatedAt
: timestamp(),
};
let sessionSyncFailed = false;
if (!reusingRectificationSession) {
try {
await persistSession(boundSession, "create");
} catch {
sessionSyncFailed = true;
}
setSessions((current) => [boundSession, ...current.filter((session) => session.id !== boundSession.id)]);
} else if (boundSession !== rectificationSession) {
updateSession(rectificationSession.id, () => boundSession);
try {
await persistSession(boundSession);
} catch {
sessionSyncFailed = true;
}
}
setRectificationInitialTurn(turn);
setRectificationOpeningAssistantText("");
synchronizeRectificationQuestion(turn, sourceSession);
setComposerNotice(sessionSyncFailed ? "校正已经开始,但会话关联暂时未同步到云端。" : "");
setComposerNotice("");
if (!reusingRectificationSession) {
setSessions((current) => [boundSession, ...current.filter((session) => session.id !== boundSession.id)]);
void rectificationPersistence.current.enqueue(
boundSession.id,
() => persistSession(boundSession, "create"),
).catch(() => {
setComposerNotice("校正已经开始,但会话关联暂时未同步到云端。");
});
} else if (boundSession !== rectificationSession) {
updateSession(rectificationSession.id, () => boundSession);
void rectificationPersistence.current.enqueue(
boundSession.id,
() => persistSession(boundSession),
).catch(() => {
setComposerNotice("校正已经开始,但会话关联暂时未同步到云端。");
});
}
} catch (caught) {
const message = caught instanceof Error
? caught.message
@@ -559,6 +559,89 @@ function midpointOfRange(range: Readonly<{ startTime: string; endTime: string }>
return clock(Math.round((start + end) / 2));
}
function declaredRange(input: DeclaredBirthInput): Readonly<{ startTime: string; endTime: string }> {
if (input.source === "period_only") {
return {
early_morning: { startTime: "04:00", endTime: "07:59" },
morning: { startTime: "08:00", endTime: "11:59" },
afternoon: { startTime: "12:00", endTime: "17:59" },
evening: { startTime: "18:00", endTime: "22:59" },
late_night: { startTime: "23:00", endTime: "03:59" },
}[input.reportedPeriod];
}
if (input.source === "unknown") return { startTime: "00:00", endTime: "23:59" };
if (input.source === "legacy_import" && !input.reportedTime) {
return input.reportedPeriod
? declaredRange({ ...input, source: "period_only", reportedPeriod: input.reportedPeriod })
: { startTime: "00:00", endTime: "23:59" };
}
if (!input.reportedTime) throw new ConversationalRectificationError("profile_incomplete");
const minute = (value: string) => {
const [hour = 0, part = 0] = value.split(":").map(Number);
return hour * 60 + part;
};
const clock = (value: number) => {
const normalized = ((value % 1_440) + 1_440) % 1_440;
return `${String(Math.floor(normalized / 60)).padStart(2, "0")}:${String(normalized % 60).padStart(2, "0")}`;
};
return {
startTime: clock(minute(input.reportedTime) - (input.uncertaintyBeforeMinutes ?? 2)),
endTime: clock(minute(input.reportedTime) + (input.uncertaintyAfterMinutes ?? 2)),
};
}
function openingRectificationState(input: {
readonly caseId: string;
readonly declaredBirthInput: DeclaredBirthInput;
readonly pendingConsultationQuestion: string | null;
}) {
const range = declaredRange(input.declaredBirthInput);
const representativeTime = midpointOfRange(range);
const calculationVersion = "rectification-opening-v1";
const turn = conversationalRectificationTurnSchema.parse({
caseId: input.caseId,
journeyProtocol: "conversational-evidence-v3",
status: "active",
turnVersion: 0,
narrative: "我们先从一件时间最明确、影响比较大的真实经历开始。请只说一件,并告诉我大约发生在哪一年、哪一月?",
candidate: {
status: "pending_validation",
representativeTime,
rangeStart: range.startTime,
rangeEnd: range.endTime,
},
technicalReceipt: {
calculationVersion,
stableLayers: [],
sensitiveLayers: [],
candidateDifferenceRefs: [],
},
evidenceRequest: {
domains: ["career", "education", "relocation", "relationship"],
datePrecision: "month_preferred",
freeTextAllowed: true,
prompt: "请说一件已经发生、时间比较明确的重要经历,并告诉我大约是哪一年、哪一月?",
followUp: { kind: "new_event", evidenceId: null },
},
evidenceRecap: [],
actions: ["answer", "pause", "abandon"],
pendingConsultationQuestion: input.pendingConsultationQuestion,
});
const privateCandidate = privateCandidateSchema.parse({
resultId: null,
representativeTime,
rangeStart: range.startTime,
rangeEnd: range.endTime,
calculationVersion,
workingState: { phase: "collecting_evidence", iteration: 0, notes: [] },
});
return {
turn,
privateCandidate,
validationReceipt: transitionReceipt("deterministic-rectification-opening"),
};
}
type CorrectionResetReason =
| "needs_clarification"
| "non_scoreable"
@@ -1193,33 +1276,10 @@ export function createConversationalRectificationService(
});
reserved = reservation.billingState === "reserved";
if (reserved) lastTelemetryOutcome = { billingState: "unknown", caseStatus: null };
const computed = await ports.buildTechnicalPacket({
userId,
const opening = openingRectificationState({
caseId,
asOfDate: ports.asOfDate(),
declaredBirthInput: declared.data,
privateCandidate: null,
evidence: [],
});
const gatedPacket = confirmationGatedPacket(computed.packet, 0, 0);
const narrative = await generateRectificationNarrative({
phase: "first",
packet: gatedPacket,
generator: ports.narrativeGenerator,
});
const privateCandidate = privateCandidateFromPacket({
packet: gatedPacket,
resultId: null,
iteration: 0,
forceCollecting: true,
});
const firstTurn = turnFromNarrative({
caseId,
turnVersion: 0,
pendingConsultationQuestion: command.pendingConsultationQuestion ?? null,
packet: gatedPacket,
narrative,
evidence: [],
declaredBirthInput: declared.data,
});
const created = await ports.store.createCaseWithFirstTurn({
userId,
@@ -1229,9 +1289,9 @@ export function createConversationalRectificationService(
revisionOfCaseId: profile.revisionOfCaseId,
pendingConsultationQuestion: command.pendingConsultationQuestion ?? null,
declaredBirthInput: declared.data,
firstTurn,
validationReceipt: narrative.validationReceipt,
privateCandidate,
firstTurn: opening.turn,
validationReceipt: opening.validationReceipt,
privateCandidate: opening.privateCandidate,
});
observeCase(created, "unknown");
await ports.billing.complete({