fix(consult): resolve chat subject from server-owned profile binding
This commit is contained in:
@@ -74,6 +74,11 @@ import {
|
||||
prepareConsultationRoute,
|
||||
type PreparedConsultationRoute,
|
||||
} from "@/lib/consultation-route-service";
|
||||
import {
|
||||
ConsultationSubjectError,
|
||||
consultationSubjectBindingFromSession,
|
||||
consultationSubjectFailureResponse,
|
||||
} from "@/lib/consultation-subject-resolver";
|
||||
import { loadReportCandidateRange } from "@/lib/report-candidate-range";
|
||||
import {
|
||||
loadGeneralDailyPanchangaContext,
|
||||
@@ -297,7 +302,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,messages,title,theme,chart_profile_role,context_summary")
|
||||
.select("id,model_id,model_config_version,session_type,messages,title,theme,chart_profile_id,chart_profile_name,chart_profile_role,context_summary")
|
||||
.eq("id", parsed.data.sessionId)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle();
|
||||
@@ -393,6 +398,23 @@ export async function POST(request: Request) {
|
||||
prepared = await prepareConsultationRoute<ModelSelection, ConsultationPlan>({
|
||||
userId,
|
||||
mode: parsed.data.consultationMode,
|
||||
subject: {
|
||||
binding: consultationSubjectBindingFromSession(chatSession),
|
||||
clientBirth: parsed.data,
|
||||
async loadOwnedChartProfile(chartProfileId) {
|
||||
const { data, error } = await supabase
|
||||
.from("chart_profiles")
|
||||
.select("id,user_id,role,profile")
|
||||
.eq("id", chartProfileId)
|
||||
.eq("user_id", userId)
|
||||
.maybeSingle();
|
||||
if (error) throw new ConsultationSubjectError("subject_unavailable");
|
||||
if (!data || typeof data.id !== "string" || typeof data.user_id !== "string" || typeof data.role !== "string") {
|
||||
return null;
|
||||
}
|
||||
return { id: data.id, userId: data.user_id, role: data.role, profile: data.profile };
|
||||
},
|
||||
},
|
||||
async loadProfile(profileUserId) {
|
||||
const { data, error } = await supabase
|
||||
.from("profiles")
|
||||
@@ -458,6 +480,10 @@ export async function POST(request: Request) {
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
if (error instanceof ConsultationSubjectError) {
|
||||
const failure = consultationSubjectFailureResponse(error);
|
||||
return NextResponse.json(failure.body, { status: failure.status });
|
||||
}
|
||||
if (error instanceof ConsultationProfileTruthError) {
|
||||
const modeChanged = error.code === "mode_changed";
|
||||
return NextResponse.json(
|
||||
@@ -483,6 +509,10 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const subjectName = prepared.subject?.name
|
||||
|| prepared.serverChart?.name
|
||||
|| prepared.declaredWindow?.name
|
||||
|| "";
|
||||
const resolvedQuestion = resolveConsultationQuestion({
|
||||
visibleQuestion: parsed.data.question,
|
||||
entrypoint: parsed.data.entrypoint,
|
||||
@@ -593,7 +623,7 @@ export async function POST(request: Request) {
|
||||
model: selectedModel,
|
||||
question: visibleQuestion,
|
||||
history: chatSession.messages,
|
||||
name: parsed.data.name,
|
||||
name: subjectName,
|
||||
signal: request.signal,
|
||||
onObservation(observation) {
|
||||
if (!observation.late) {
|
||||
@@ -803,7 +833,7 @@ export async function POST(request: Request) {
|
||||
model: selectedModel,
|
||||
question: parsed.data.question,
|
||||
theme: consultationTheme,
|
||||
chartRole: chatSession.chart_profile_role === "other" ? "other" : "self",
|
||||
chartRole: prepared.subject?.role === "other" ? "other" : "self",
|
||||
signal: request.signal,
|
||||
}).then(async (title) => {
|
||||
if (!title) return null;
|
||||
@@ -1359,7 +1389,7 @@ export async function POST(request: Request) {
|
||||
|
||||
try {
|
||||
const history = storedHistory;
|
||||
const name = prepared.serverChart?.name ?? prepared.declaredWindow?.name ?? parsed.data.name;
|
||||
const name = subjectName;
|
||||
const consultationMode: ConsultationBirthTimeMode = prepared.consultationMode;
|
||||
const generalDailyContext = shouldLoadGeneralDailyPanchanga({
|
||||
consultationMode,
|
||||
|
||||
@@ -10,6 +10,15 @@ import {
|
||||
import { logIgnoredSessionMessages } from "@/lib/chat-session-observability";
|
||||
import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
|
||||
import { metadataUpdateValues } from "@/lib/session-metadata-update";
|
||||
import {
|
||||
authoritativeSessionBinding,
|
||||
ConsultationSubjectError,
|
||||
consultationSubjectBindingFromSession,
|
||||
consultationSubjectFailureResponse,
|
||||
ownedChartProfileFromResult,
|
||||
selfNameFromResult,
|
||||
sessionBindingRequested,
|
||||
} from "@/lib/consultation-subject-resolver";
|
||||
|
||||
type RouteContext = { params: Promise<{ id: string }> };
|
||||
|
||||
@@ -91,6 +100,34 @@ export async function PATCH(request: Request, context: RouteContext) {
|
||||
if (!values) {
|
||||
return NextResponse.json({ error: "聊天记录格式不正确" }, { status: 400 });
|
||||
}
|
||||
if (sessionBindingRequested(values)) {
|
||||
const { data: existing, error: existingError } = await supabase
|
||||
.from("chat_sessions")
|
||||
.select("messages,chart_profile_id,chart_profile_name,chart_profile_role")
|
||||
.eq("id", id)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle();
|
||||
if (existingError) return NextResponse.json({ error: "聊天记录暂时无法同步" }, { status: 500 });
|
||||
if (!existing) return NextResponse.json({ error: "聊天记录不存在或已被删除" }, { status: 404 });
|
||||
const binding = await authoritativeSessionBinding({
|
||||
userId: user.id,
|
||||
requested: values,
|
||||
existingMessages: existing.messages,
|
||||
existingBinding: consultationSubjectBindingFromSession(existing),
|
||||
loadSelfProfile: async (profileUserId) => selfNameFromResult(await supabase
|
||||
.from("profiles")
|
||||
.select("name")
|
||||
.eq("id", profileUserId)
|
||||
.maybeSingle()),
|
||||
loadOwnedChartProfile: async (chartProfileId) => ownedChartProfileFromResult(await supabase
|
||||
.from("chart_profiles")
|
||||
.select("id,user_id,role,profile")
|
||||
.eq("id", chartProfileId)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle()),
|
||||
});
|
||||
if (binding) values = { ...values, ...binding };
|
||||
}
|
||||
const { data, error } = await supabase
|
||||
.from("chat_sessions")
|
||||
.update(values)
|
||||
@@ -102,6 +139,10 @@ export async function PATCH(request: Request, context: RouteContext) {
|
||||
if (!data) return NextResponse.json({ error: "聊天记录不存在或已被删除" }, { status: 404 });
|
||||
return NextResponse.json({ ok: true });
|
||||
} catch (error) {
|
||||
if (error instanceof ConsultationSubjectError) {
|
||||
const failure = consultationSubjectFailureResponse(error);
|
||||
return NextResponse.json(failure.body, { status: failure.status });
|
||||
}
|
||||
if (error instanceof ChatSessionBodyTooLargeError) {
|
||||
return NextResponse.json({ error: error.message }, { status: 413 });
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ import {
|
||||
ChatSessionBodyTooLargeError,
|
||||
readChatSessionJson,
|
||||
} from "@/lib/chat-session-write-contract";
|
||||
import {
|
||||
authoritativeSessionBinding,
|
||||
ConsultationSubjectError,
|
||||
consultationSubjectFailureResponse,
|
||||
ownedChartProfileFromResult,
|
||||
selfNameFromResult,
|
||||
} from "@/lib/consultation-subject-resolver";
|
||||
import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
|
||||
import { resolveInheritedContextSummary } from "@/lib/session-context-summary";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
@@ -93,6 +100,24 @@ export async function POST(request: Request) {
|
||||
const parsed = chatSessionCreateSchema.safeParse(await readChatSessionJson(request));
|
||||
if (!parsed.success) return NextResponse.json({ error: "聊天记录格式不正确" }, { status: 400 });
|
||||
const { id, updated_at: _ignoredClientClock, continued_from_session_id: continuedFromSessionId, ...values } = parsed.data;
|
||||
const binding = await authoritativeSessionBinding({
|
||||
userId: user.id,
|
||||
requested: values,
|
||||
existingMessages: values.messages,
|
||||
existingBinding: null,
|
||||
loadSelfProfile: async (profileUserId) => selfNameFromResult(await supabase
|
||||
.from("profiles")
|
||||
.select("name")
|
||||
.eq("id", profileUserId)
|
||||
.maybeSingle()),
|
||||
loadOwnedChartProfile: async (chartProfileId) => ownedChartProfileFromResult(await supabase
|
||||
.from("chart_profiles")
|
||||
.select("id,user_id,role,profile")
|
||||
.eq("id", chartProfileId)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle()),
|
||||
});
|
||||
const insertValues = binding ? { ...values, ...binding } : values;
|
||||
const inheritedSummary = await resolveInheritedContextSummary({
|
||||
continuedFromSessionId,
|
||||
loadOwnedSummary: async (sourceId) => {
|
||||
@@ -109,7 +134,7 @@ export async function POST(request: Request) {
|
||||
chatSessionCreateInsertRow({
|
||||
id,
|
||||
userId: user.id,
|
||||
values,
|
||||
values: insertValues,
|
||||
inheritedSummary,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
@@ -117,6 +142,10 @@ export async function POST(request: Request) {
|
||||
if (error) return NextResponse.json({ error: "聊天记录暂时无法同步" }, { status: 500 });
|
||||
return NextResponse.json({ ok: true }, { status: 201 });
|
||||
} catch (error) {
|
||||
if (error instanceof ConsultationSubjectError) {
|
||||
const failure = consultationSubjectFailureResponse(error);
|
||||
return NextResponse.json(failure.body, { status: failure.status });
|
||||
}
|
||||
if (error instanceof ChatSessionBodyTooLargeError) {
|
||||
return NextResponse.json({ error: error.message }, { status: 413 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user