feat: AI 辅助修改行程(自然语言调整 + 单活动替代推荐)
- 新增 refinePlan / suggestAlternativeItems 到 ai.ts - 新增 POST /api/blindbox/plan/refine(整体行程调整) - 新增 POST /api/blindbox/plan/suggest-item(单活动 AI 替代 + POI 搜索) - BlindboxPlan 底部新增自然语言输入框(方案 A) - 编辑 modal 内新增 AI 推荐替代方案卡片(方案 B) - export searchPois 供 suggest-item 路由复用
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { apiHandler, ApiError, requireUserId } from "@/lib/api";
|
||||
import { refinePlan } from "@/lib/ai";
|
||||
|
||||
export const POST = apiHandler(async (req) => {
|
||||
const { userId, instruction, days } = await req.json();
|
||||
requireUserId(userId);
|
||||
if (!instruction?.trim()) throw new ApiError("指令不能为空", 400);
|
||||
if (!Array.isArray(days) || days.length === 0) throw new ApiError("days 无效", 400);
|
||||
|
||||
const newDays = await refinePlan(days, instruction);
|
||||
if (!newDays) throw new ApiError("AI 调整失败,请重试", 500);
|
||||
return NextResponse.json({ days: newDays });
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { apiHandler, ApiError } from "@/lib/api";
|
||||
import { suggestAlternativeItems } from "@/lib/ai";
|
||||
import { searchPois } from "@/lib/blindboxPlanGen";
|
||||
|
||||
export const POST = apiHandler(async (req) => {
|
||||
const { activity, time, location } = await req.json();
|
||||
if (!activity) throw new ApiError("activity 不能为空", 400);
|
||||
|
||||
const alts = await suggestAlternativeItems(activity, time ?? "");
|
||||
if (!alts) throw new ApiError("AI 推荐失败,请重试", 500);
|
||||
|
||||
// Parse location "lng,lat"
|
||||
let anchorLat = 31.23, anchorLng = 121.47; // default Shanghai
|
||||
if (location) {
|
||||
const [lng, lat] = location.split(",").map(Number);
|
||||
if (!isNaN(lat) && !isNaN(lng)) { anchorLat = lat; anchorLng = lng; }
|
||||
}
|
||||
|
||||
// Parallel POI search with per-item fallback
|
||||
const results = await Promise.all(
|
||||
alts.map(async (alt) => {
|
||||
try {
|
||||
const pois = await searchPois(alt.searchQuery, "place", anchorLat, anchorLng);
|
||||
const top = pois[0];
|
||||
if (top) {
|
||||
return {
|
||||
activity: alt.activity,
|
||||
poi: top.name,
|
||||
address: top.address,
|
||||
lat: top.lat,
|
||||
lng: top.lng,
|
||||
reason: alt.reason,
|
||||
};
|
||||
}
|
||||
} catch { /* ignore, use fallback */ }
|
||||
return {
|
||||
activity: alt.activity,
|
||||
poi: alt.searchQuery,
|
||||
address: "",
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
reason: alt.reason,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return NextResponse.json({ suggestions: results });
|
||||
});
|
||||
Reference in New Issue
Block a user