Files
tripsense/config.js
T
tamakyi c5c2ffae68 TripSense 行知 · 周边出行顾问:首版
零依赖 Node 服务,三步流程(选出发地 → 分析候选 → 生成方案 + 路线图 + 住宿参考)。

- 候选发现:高德 place/text 类型词跨城检索 + 内置景区/城市/小众库 + Photon 兜底
- 区县级「排除同城」判定(adname),过滤发生在轮转取样之前
- 路线图画的是 AI 方案里实际安排的每个地点,按正文顺序编号 + 虚线动线 + 真实高德底图
- 住宿:RollingGo 只读 CLI 取实时报价,无价格源时如实标注不编造
- 服务端内存缓存(指纹 + TTL + LRU),数据源层与 HTTP 层分别缓存
- 上下文瘦身:逐日天气只给 rank1,紧凑 JSON,实测约 1335 token
2026-11-05 20:25:03 +08:00

123 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 条,每条「触发条件 → 替换动作」)
`,
};