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:
+101
@@ -230,6 +230,107 @@ ${Object.entries(ctx.candidates)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// refinePlan: adjust an existing plan based on a natural-language instruction
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const REFINE_PLAN_SYSTEM_PROMPT = `你是一个周末行程调整助手。根据用户指令修改现有行程。规则:
|
||||
1. 只改用户明确要求的部分,其余原样保留
|
||||
2. 时间安排合理,活动间留交通时间
|
||||
3. 若需新增活动,poi/address 可留空字符串,lat/lng 填 0
|
||||
4. 严格按输入的 JSON 结构输出完整 days 数组`;
|
||||
|
||||
export async function refinePlan(
|
||||
days: import("@/types").WeekendPlanData[],
|
||||
instruction: string,
|
||||
): Promise<import("@/types").WeekendPlanData[] | null> {
|
||||
try {
|
||||
const client = getClient();
|
||||
const response = await client.chat.completions.create({
|
||||
model: "deepseek-chat",
|
||||
messages: [
|
||||
{ role: "system", content: REFINE_PLAN_SYSTEM_PROMPT },
|
||||
{
|
||||
role: "user",
|
||||
content: `当前行程:\n${JSON.stringify(days)}\n\n用户指令:${instruction}`,
|
||||
},
|
||||
],
|
||||
response_format: { type: "json_object" },
|
||||
temperature: 0.5,
|
||||
max_tokens: 2000,
|
||||
});
|
||||
|
||||
const text = response.choices[0]?.message?.content;
|
||||
if (!text) return null;
|
||||
|
||||
const parsed = JSON.parse(text);
|
||||
const result = parsed.days ?? parsed;
|
||||
if (!Array.isArray(result) || result.length === 0) return null;
|
||||
if (!result.every((d: unknown) => {
|
||||
if (typeof d !== "object" || d === null) return false;
|
||||
const day = d as Record<string, unknown>;
|
||||
return typeof day.date === "string" && Array.isArray(day.items);
|
||||
})) return null;
|
||||
|
||||
return result as import("@/types").WeekendPlanData[];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// suggestAlternativeItems: recommend 3 alternatives for a single activity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SUGGEST_ALT_SYSTEM_PROMPT = `你是一个周末活动推荐助手。根据当前活动推荐 3 个类似但不同的替代方案,用于替换行程中的该活动。
|
||||
|
||||
每个替代方案需要包含:
|
||||
- activity: 活动描述(5-15字,口语化)
|
||||
- searchQuery: 高德地图搜索关键词
|
||||
- reason: 推荐理由(一句话)
|
||||
|
||||
只返回 JSON:{ "alternatives": [{ "activity", "searchQuery", "reason" }] }`;
|
||||
|
||||
export async function suggestAlternativeItems(
|
||||
activity: string,
|
||||
time: string,
|
||||
): Promise<Array<{ activity: string; searchQuery: string; reason: string }> | null> {
|
||||
try {
|
||||
const client = getClient();
|
||||
const response = await client.chat.completions.create({
|
||||
model: "deepseek-chat",
|
||||
messages: [
|
||||
{ role: "system", content: SUGGEST_ALT_SYSTEM_PROMPT },
|
||||
{
|
||||
role: "user",
|
||||
content: `当前活动:${activity}${time ? `(时间:${time})` : ""}`,
|
||||
},
|
||||
],
|
||||
response_format: { type: "json_object" },
|
||||
temperature: 0.8,
|
||||
max_tokens: 400,
|
||||
});
|
||||
|
||||
const text = response.choices[0]?.message?.content;
|
||||
if (!text) return null;
|
||||
|
||||
const parsed = JSON.parse(text);
|
||||
if (!Array.isArray(parsed.alternatives)) return null;
|
||||
|
||||
return parsed.alternatives
|
||||
.filter(
|
||||
(a: unknown) =>
|
||||
typeof a === "object" &&
|
||||
a !== null &&
|
||||
typeof (a as Record<string, unknown>).activity === "string" &&
|
||||
typeof (a as Record<string, unknown>).searchQuery === "string",
|
||||
)
|
||||
.slice(0, 3) as Array<{ activity: string; searchQuery: string; reason: string }>;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generic tool-calling agent loop
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user