diff --git a/src/components/BlindboxPlan.tsx b/src/components/BlindboxPlan.tsx index 0204ec2..a214d16 100644 --- a/src/components/BlindboxPlan.tsx +++ b/src/components/BlindboxPlan.tsx @@ -469,6 +469,20 @@ export default function BlindboxPlan({ setDraft({ ...item }); }} /> + {/* Transit connector to next activity */} + {item.transitToNext != null && i < currentDay.items.length - 1 && ( +
+ +
+ {item.transitDescription && ( + + {item.transitDescription} + + )} + 约 {item.transitToNext} 分钟 +
+
+ )} ))} diff --git a/src/lib/ai.ts b/src/lib/ai.ts index d835fa3..e42401b 100644 --- a/src/lib/ai.ts +++ b/src/lib/ai.ts @@ -53,12 +53,15 @@ const SCHEDULE_SYSTEM_PROMPT = `你是一个周末行程规划师。根据用户 "lat": 31.2, "lng": 121.5, "duration": 120, - "reason": "选择这个时间和地点的简短理由" + "reason": "选择这个时间和地点的简短理由", + "transitToNext": 20, + "transitDescription": "地铁2号线(6站) → 地铁9号线(3站)" } ], "summary": "一句话总结这个行程的亮点" } +transitToNext 为从本活动结束到下一活动的预计交通时间(分钟),根据两点地理距离估算;transitDescription 为交通方式描述(如"地铁X号线"、"公交XXX路"、"步行");最后一项不填这两个字段。 按时间顺序排列。只返回 JSON。`; export interface ScheduleContext { @@ -222,6 +225,14 @@ ${Object.entries(ctx.candidates) lng: Number(item.lng) || 0, duration: Number(item.duration) || 60, reason: String(item.reason ?? ""), + ...(item.transitToNext != null && Number(item.transitToNext) > 0 + ? { + transitToNext: Math.round(Number(item.transitToNext)), + ...(item.transitDescription + ? { transitDescription: String(item.transitDescription) } + : {}), + } + : {}), })), summary: String(parsed.summary ?? ""), }; diff --git a/src/lib/blindboxPlanGen.ts b/src/lib/blindboxPlanGen.ts index 05ffbb7..c53404c 100644 --- a/src/lib/blindboxPlanGen.ts +++ b/src/lib/blindboxPlanGen.ts @@ -110,6 +110,34 @@ function selectIdeasForSlots(ideas: TaggedIdea[], availableHours: number): Tagge return selected; } +// --------------------------------------------------------------------------- +// Transit segment parser +// --------------------------------------------------------------------------- + +function parseTransitSegments( + segments: Record[], +): { description: string; mode: string } { + const parts: string[] = []; + let hasSubway = false; + let hasBus = false; + + for (const seg of segments) { + const bus = seg.bus as { buslines?: Record[] } | undefined; + if (!bus?.buslines?.length) continue; + for (const line of bus.buslines) { + const name = String(line.name ?? ""); + const viaNum = Number(line.via_num ?? 0); + const isSubway = String(line.type ?? "").includes("地铁") || String(line.type ?? "").includes("轨道"); + if (isSubway) hasSubway = true; else hasBus = true; + parts.push(viaNum > 0 ? `${name}(${viaNum}站)` : name); + } + } + + const mode = hasSubway ? "地铁" : hasBus ? "公交" : "步行"; + const description = parts.length > 0 ? parts.join(" → ") : mode; + return { description, mode }; +} + // --------------------------------------------------------------------------- // POI search (shared by both paths) // --------------------------------------------------------------------------- @@ -205,7 +233,7 @@ const AGENT_SYSTEM_PROMPT = `你是一个周末行程规划 Agent。你有以下 4. 如果搜索结果不理想(0结果或不相关),尝试换关键词重搜 5. 用 get_travel_time 检查关键路段的公共交通时间,如果某段超过 45 分钟考虑换更近的地点 6. 综合地理位置、时间安排、活动特点,规划最优路线 -7. 确认无误后 finalize_plan 提交 +7. 确认无误后 finalize_plan 提交,每个活动填写 transit_to_next_minutes 和 transit_to_next_description(直接复制 get_travel_time 返回的 durationMin 和 description,最后一项不填) 规划原则: - 地理位置相近,最小化移动距离 @@ -229,6 +257,8 @@ interface FinalizePlanDay { lng: number; duration: number; reason: string; + transit_to_next_minutes?: number; + transit_to_next_description?: string; }[]; summary: string; } @@ -333,10 +363,10 @@ function buildAgentTools( return JSON.stringify({ error: "未找到公交路线" }); } const transit = data.route.transits[0]; - return JSON.stringify({ - distanceKm: Math.round(Number(data.route.distance) / 100) / 10, - durationMin: Math.ceil(Number(transit.duration) / 60), - }); + const durationMin = Math.ceil(Number(transit.duration) / 60); + const distanceKm = Math.round(Number(data.route.distance) / 100) / 10; + const { description, mode } = parseTransitSegments(transit.segments ?? []); + return JSON.stringify({ durationMin, distanceKm, description, mode }); } catch { return JSON.stringify({ error: "路线查询失败" }); } @@ -345,7 +375,7 @@ function buildAgentTools( progressAfter: (_args, result) => { const r = JSON.parse(result); if (r.error) return `路线查询失败`; - return `公共交通约 ${r.durationMin} 分钟(${r.distanceKm}km)`; + return `${r.description} 约 ${r.durationMin} 分钟(${r.distanceKm}km)`; }, }; @@ -374,6 +404,14 @@ function buildAgentTools( lng: { type: "number" }, duration: { type: "number", description: "时长(分钟)" }, reason: { type: "string", description: "选择理由" }, + transit_to_next_minutes: { + type: "number", + description: "从本活动结束后到下一活动的预计交通时间(分钟)。最后一个活动不填", + }, + transit_to_next_description: { + type: "string", + description: "到下一活动的交通方式描述,直接使用 get_travel_time 返回的 description 字段值,例如:地铁2号线(6站) → 地铁9号线(3站)。最后一个活动不填", + }, }, required: ["time", "activity", "poi", "address", "lat", "lng", "duration", "reason"], }, @@ -440,6 +478,14 @@ async function runAgentPlanGeneration( lng: Number(item.lng) || 0, duration: Number(item.duration) || 60, reason: String(item.reason ?? ""), + ...(item.transit_to_next_minutes != null && Number(item.transit_to_next_minutes) > 0 + ? { + transitToNext: Math.round(Number(item.transit_to_next_minutes)), + ...(item.transit_to_next_description + ? { transitDescription: String(item.transit_to_next_description) } + : {}), + } + : {}), })) : [], summary: String(day.summary ?? ""), diff --git a/src/types/index.ts b/src/types/index.ts index 8cfd1e3..ff7b713 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -113,6 +113,10 @@ export interface PlanItem { lng: number; duration: number; reason: string; + /** 到下一个活动的估计交通时间(分钟),最后一项无此字段 */ + transitToNext?: number; + /** 到下一个活动的交通方式描述,如"地铁2号线→9号线" */ + transitDescription?: string; } export interface WeekendPlanData {