fix(chat): make the server the only writer of session messages
List GET no longer ships transcripts; consult appends questions after reserve and ignores client history so dual-tab last-write-wins cannot erase messages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -71,6 +71,7 @@ import {
|
||||
loadGeneralDailyPanchangaContext,
|
||||
type GeneralDailyPanchangaContext,
|
||||
} from "@/lib/general-daily-panchanga";
|
||||
import { consultationHistoryFromStoredMessages } from "@/lib/consultation-session-history";
|
||||
import { z } from "zod";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
@@ -94,6 +95,7 @@ const chatRequestMetadataSchema = z.object({
|
||||
}),
|
||||
)
|
||||
.max(20)
|
||||
.optional()
|
||||
.default([]),
|
||||
});
|
||||
|
||||
@@ -143,6 +145,11 @@ const consultationCompletionSchema = z.object({
|
||||
error_code: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
const consultationQuestionAppendSchema = z.object({
|
||||
success: z.boolean(),
|
||||
error_code: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
function first<T>(value: T | T[]): T {
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
@@ -273,7 +280,7 @@ export async function POST(request: Request) {
|
||||
|
||||
const { data: chatSession, error: chatSessionError } = await supabase
|
||||
.from("chat_sessions")
|
||||
.select("id,model_id,model_config_version,session_type")
|
||||
.select("id,model_id,model_config_version,session_type,messages")
|
||||
.eq("id", parsed.data.sessionId)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle();
|
||||
@@ -329,11 +336,11 @@ export async function POST(request: Request) {
|
||||
const consultationTheme = parsed.data.theme;
|
||||
const visibleQuestion = parsed.data.question;
|
||||
|
||||
// Client `history` stays in the request schema for old bundles and is not read.
|
||||
const storedHistory = consultationHistoryFromStoredMessages(chatSession.messages);
|
||||
const userControlledPrompt = [
|
||||
parsed.data.question,
|
||||
...parsed.data.history
|
||||
.filter((message) => message.role === "user")
|
||||
.map((message) => message.text),
|
||||
...storedHistory.filter((message) => message.role === "user").map((message) => message.text),
|
||||
].join("\n");
|
||||
if (blocksPromptExtraction(userControlledPrompt)) {
|
||||
return NextResponse.json(
|
||||
@@ -504,6 +511,46 @@ export async function POST(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
let appendedQuestion: { success: boolean; error_code?: string | null };
|
||||
try {
|
||||
appendedQuestion = await retryDetachedSettlement(async () => {
|
||||
const { data, error } = await accounting.rpc("append_consultation_question", {
|
||||
p_user_id: userId,
|
||||
p_request_id: requestId,
|
||||
p_session_id: sessionId,
|
||||
p_question_message: { role: "user", text: visibleQuestion },
|
||||
});
|
||||
const parsedAppend = consultationQuestionAppendSchema.safeParse(first(data ?? []));
|
||||
if (error || !parsedAppend.success) {
|
||||
throw new CreditRpcError(error?.message || "invalid_question_append_response");
|
||||
}
|
||||
return parsedAppend.data;
|
||||
});
|
||||
} catch {
|
||||
await cancel();
|
||||
return NextResponse.json(
|
||||
{ error: "暂时无法保存问题", message: "请稍后重试。" },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
if (!appendedQuestion.success) {
|
||||
await cancel();
|
||||
if (appendedQuestion.error_code === "session_full") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "这段对话已写满",
|
||||
message: "这段对话已写满,开个新对话继续吧",
|
||||
code: "session_full",
|
||||
},
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
return NextResponse.json(
|
||||
{ error: "暂时无法保存问题", message: "请稍后重试。" },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
const usageStartedAt = Date.now();
|
||||
async function usagePayload(usage: Promise<{ inputTokens?: number; outputTokens?: number }>) {
|
||||
const resolved = await usage;
|
||||
@@ -1044,7 +1091,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { history } = parsed.data;
|
||||
const history = storedHistory;
|
||||
const name = prepared.serverChart?.name ?? prepared.declaredWindow?.name ?? parsed.data.name;
|
||||
const consultationMode: ConsultationBirthTimeMode = prepared.consultationMode;
|
||||
const generalDailyContext = shouldLoadGeneralDailyPanchanga({
|
||||
|
||||
Reference in New Issue
Block a user