fix(consult): preserve streaming across disconnects
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { runCreditRpc } from "@/lib/consultation-billing";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const idSchema = z.string().uuid();
|
||||
const reservedLeaseMs = 15 * 60 * 1000;
|
||||
// ponytail: Staging MVP ceiling—without a queue/worker, status lazily refunds reservations after this lease.
|
||||
function reservationLeaseExpired(updatedAt: unknown, now = Date.now()) {
|
||||
if (typeof updatedAt !== "string") return false;
|
||||
const timestamp = Date.parse(updatedAt);
|
||||
return Number.isFinite(timestamp) && now - timestamp >= reservedLeaseMs;
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
let supabase: Awaited<ReturnType<typeof createServerSupabaseClient>>;
|
||||
try {
|
||||
supabase = await createServerSupabaseClient();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "服务尚未配置" }, { status: 503 });
|
||||
}
|
||||
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
||||
if (authError || !user) {
|
||||
return NextResponse.json({ error: "请先登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const params = new URL(request.url).searchParams;
|
||||
const sessionIdValue = params.get("sessionId");
|
||||
const requestIdValue = params.get("requestId");
|
||||
const activeLookup = sessionIdValue === null && requestIdValue === null;
|
||||
if (!activeLookup && (sessionIdValue === null || requestIdValue === null)) {
|
||||
return NextResponse.json({ error: "咨询请求格式不正确" }, { status: 400 });
|
||||
}
|
||||
|
||||
const sessionId = sessionIdValue === null ? null : idSchema.safeParse(sessionIdValue);
|
||||
const requestId = requestIdValue === null ? null : idSchema.safeParse(requestIdValue);
|
||||
if ((sessionId && !sessionId.success) || (requestId && !requestId.success)) {
|
||||
return NextResponse.json({ error: "咨询请求格式不正确" }, { status: 400 });
|
||||
}
|
||||
|
||||
let accounting: ReturnType<typeof createAdminSupabaseClient>;
|
||||
try {
|
||||
accounting = createAdminSupabaseClient();
|
||||
} catch {
|
||||
return NextResponse.json({ error: "服务尚未配置" }, { status: 503 });
|
||||
}
|
||||
|
||||
let query = accounting
|
||||
.from("consultation_requests")
|
||||
.select("request_id,session_id,status,response_message,updated_at")
|
||||
.eq("user_id", user.id);
|
||||
|
||||
if (activeLookup) {
|
||||
query = query.eq("status", "reserved").order("created_at", { ascending: false }).limit(1);
|
||||
} else {
|
||||
query = query
|
||||
.eq("session_id", sessionId!.data)
|
||||
.eq("request_id", requestId!.data);
|
||||
}
|
||||
|
||||
const { data, error } = await query.maybeSingle();
|
||||
if (error) {
|
||||
return NextResponse.json({ error: "暂时无法读取咨询状态" }, { status: 503 });
|
||||
}
|
||||
if (!data || typeof data.session_id !== "string") {
|
||||
return NextResponse.json({ error: "咨询请求不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
let statusData = data;
|
||||
if (statusData.status === "reserved" && reservationLeaseExpired(statusData.updated_at)) {
|
||||
let cancellation;
|
||||
try {
|
||||
cancellation = await runCreditRpc(
|
||||
accounting,
|
||||
"cancel_consultation_credit",
|
||||
user.id,
|
||||
statusData.request_id,
|
||||
);
|
||||
} catch {
|
||||
return NextResponse.json({ error: "暂时无法回收超时咨询" }, { status: 503 });
|
||||
}
|
||||
if (!cancellation.success && cancellation.error_code !== "request_completed") {
|
||||
return NextResponse.json({ error: "暂时无法回收超时咨询" }, { status: 503 });
|
||||
}
|
||||
|
||||
const { data: settledData, error: settledError } = await accounting
|
||||
.from("consultation_requests")
|
||||
.select("request_id,session_id,status,response_message,updated_at")
|
||||
.eq("user_id", user.id)
|
||||
.eq("session_id", statusData.session_id)
|
||||
.eq("request_id", statusData.request_id)
|
||||
.maybeSingle();
|
||||
if (settledError || !settledData || typeof settledData.session_id !== "string") {
|
||||
return NextResponse.json({ error: "暂时无法读取咨询状态" }, { status: 503 });
|
||||
}
|
||||
statusData = settledData;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
requestId: statusData.request_id,
|
||||
sessionId: statusData.session_id,
|
||||
status: statusData.status,
|
||||
responseMessage: statusData.response_message,
|
||||
updatedAt: statusData.updated_at,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user