'use strict'; /** * TripSense · 周边出行顾问 —— 全局配置 * * 所有密钥优先从环境变量读取;也可以在网页「AI 接口设置」里临时覆盖 * (覆盖值只保存在浏览器 localStorage,并由前端通过请求头传给本服务代理)。 * 生产环境请只用环境变量,不要把密钥写进前端。 */ const env = process.env; module.exports = { PORT: Number(env.PORT || 8787), // 默认绑定 0.0.0.0,方便手机 / 同网段设备直接访问。 // 只在本机用就把 HOST 设成 127.0.0.1(`HOST=127.0.0.1 node server.js`)。 HOST: env.HOST || '0.0.0.0', // 端口被占用时,server.js 会自动往后找可用端口,启动日志会打印实际地址。 // ---------------------------------------------------------------- 数据源 ENDPOINTS: { geocoding: 'https://geocoding-api.open-meteo.com/v1/search', forecast: 'https://api.open-meteo.com/v1/forecast', archive: 'https://archive-api.open-meteo.com/v1/archive', airQuality: 'https://air-quality-api.open-meteo.com/v1/air-quality', photon: 'https://photon.komoot.io/api', reverse: 'https://api.bigdatacloud.net/data/reverse-geocode-client', osrmTable: 'https://router.project-osrm.org/table/v1/driving', amapDriving: 'https://restapi.amap.com/v3/direction/driving', amapAround: 'https://restapi.amap.com/v3/place/around', amapGeocode: 'https://restapi.amap.com/v3/geocode/geo', amapPlaceText: 'https://restapi.amap.com/v3/place/text', // 反向地理编码(坐标 → 地名):BigDataCloud / Photon reverse 在国内常被限流或返回空, // 高德 regeo 能稳定拿到「城市 + 区县 + 街道」,是住宿城市兜底的关键 amapRegeo: 'https://restapi.amap.com/v3/geocode/regeo', // 静态地图(真实地图底图):仅使用合规白名单内的地图服务商,Key 由后端代理,不下发前端 amapStaticMap: 'https://restapi.amap.com/v3/staticmap', }, // 选填:高德 Web 服务 Key。配置后公路耗时改用高德(国内路网/限行更贴近实际) AMAP_KEY: env.AMAP_KEY || '', // 选填:铁路/高铁接口模板(留给 12306 或第三方服务)。 // 支持的占位符:{fromLat} {fromLon} {toLat} {toLon} {date} {fromName} {toName} // 期望返回 JSON:{ available:boolean, durationMinutes:number, distanceKm:number, fareCny:number, note:string } // 未配置或调用失败时,自动降级为「里程-速度」估算,并在结果里明确标注「估算」。 RAIL_API_URL: env.RAIL_API_URL || '', RAIL_API_KEY: env.RAIL_API_KEY || '', // 选填:住宿价格数据源(高德 POI 的价格字段覆盖很差,多数酒店没有参考价)。 // 支持占位符:{name} {lat} {lon} {city} // 期望返回 JSON:[{ name:string, price:number, currency?:string, url?:string }] // 按酒店名模糊匹配后合并;未配置或失败时不影响主流程,只是没有价格。 HOTEL_PRICE_API_URL: env.HOTEL_PRICE_API_URL || '', HOTEL_PRICE_API_KEY: env.HOTEL_PRICE_API_KEY || '', // 选填:住宿数据来源。auto = 装了 rgh 且已登录就用 RollingGo,否则用高德 POI // amap 只用高德 POI(无实时房价,但零配置) // rollinggo 只尝试本机 rgh CLI(OAuth 登录后才有实时报价) // 注意:rgh 只以只读方式调用(status / search-hotels / hotel-detail), // 绝不调用 price-confirm / book / cancel 这类会产生真实交易的子命令。 HOTEL_PROVIDER: (env.HOTEL_PROVIDER || 'auto').toLowerCase(), RHG_BIN: env.RHG_BIN || 'rgh', ROLLINGGO_TIMEOUT_MS: Number(env.ROLLINGGO_TIMEOUT_MS || 25000), // 检索参数 SEARCH: { maxRadiusKm: 300, // 最大检索半径 photonConcurrency: 5, // 对公共 Photon 服务保持克制 photonPerCategory: 8, // 每个类别取多少条 photonTimeoutMs: 4000, // Photon 公共实例易被限流,快速失败并走兜底 photonRetries: 0, maxCandidates: 28, // 进入天气/路网计算的候选上限 requestTimeoutMs: 15000, // 天气/地理编码等主数据源 osrmTimeoutMs: 12000, amapAroundRadius: 50000, // 高德 around 检索上限 50km }, // ------------------------------------------------- OpenAI 兼容接口(预留) AI: { // 任何兼容 /chat/completions 的服务都可以直接填这里 BASE_URL: env.AI_BASE_URL || 'https://api.openai.com/v1', API_KEY: env.AI_API_KEY || '', MODEL: env.AI_MODEL || 'gpt-4o-mini', TEMPERATURE: Number(env.AI_TEMPERATURE || 0.7), MAX_TOKENS: Number(env.AI_MAX_TOKENS || 8000), TIMEOUT_MS: Number(env.AI_TIMEOUT_MS || 120000), }, // ------------------------------------------------------------ 预设提示词 // 这段提示词把「本站能做什么、数据从哪来、哪些是估算」写死给模型, // 避免模型编造票价/班次/景点,也保证输出结构稳定。 SYSTEM_PROMPT: `你是「行知 · 周边出行顾问」的行程规划师。只使用输入 JSON 中的数据,禁止编造。 【输入】 origin 出发地;timeRange 起止/天数;constraints 单程上限与兴趣; shortlist 候选(name/kind/tags/distanceKm/direction/score/verdict/travel/weather/aqi); 只有 rank1 带 weather.daily 与 road/rail 明细,其余候选只有区间摘要与 bestHours; nearby 目的地周边可点名地点(food 餐饮、sights 景点,含 name/type/distanceKm/rating/cost); hotels 住宿 POI;lodging 用户价位区间;userNeeds 用户需求;dataNotes 数据口径。 【规则】 1. 不编造未提供的景点、餐厅、票价、班次、营业时间;需要用户核实的写「出发前核对」。 2. 估算数据(铁路耗时票价、历史气候替代、路网兜底)必须标注「估算」,不得当事实。 3. 有 nearby 时,吃和玩必须从中点名:「名称(类型,距目的地 X km,评分 Y,人均 ¥Z)」,cost 没有就不写。 禁止用「附近有很多小吃」这类空话代替点名。某一类确实缺失,才退回「片区 + 筛选方法」,并说明缺的是哪类。 4. 住宿从 hotels 里挑,写名称 + 距离 + 参考价,注明房态与实时价需在平台核实; lodging 有 min/max 就必须落在区间内,与 hotels 价格冲突时直说并建议放宽,不要硬塞进预算。 5. 住宿靠近当天主要活动地;跨片区要说明是否换住处、换的成本。 6. 发现现实冲突(时间不够、天气差、闭馆、预算不足)直接指出,不要讨好式输出。 【输出】Markdown,直接从「## 方案总览」开始,不要开场白、不要复述规则。 篇幅硬约束:全文 ≤ 900 字;表格单元格 ≤ 18 字;只写可执行结论,不复述输入数据。 ## 方案总览(结论 + 主推理由 + 备选及原因 + 总节奏) ## 逐日安排(### D1(日期 星期)+ 表格:时段|安排|交通与耗时|天气应对;雨天给室内替换) ## 天气与装备(逐日摘要 + 穿衣/雨具 + 降雨大风兜底动作) ## 交通方案(按允许方式写耗时/里程/来源标注/停车换乘/末班提醒,车次余票需自行核对) ## 预算粗估(表格:交通/门票/餐饮/住宿/其他 + 合计,注明粗略、不含购物) ## 出发前清单(预订、证件、装备、需复核的信息) ## 风险与备选(2-4 条,每条「触发条件 → 替换动作」) `, };