TripSense 行知 · 周边出行顾问:首版
零依赖 Node 服务,三步流程(选出发地 → 分析候选 → 生成方案 + 路线图 + 住宿参考)。 - 候选发现:高德 place/text 类型词跨城检索 + 内置景区/城市/小众库 + Photon 兜底 - 区县级「排除同城」判定(adname),过滤发生在轮转取样之前 - 路线图画的是 AI 方案里实际安排的每个地点,按正文顺序编号 + 虚线动线 + 真实高德底图 - 住宿:RollingGo 只读 CLI 取实时报价,无价格源时如实标注不编造 - 服务端内存缓存(指纹 + TTL + LRU),数据源层与 HTTP 层分别缓存 - 上下文瘦身:逐日天气只给 rank1,紧凑 JSON,实测约 1335 token
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="行知 TripSense:按时间与所在地分析周边适合出行的目的地,叠加天气与交通,并由 AI 生成逐日行程规划。">
|
||||
<title>行知 · 周边出行顾问 | TripSense</title>
|
||||
<link rel="stylesheet" href="styles.css?v=18">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="wrap topbar-inner">
|
||||
<div class="brand">
|
||||
<span class="brand-mark">◈</span>
|
||||
<div class="brand-text"><strong>行知</strong><span>TripSense · 周边出行顾问</span></div>
|
||||
</div>
|
||||
<div class="topbar-meta">
|
||||
<span class="status-pill" id="aiStatus"><i></i> 读取配置中…</span>
|
||||
<button class="btn-ghost" id="openSettings">设置</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="wrap">
|
||||
<section class="hero">
|
||||
<div class="hero-text">
|
||||
<div class="eyebrow">周边出行 · 按时间 · 按位置 · 按天气与交通</div>
|
||||
<h1>这个时间,去哪儿最合适?</h1>
|
||||
<p>填入出行时间与出发地,系统检索周边目的地,叠加逐日天气、空气质量、公路与铁路可达性,给出可解释的备选清单。再补充你的具体需求,由 AI 生成可执行的逐日行程。</p>
|
||||
</div>
|
||||
<ol class="stepper" id="stepper">
|
||||
<li class="active" data-step="1"><b>1</b><span>出行条件</span></li>
|
||||
<li data-step="2"><b>2</b><span>备选清单</span></li>
|
||||
<li data-step="3"><b>3</b><span>行程规划</span></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ============================ STEP 1 ============================ -->
|
||||
<section class="card step-panel active" id="panel1">
|
||||
<div class="card-head">
|
||||
<h2><span class="num">①</span> 出行条件</h2>
|
||||
<p>出发地支持输入联想或浏览器定位;时间范围决定天气查询窗口与行程节奏。</p>
|
||||
</div>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="field span2 origin-field">
|
||||
<label for="originInput">出发地 <em>*</em></label>
|
||||
<div class="origin-row">
|
||||
<input id="originInput" type="text" placeholder="输入城市 / 地名,如:杭州、成都、上海" autocomplete="off" spellcheck="false">
|
||||
<button class="btn-outline" id="geoBtn" type="button">📍 使用当前位置</button>
|
||||
</div>
|
||||
<div class="suggest hidden" id="suggestBox"></div>
|
||||
<p class="hint" id="originHint">可直接输入地名并从下拉中选择,或点击定位(浏览器会请求位置权限)。</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="startDate">开始日期 <em>*</em></label>
|
||||
<input type="date" id="startDate">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="endDate">结束日期 <em>*</em></label>
|
||||
<input type="date" id="endDate">
|
||||
</div>
|
||||
|
||||
<div class="field span2 window-hint hidden" id="windowHint"></div>
|
||||
|
||||
<div class="field">
|
||||
<label for="maxHours">可接受单程时长 <em id="hoursVal">2.5 小时</em></label>
|
||||
<input type="range" id="maxHours" min="0.5" max="6" step="0.5" value="2.5">
|
||||
<div class="range-scale"><span>0.5h</span><span>6h</span></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="radius">搜索半径</label>
|
||||
<select id="radius">
|
||||
<option value="50">50 km</option>
|
||||
<option value="100">100 km</option>
|
||||
<option value="150" selected>150 km</option>
|
||||
<option value="250">250 km</option>
|
||||
<option value="300">300 km</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field span2">
|
||||
<label class="check inline-check">
|
||||
<input type="checkbox" id="excludeLocal" checked>
|
||||
排除出发地同城地点
|
||||
<span class="label-note">剔除出发地所在城区的点位(同市下辖的其它县仍会作为周边目的地保留);想连本地一日游一起看就取消勾选</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field span2">
|
||||
<label>交通方式 <span class="label-note">可多选,选「不限」会清空其他</span></label>
|
||||
<div class="chips" id="transportChips"></div>
|
||||
</div>
|
||||
<div class="field span2">
|
||||
<label>兴趣偏好 <span class="label-note">用于排序加成,可多选</span></label>
|
||||
<div class="chips" id="interestChips"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn-primary" id="analyzeBtn" type="button">分析周边目的地 <span>→</span></button>
|
||||
<span class="hint" id="analyzeHint">首次分析需检索多个数据源,通常 5~15 秒。</span>
|
||||
</div>
|
||||
|
||||
<div class="progress-wrap hidden" id="progressWrap">
|
||||
<div class="progress-head"><span id="progressText">准备中…</span><b id="progressPct">0%</b></div>
|
||||
<div class="progress-bar"><i id="progressBar"></i></div>
|
||||
<div class="progress-stages" id="progressStages"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================ STEP 2 ============================ -->
|
||||
<section class="card step-panel" id="panel2">
|
||||
<div class="card-head row">
|
||||
<div>
|
||||
<h2><span class="num">②</span> 备选目的地清单</h2>
|
||||
<p id="resultSub">按适配分排序,标注每一项的数据来源与估算内容。</p>
|
||||
</div>
|
||||
<div class="head-actions">
|
||||
<button class="btn-outline sm" id="backBtn1" type="button">← 改条件</button>
|
||||
<button class="btn-primary sm" id="toStep3" type="button">用已选目的地生成规划 →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden" id="alertBox"></div>
|
||||
|
||||
<!-- 分析结果缓存提示:命中缓存时告知,并给一个强刷出口 -->
|
||||
<div class="cache-bar hidden" id="cacheBar"></div>
|
||||
|
||||
<div class="result-top">
|
||||
<div class="compass-panel">
|
||||
<h3>出行方位罗盘 <span>点击圆点定位卡片</span></h3>
|
||||
<div id="compass"></div>
|
||||
<div class="legend">
|
||||
<span><i class="lg good"></i>适配分 ≥ 60</span>
|
||||
<span><i class="lg mid"></i>45 ~ 59</span>
|
||||
<span><i class="lg low"></i>< 45</span>
|
||||
<span class="legend-note">方位=相对出发地,半径=距离</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-panel" id="summaryPanel"></div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="seg" id="sortSeg">
|
||||
<button class="active" data-sort="score">综合评分</button>
|
||||
<button data-sort="weather">天气最好</button>
|
||||
<button data-sort="distance">距离最近</button>
|
||||
<button data-sort="time">单程最短</button>
|
||||
</div>
|
||||
<label class="check"><input type="checkbox" id="onlyRec"> 仅显示「推荐」及以上</label>
|
||||
<label class="check"><input type="checkbox" id="onlyNiche"> 只看小众</label>
|
||||
<button class="btn-outline sm" id="compareBtn" type="button">对比已选</button>
|
||||
<span class="selected-count" id="selectedCount">已选 0 个</span>
|
||||
</div>
|
||||
|
||||
<div class="preselect-hint hidden" id="preselectHint">已自动预选评分最高的 3 个目的地,可点击卡片按钮取消或改选</div>
|
||||
<div class="compare-panel hidden" id="comparePanel"></div>
|
||||
<div class="cards" id="cards"></div>
|
||||
<div class="notes" id="dataNotes"></div>
|
||||
</section>
|
||||
|
||||
<!-- ============================ STEP 3 ============================ -->
|
||||
<section class="card step-panel" id="panel3">
|
||||
<div class="card-head row">
|
||||
<div>
|
||||
<h2><span class="num">③</span> 具体需求与 AI 行程规划</h2>
|
||||
<p>清单数据 + 你的需求 + 本站预设提示词 → 任意 OpenAI 兼容接口(未配置时使用演示模式)。</p>
|
||||
</div>
|
||||
<button class="btn-outline sm" id="backBtn2" type="button">← 返回清单</button>
|
||||
</div>
|
||||
|
||||
<div class="plan-grid">
|
||||
<div class="plan-left">
|
||||
<label>已选目的地 <span class="label-note" id="selHint">返回上一步可调整</span></label>
|
||||
<div class="selected-chips" id="selectedChips"></div>
|
||||
|
||||
<label for="needs">你的具体需求</label>
|
||||
<textarea id="needs" rows="9" placeholder="例如:带 6 岁孩子和一位老人,两天一晚,总预算 1500 元以内;不想走太多路;希望有室内备选;不自驾,优先高铁;想吃当地特色但别太辣……"></textarea>
|
||||
|
||||
<div class="lodging-row">
|
||||
<span class="lodging-label">住宿价位</span>
|
||||
<div class="chips" id="lodgingChips"></div>
|
||||
</div>
|
||||
<div class="lodging-custom hidden" id="lodgingCustom">
|
||||
<span class="cny">¥</span>
|
||||
<input id="lodgingMin" type="number" min="0" step="50" inputmode="numeric" placeholder="最低">
|
||||
<span class="to">–</span>
|
||||
<input id="lodgingMax" type="number" min="0" step="50" inputmode="numeric" placeholder="最高">
|
||||
<span class="unit">元 / 晚</span>
|
||||
</div>
|
||||
<p class="lodging-tip" id="lodgingTip"></p>
|
||||
|
||||
<div class="quick-needs" id="quickNeeds"></div>
|
||||
<div class="ai-inline" id="aiInline"></div>
|
||||
<button class="btn-primary full" id="planBtn" type="button">生成行程规划 →</button>
|
||||
<p class="hint">生成方案后,会据此给出住宿建议与路线示意图。</p>
|
||||
</div>
|
||||
|
||||
<div class="plan-right">
|
||||
<div class="output-head">
|
||||
<span id="outputMeta">等待生成</span>
|
||||
<div class="head-tools">
|
||||
<select id="versionSelect" class="version-select hidden" title="切换版本 / 载入已保存方案"></select>
|
||||
<button class="btn-ghost sm hidden" id="comparePlan" type="button">对比上一版</button>
|
||||
<button class="btn-ghost sm hidden" id="savePlan" type="button">保存</button>
|
||||
<button class="btn-ghost sm" id="copyBtn" type="button">复制</button>
|
||||
<button class="btn-ghost sm" id="downloadBtn" type="button">下载 .md</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="compare-wrap hidden" id="compareWrap">
|
||||
<div class="compare-col"><div class="compare-col-head" id="compareLeftHead">上一版</div><div class="markdown" id="compareLeft"></div></div>
|
||||
<div class="compare-col"><div class="compare-col-head" id="compareRightHead">当前版</div><div class="markdown" id="compareRight"></div></div>
|
||||
</div>
|
||||
<div class="markdown empty" id="planOutput">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">✦</div>
|
||||
<h4>方案会显示在这里</h4>
|
||||
<p>包含方案总览、逐日安排表、天气与装备、交通方案、预算粗估、出发前清单和风险备选。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="followup hidden" id="planFollowup">
|
||||
<div class="followup-row">
|
||||
<input id="followupInput" type="text" placeholder="继续调整,例如:第二天改成室内为主;总预算压到 1200;换一家离景区更近的住处" autocomplete="off">
|
||||
<button class="btn-primary sm" id="followupBtn" type="button">继续优化</button>
|
||||
</div>
|
||||
<div class="followup-hint">
|
||||
<span id="followupCount">会基于当前方案重写完整版本</span>
|
||||
<button class="btn-ghost sm" id="resetPlan" type="button">回到初版</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 方案定稿后才出现:路线示意 + 按行程与价位给的住宿建议 -->
|
||||
<div class="plan-extra hidden" id="planExtra">
|
||||
<div class="extra-card">
|
||||
<div class="extra-head">
|
||||
<span>行程路线示意</span>
|
||||
<span class="extra-note" id="routeNote"></span>
|
||||
</div>
|
||||
<div class="route-wrap" id="routeMap"></div>
|
||||
</div>
|
||||
<div class="extra-card">
|
||||
<div class="extra-head">
|
||||
<span>住宿建议</span>
|
||||
<span class="extra-note" id="stayNote"></span>
|
||||
</div>
|
||||
<div class="hotel-box" id="hotelBox"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div><strong>行知 · TripSense</strong> — 周边出行决策原型</div>
|
||||
<div>数据:Open-Meteo(天气/空气质量/地理编码)· OpenStreetMap / Photon(周边地点)· OSRM(公路)· 内置知名 + 小众地点库 · 可选高德 / 铁路接口 · OpenAI 兼容模型</div>
|
||||
<div class="disclaimer">天气为预报或历史气候参考,铁路与部分路网耗时为估算;票价、班次、门票、开放时间请临近出发前在官方渠道核对。本工具不构成出行安全承诺。</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<!-- ============================ 设置弹窗 ============================ -->
|
||||
<div class="modal hidden" id="settingsModal">
|
||||
<div class="modal-mask" id="modalMask"></div>
|
||||
<div class="modal-box">
|
||||
<div class="modal-head">
|
||||
<h3>设置 <span>AI 接口 · 数据源</span></h3>
|
||||
<button class="icon-btn" id="closeSettings" type="button">✕</button>
|
||||
</div>
|
||||
<p class="modal-desc">任何提供 <code>/chat/completions</code> 的服务都可接入。此处填写的值仅保存在本机浏览器,并随请求传给本地服务代理;生产环境建议改用服务端环境变量,不要在前端放密钥。留空 API Key 即使用演示模式。</p>
|
||||
|
||||
<label for="aiBase">接口地址 Base URL</label>
|
||||
<input id="aiBase" type="text" placeholder="https://api.openai.com/v1" spellcheck="false">
|
||||
<div class="presets" id="aiPresets"></div>
|
||||
|
||||
<label for="aiKey">API Key</label>
|
||||
<input id="aiKey" type="password" placeholder="sk-…(留空 = 演示模式)" autocomplete="off">
|
||||
|
||||
<label for="aiModel">模型名称</label>
|
||||
<input id="aiModel" type="text" placeholder="gpt-4o-mini" spellcheck="false">
|
||||
|
||||
<div class="field-row-inline">
|
||||
<button class="btn-ghost sm" id="testAi" type="button">测试 AI 接口</button>
|
||||
<span class="field-hint inline" id="aiTestResult"></span>
|
||||
</div>
|
||||
|
||||
<div class="modal-divider"><span>数据源增强(可选)</span></div>
|
||||
|
||||
<label for="amapKey">高德 Web 服务 Key
|
||||
<a class="field-link" href="https://console.amap.com/dev/key/app" target="_blank" rel="noopener">去申请 ↗</a>
|
||||
</label>
|
||||
<input id="amapKey" type="text" placeholder="留空 = 只用免密钥数据源" spellcheck="false" autocomplete="off">
|
||||
<div class="field-row-inline">
|
||||
<button class="btn-ghost sm" id="testAmap" type="button">测试这个 Key</button>
|
||||
<span class="field-hint inline" id="amapTestResult"></span>
|
||||
</div>
|
||||
<p class="field-hint">用于实时周边 POI、国内驾车路径与中文地名检索,须选「Web服务」类型的 Key(不是「Web端(JS API)」)。填好后重新分析,清单里会出现更多实时地点,顶部「实时周边 POI 未取到」的提示也会消失。此值同样只存在本机浏览器。</p>
|
||||
|
||||
<label>住宿数据源</label>
|
||||
<div class="field-row-inline">
|
||||
<button class="btn-ghost sm" id="checkProviders" type="button">检测</button>
|
||||
<span class="field-hint inline" id="providerResult">未检测</span>
|
||||
</div>
|
||||
<p class="field-hint">装了 <code>RollingGo</code> 的 <code>rgh</code> CLI 并执行 <code>rgh login</code> 后,住宿会显示实时报价与可预订链接;否则退回高德 POI(多数没有房价)。本项目只调用 <code>status</code> / <code>search-hotels</code> / <code>hotel-detail</code> 三个只读命令,<b>不会下单、不会取消订单</b>。</p>
|
||||
|
||||
<details class="prompt-details">
|
||||
<summary>查看 / 复制本站预设提示词</summary>
|
||||
<div class="prompt-actions"><button class="btn-ghost sm" id="copyPrompt" type="button">复制提示词</button></div>
|
||||
<pre id="promptPreview"></pre>
|
||||
</details>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="btn-primary" id="saveSettings" type="button">保存设置</button>
|
||||
<button class="btn-outline" id="cancelSettings" type="button">取消</button>
|
||||
<button class="btn-ghost sm" id="demoMode" type="button">强制演示模式</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast hidden" id="toast"></div>
|
||||
<script src="app.js?v=18"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user