feat: complete birth time consultation handoff
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { candidateWorkingTime } from "@/lib/birth-time-candidate-completion";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const requestSchema = z.object({
|
||||
caseId: z.string().uuid(),
|
||||
resultId: z.string().uuid(),
|
||||
time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/),
|
||||
}).strict();
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const supabase = await createServerSupabaseClient();
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
||||
if (authError || !user) {
|
||||
return NextResponse.json({ error: "请先登录" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsed = requestSchema.safeParse(await request.json().catch(() => null));
|
||||
if (!parsed.success) {
|
||||
return NextResponse.json({ error: "候选时间格式不正确" }, { status: 400 });
|
||||
}
|
||||
|
||||
const admin = createAdminSupabaseClient();
|
||||
const { data: stored, error: caseError } = await admin
|
||||
.from("birth_time_rectification_cases")
|
||||
.select("id,user_id,status,candidate_result_id,candidate_result,turn_state")
|
||||
.eq("id", parsed.data.caseId)
|
||||
.eq("user_id", user.id)
|
||||
.maybeSingle();
|
||||
const time = candidateWorkingTime(stored, parsed.data);
|
||||
if (caseError || !time) {
|
||||
return NextResponse.json(
|
||||
{ error: "候选结果已变化", message: "请使用当前评估结果继续。" },
|
||||
{ status: 409 },
|
||||
);
|
||||
}
|
||||
|
||||
const { data: profile, error: profileError } = await admin
|
||||
.from("profiles")
|
||||
.update({
|
||||
active_birth_time: time,
|
||||
birth_time_status: "candidate",
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq("id", user.id)
|
||||
.eq("rectification_case_id", parsed.data.caseId)
|
||||
.select("id")
|
||||
.maybeSingle();
|
||||
if (profileError || !profile) {
|
||||
return NextResponse.json(
|
||||
{ error: "候选时间暂时无法保存", message: "当前评估结果仍已保留,请稍后重试。" },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true, activeTime: time, birthTimeStatus: "candidate" });
|
||||
} catch (error) {
|
||||
if (isSupabaseConfigurationError(error)) {
|
||||
return NextResponse.json({ error: "Supabase 尚未配置" }, { status: 503 });
|
||||
}
|
||||
return NextResponse.json({ error: "候选时间暂时无法保存" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@/lib/birth-time-guide-service";
|
||||
import { BirthTimeJourneyActionError, createJourneyTurnActions } from "@/lib/birth-time-journey-actions";
|
||||
import { BirthTimeDynamicActionError } from "@/lib/birth-time-dynamic-actions";
|
||||
import { BirthTimeJourneyEngineError, createJyotishBirthTimeJourneyEngine } from "@/lib/birth-time-journey-engine";
|
||||
import { BirthTimeJourneyEngineConfigurationError, BirthTimeJourneyEngineError, createJyotishBirthTimeJourneyEngine } from "@/lib/birth-time-journey-engine";
|
||||
import { createBirthTimeJourneyService } from "@/lib/birth-time-journey-service";
|
||||
import {
|
||||
BirthTimeJourneyStoreError,
|
||||
@@ -166,6 +166,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
if (error instanceof BirthTimeJourneyStoreError
|
||||
|| error instanceof BirthTimeJourneyEngineError
|
||||
|| error instanceof BirthTimeJourneyEngineConfigurationError
|
||||
|| (error instanceof BirthTimeDynamicActionError && error.reason === "unavailable")) {
|
||||
return NextResponse.json(
|
||||
{ error: "生时引导暂时不可用", message: "当前资料已保留,请稍后重试。" },
|
||||
|
||||
@@ -8,6 +8,10 @@ import {
|
||||
resolveLanguageModel,
|
||||
} from "@/mastra/model";
|
||||
import { blocksPromptExtraction } from "@/lib/consult-safety";
|
||||
import {
|
||||
consultationEntrypointSchema,
|
||||
resolveConsultationQuestion,
|
||||
} from "@/lib/consultation-entrypoint";
|
||||
import { CreditRpcError, runCreditRpc } from "@/lib/consultation-billing";
|
||||
import { reserveConsultationModel } from "@/lib/consultation-model-selection";
|
||||
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
|
||||
@@ -21,6 +25,7 @@ export const maxDuration = 60;
|
||||
const chatRequestSchema = consultationInputSchema.extend({
|
||||
requestId: z.string().uuid(),
|
||||
modelId: z.string().trim().min(1).max(64),
|
||||
entrypoint: consultationEntrypointSchema.optional(),
|
||||
name: z.string().trim().max(80).optional().default(""),
|
||||
history: z.array(z.object({
|
||||
role: z.enum(["user", "assistant"]),
|
||||
@@ -36,6 +41,10 @@ function currentTimeContext(now = new Date()) {
|
||||
return `服务端当前时间(权威):${now.toISOString()};中国标准时间(UTC+8):${chinaTime}。涉及“现在、今天、今年、未来几个月”等相对时间时,以此为准。`;
|
||||
}
|
||||
|
||||
function chinaCalendarDate(now: Date) {
|
||||
return new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
async function recordModelUsage(
|
||||
accounting: ReturnType<typeof createAdminSupabaseClient>,
|
||||
userId: string,
|
||||
@@ -105,6 +114,13 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const requestTime = new Date();
|
||||
const resolvedQuestion = resolveConsultationQuestion({
|
||||
visibleQuestion: parsed.data.question,
|
||||
entrypoint: parsed.data.entrypoint,
|
||||
currentDate: chinaCalendarDate(requestTime),
|
||||
});
|
||||
|
||||
const userId = user.id;
|
||||
const requestId = parsed.data.requestId;
|
||||
let modelSelection;
|
||||
@@ -166,7 +182,10 @@ export async function POST(request: Request) {
|
||||
|
||||
try {
|
||||
const { history, name } = parsed.data;
|
||||
const toolInput = consultationInputSchema.parse(parsed.data);
|
||||
const toolInput = consultationInputSchema.parse({
|
||||
...parsed.data,
|
||||
question: resolvedQuestion.modelQuestion,
|
||||
});
|
||||
|
||||
const result = await getJyotishAgent(selectedModel).stream([
|
||||
...history.map((message) => message.role === "user"
|
||||
@@ -175,9 +194,9 @@ export async function POST(request: Request) {
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
currentTimeContext(),
|
||||
currentTimeContext(requestTime),
|
||||
name ? `用户称呼:${name}` : "",
|
||||
parsed.data.question,
|
||||
resolvedQuestion.modelQuestion,
|
||||
"\n需要查询星盘时,使用以下经过服务端校验的工具参数:",
|
||||
JSON.stringify(toolInput),
|
||||
].filter(Boolean).join("\n"),
|
||||
|
||||
@@ -45,7 +45,9 @@ function hasCompleteBirthProfile(profile: Record<string, unknown>) {
|
||||
profile.name
|
||||
&& profile.birth_date
|
||||
&& (profile.active_birth_time || profile.birth_time)
|
||||
&& (profile.birth_time_status === "confirmed" || (!profile.birth_time_status && profile.birth_time))
|
||||
&& (profile.birth_time_status === "confirmed"
|
||||
|| profile.birth_time_status === "candidate"
|
||||
|| (!profile.birth_time_status && profile.birth_time))
|
||||
&& profile.country_code
|
||||
&& profile.province_code
|
||||
&& profile.city_code,
|
||||
|
||||
Reference in New Issue
Block a user