import OpenAI from "openai"; import type { IdeaTags, PlanItem } from "@/types"; function getClient() { const apiKey = process.env.DEEPSEEK_API_KEY; if (!apiKey) throw new Error("DEEPSEEK_API_KEY is not configured"); return new OpenAI({ baseURL: "https://api.deepseek.com", apiKey }); } const TAG_SYSTEM_PROMPT = `你是一个周末活动分析助手。用户会输入一条周末活动想法,你需要分析并返回结构化 JSON。 返回字段: - category: 活动品类,必须是以下之一: "dining"(餐饮美食)| "outdoor"(户外活动)| "entertainment"(娱乐休闲,如电影、KTV、密室)| "shopping"(购物逛街)| "sports"(运动健身)| "culture"(文化艺术,如博物馆、展览)| "relaxation"(放松休息,如SPA、咖啡、下午茶) - timeSlot: 最适合的时间段,必须是以下之一: "morning"(上午)| "afternoon"(下午)| "evening"(晚上)| "flexible"(任意时间都可以)| "all_day"(需要一整天) - estimatedMinutes: 预估活动时长(整数,单位分钟) - outdoor: 是否户外活动(布尔值) - searchQuery: 在地图服务上搜索的关键词(品牌名、地名或品类名) - searchType: 搜索策略,必须是以下之一: "brand"(连锁品牌,有多个分店)| "place"(唯一地点,如某个公园)| "category"(模糊品类,搜附近匹配的) 只返回 JSON,不要任何额外文字。`; const SCHEDULE_SYSTEM_PROMPT = `你是一个周末行程规划师。根据用户选定的活动和候选地点坐标,生成最优行程安排。 规划原则: 1. 选择地理位置相近的 POI,最小化总移动距离 2. 尊重活动的时间偏好(公园上午、正餐在饭点、电影灵活) 3. 活动之间留出合理的交通时间(15-30分钟) 4. 如果有"category"类型的活动,选择离其他已确定地点最近的候选 返回 JSON 格式: { "items": [ { "time": "10:00", "activity": "原始活动描述", "poi": "选定的具体 POI 名称", "address": "详细地址", "lat": 31.2, "lng": 121.5, "duration": 120, "reason": "选择这个时间和地点的简短理由" } ], "summary": "一句话总结这个行程的亮点" } 按时间顺序排列。只返回 JSON。`; export interface ScheduleContext { ideas: { content: string; category: string; timeSlot: string; estimatedMinutes: number; searchQuery: string; searchType: string; }[]; candidates: Record< string, { name: string; address: string; lat: number; lng: number; rating?: number }[] >; userLocation: { lat: number; lng: number }; availableTime: { date: string; startHour: number; endHour: number }; } export async function tagIdea(content: string): Promise { try { const client = getClient(); const response = await client.chat.completions.create({ model: "deepseek-chat", messages: [ { role: "system", content: TAG_SYSTEM_PROMPT }, { role: "user", content }, ], response_format: { type: "json_object" }, max_tokens: 200, temperature: 0.3, }); const text = response.choices[0]?.message?.content; if (!text) return null; const parsed = JSON.parse(text); const validCategories = ["dining", "outdoor", "entertainment", "shopping", "sports", "culture", "relaxation"]; const validTimeSlots = ["morning", "afternoon", "evening", "flexible", "all_day"]; const validSearchTypes = ["brand", "place", "category"]; if ( !validCategories.includes(parsed.category) || !validTimeSlots.includes(parsed.timeSlot) || !validSearchTypes.includes(parsed.searchType) || typeof parsed.estimatedMinutes !== "number" || typeof parsed.outdoor !== "boolean" || typeof parsed.searchQuery !== "string" ) { return null; } return { category: parsed.category, timeSlot: parsed.timeSlot, estimatedMinutes: parsed.estimatedMinutes, outdoor: parsed.outdoor, searchQuery: parsed.searchQuery, searchType: parsed.searchType, }; } catch { return null; } } const SUGGEST_SYSTEM_PROMPT = `你是一个脑洞大开的周末活动灵感助手。根据用户房间里已有的活动想法,推荐 4 个风格相近但不重复的新想法。 要求: - 贴合已有想法的整体调性(如果偏文艺就推文艺的,偏冒险就推冒险的) - 适当加入一些意想不到的创意,激发灵感 - 每条想法 5-15 个字,口语化、有画面感 - 不要与已有想法重复或过于相似 只返回 JSON:{ "suggestions": ["想法1", "想法2", "想法3", "想法4"] }`; export async function suggestIdeas(existingIdeas: string[]): Promise { try { const client = getClient(); const response = await client.chat.completions.create({ model: "deepseek-chat", messages: [ { role: "system", content: SUGGEST_SYSTEM_PROMPT }, { role: "user", content: `已有想法:\n${existingIdeas.map((s, i) => `${i + 1}. ${s}`).join("\n")}` }, ], response_format: { type: "json_object" }, max_tokens: 200, temperature: 0.9, }); const text = response.choices[0]?.message?.content; if (!text) return []; const parsed = JSON.parse(text); if (!Array.isArray(parsed.suggestions)) return []; return parsed.suggestions .filter((s: unknown) => typeof s === "string" && s.length > 0) .slice(0, 4); } catch { return []; } } export async function generateSchedule( ctx: ScheduleContext, ): Promise<{ items: PlanItem[]; summary: string } | null> { try { const client = getClient(); const userPrompt = ` 可用时间:${ctx.availableTime.date},${ctx.availableTime.startHour}:00 - ${ctx.availableTime.endHour}:00 用户出发位置:纬度 ${ctx.userLocation.lat},经度 ${ctx.userLocation.lng} 活动列表: ${ctx.ideas.map((idea, i) => `${i + 1}. "${idea.content}"(品类:${idea.category},偏好时间:${idea.timeSlot},预估${idea.estimatedMinutes}分钟)`).join("\n")} 各活动的候选地点: ${Object.entries(ctx.candidates) .map( ([query, pois]) => `"${query}" 的候选:\n${pois.map((p) => ` - ${p.name} | ${p.address} | 坐标(${p.lat},${p.lng})${p.rating ? ` | 评分${p.rating}` : ""}`).join("\n")}`, ) .join("\n\n")} 请为以上活动生成最优行程安排。`; const response = await client.chat.completions.create({ model: "deepseek-chat", messages: [ { role: "system", content: SCHEDULE_SYSTEM_PROMPT }, { role: "user", content: userPrompt }, ], response_format: { type: "json_object" }, max_tokens: 1500, temperature: 0.5, }); const text = response.choices[0]?.message?.content; if (!text) return null; const parsed = JSON.parse(text); if (!Array.isArray(parsed.items) || parsed.items.length === 0) return null; return { items: parsed.items.map((item: Record) => ({ time: String(item.time ?? ""), activity: String(item.activity ?? ""), poi: String(item.poi ?? ""), address: String(item.address ?? ""), lat: Number(item.lat) || 0, lng: Number(item.lng) || 0, duration: Number(item.duration) || 60, reason: String(item.reason ?? ""), })), summary: String(parsed.summary ?? ""), }; } catch { return null; } }