Spaces:
Sleeping
Sleeping
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>冷链闭环智能体 · ColdChain Optimizer</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | |
| <style> | |
| :root { | |
| --bg: #f4f7fb; | |
| --card: #ffffff; | |
| --primary: #0f3d5e; | |
| --accent: #14b8a6; | |
| --text: #1f2937; | |
| --muted: #6b7280; | |
| --shadow: 0 12px 30px rgba(15, 61, 94, 0.12); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| margin: 0; | |
| font-family: "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| } | |
| .app-shell { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 24px 20px 48px; | |
| } | |
| header { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 16px; | |
| margin-bottom: 20px; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| .brand-mark { | |
| width: 52px; | |
| height: 52px; | |
| border-radius: 16px; | |
| background: linear-gradient(135deg, #0f3d5e, #14b8a6); | |
| color: #fff; | |
| font-weight: 700; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 20px; | |
| letter-spacing: 2px; | |
| } | |
| .brand-title { | |
| font-size: 22px; | |
| font-weight: 700; | |
| } | |
| .brand-subtitle { | |
| color: var(--muted); | |
| margin-top: 4px; | |
| } | |
| .chips { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| .chip { | |
| background: rgba(20, 184, 166, 0.12); | |
| color: #0f766e; | |
| padding: 6px 12px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| } | |
| .overview { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); | |
| gap: 16px; | |
| margin-bottom: 24px; | |
| } | |
| .overview-card { | |
| background: var(--card); | |
| border-radius: 16px; | |
| padding: 18px; | |
| box-shadow: var(--shadow); | |
| } | |
| .overview-title { | |
| font-size: 13px; | |
| color: var(--muted); | |
| } | |
| .overview-value { | |
| font-size: 26px; | |
| font-weight: 700; | |
| margin-top: 8px; | |
| } | |
| .layout { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); | |
| gap: 20px; | |
| } | |
| .panel { | |
| background: var(--card); | |
| border-radius: 18px; | |
| padding: 20px; | |
| box-shadow: var(--shadow); | |
| } | |
| .panel-title { | |
| font-size: 16px; | |
| font-weight: 700; | |
| margin-bottom: 12px; | |
| } | |
| .panel-subtitle { | |
| color: var(--muted); | |
| margin-bottom: 16px; | |
| font-size: 13px; | |
| } | |
| .form-grid { | |
| display: grid; | |
| gap: 12px; | |
| } | |
| label { | |
| font-size: 13px; | |
| color: var(--muted); | |
| } | |
| input, | |
| textarea { | |
| width: 100%; | |
| border-radius: 12px; | |
| border: 1px solid #e5e7eb; | |
| padding: 10px 12px; | |
| font-size: 14px; | |
| background: #f9fafb; | |
| } | |
| textarea { | |
| min-height: 110px; | |
| resize: vertical; | |
| } | |
| .btn-row { | |
| display: flex; | |
| gap: 12px; | |
| flex-wrap: wrap; | |
| } | |
| button { | |
| border: none; | |
| border-radius: 12px; | |
| padding: 10px 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| } | |
| .btn-primary { | |
| background: var(--primary); | |
| color: #fff; | |
| } | |
| .btn-ghost { | |
| background: rgba(15, 61, 94, 0.08); | |
| color: var(--primary); | |
| } | |
| .status-card { | |
| background: #0f3d5e; | |
| color: #fff; | |
| border-radius: 16px; | |
| padding: 16px; | |
| margin-top: 16px; | |
| } | |
| .timeline { | |
| display: grid; | |
| gap: 16px; | |
| } | |
| .timeline-item { | |
| border: 1px solid #e5e7eb; | |
| border-radius: 14px; | |
| padding: 16px; | |
| background: #fbfdff; | |
| } | |
| .timeline-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 8px; | |
| } | |
| .badge { | |
| background: rgba(15, 61, 94, 0.1); | |
| color: var(--primary); | |
| padding: 4px 10px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| } | |
| .badge-soft { | |
| background: rgba(20, 184, 166, 0.1); | |
| color: #0f766e; | |
| } | |
| .timeline-type { | |
| font-size: 13px; | |
| } | |
| .markdown { | |
| line-height: 1.7; | |
| font-size: 14px; | |
| } | |
| .history-list, | |
| .asset-list { | |
| display: grid; | |
| gap: 10px; | |
| margin-top: 12px; | |
| } | |
| .history-item, | |
| .asset-item { | |
| border: 1px solid #e5e7eb; | |
| border-radius: 12px; | |
| padding: 12px; | |
| background: #fff; | |
| cursor: pointer; | |
| } | |
| .muted { | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .footer-tip, | |
| .upload-tip { | |
| margin-top: 18px; | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| @media (max-width: 960px) { | |
| .layout { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-shell"> | |
| <header> | |
| <div class="brand"> | |
| <div class="brand-mark">CC</div> | |
| <div> | |
| <div class="brand-title">冷链闭环智能体 · ColdChain Optimizer</div> | |
| <div class="brand-subtitle"> | |
| 推理 / 工具行动 / 状态记忆 / 校验验收 / 迭代复盘 | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chips"> | |
| <div class="chip">医药冷链</div> | |
| <div class="chip">多智能体协作</div> | |
| <div class="chip">资产沉淀</div> | |
| <div class="chip">移动端适配</div> | |
| </div> | |
| </header> | |
| <section class="overview" id="overview"> | |
| <div class="overview-card"> | |
| <div class="overview-title">累计会话</div> | |
| <div class="overview-value" id="session-count">0</div> | |
| </div> | |
| <div class="overview-card"> | |
| <div class="overview-title">闭环步骤</div> | |
| <div class="overview-value" id="step-count">0</div> | |
| </div> | |
| <div class="overview-card"> | |
| <div class="overview-title">沉淀资产</div> | |
| <div class="overview-value" id="asset-count">0</div> | |
| </div> | |
| </section> | |
| <main class="layout"> | |
| <section class="panel"> | |
| <div class="panel-title">冷链场景输入</div> | |
| <div class="panel-subtitle"> | |
| 输入真实业务场景,系统将自动生成完整闭环,并支持历史回放 | |
| </div> | |
| <form id="run-form" class="form-grid"> | |
| <div> | |
| <label>会话名称</label> | |
| <input id="name" placeholder="如:华东医药冷链升级计划" /> | |
| </div> | |
| <div> | |
| <label>场景描述</label> | |
| <textarea id="scenario" placeholder="描述冷链网络、温控要求、运输路径、服务对象等"></textarea> | |
| </div> | |
| <div> | |
| <label>目标指标</label> | |
| <input id="target" placeholder="如:准时交付率≥98%,温控合规率≥99.5%" /> | |
| </div> | |
| <div> | |
| <label>约束条件</label> | |
| <textarea id="constraints" placeholder="如:夜间限行、预算、车辆通行证等"></textarea> | |
| </div> | |
| <div class="btn-row"> | |
| <button class="btn-primary" type="submit" id="run-btn">生成闭环</button> | |
| <button class="btn-ghost" type="button" id="demo-btn">填充示例</button> | |
| </div> | |
| </form> | |
| <div class="status-card" id="status-card">等待生成冷链闭环方案</div> | |
| <div class="panel-title" style="margin-top: 18px;">历史会话资产</div> | |
| <div class="history-list" id="history-list"></div> | |
| <div class="panel-title" style="margin-top: 18px;">数据上传(可选)</div> | |
| <div class="history-list"> | |
| <div class="history-item"> | |
| <div class="muted upload-tip"> | |
| 可上传冷链线路 CSV/JSON 或二进制温度记录文件,系统将只记录文件名与大小作为资产示例,避免大文件占用 Hugging Face 空间。 | |
| </div> | |
| <input type="file" id="file-input" /> | |
| <div class="btn-row" style="margin-top: 8px;"> | |
| <button class="btn-ghost" type="button" id="upload-btn">上传文件并登记资产</button> | |
| </div> | |
| <div class="muted" id="upload-status">尚未上传任何文件</div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="panel"> | |
| <div class="panel-title">闭环轨迹与资产</div> | |
| <div class="panel-subtitle">按时间顺序展示本轮推理与执行过程(已内置示例会话)</div> | |
| <div class="timeline" id="timeline"></div> | |
| <div class="panel-title" style="margin-top: 18px;">资产沉淀</div> | |
| <div class="asset-list" id="asset-list"></div> | |
| <div class="footer-tip"> | |
| 提示:设置 .env 的 SILICONFLOW_API_KEY 可接入真实硅基流推理 | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
| <script> | |
| const sessionCountEl = document.getElementById("session-count"); | |
| const stepCountEl = document.getElementById("step-count"); | |
| const assetCountEl = document.getElementById("asset-count"); | |
| const historyListEl = document.getElementById("history-list"); | |
| const timelineEl = document.getElementById("timeline"); | |
| const assetListEl = document.getElementById("asset-list"); | |
| const statusCardEl = document.getElementById("status-card"); | |
| const runForm = document.getElementById("run-form"); | |
| const runBtn = document.getElementById("run-btn"); | |
| const demoBtn = document.getElementById("demo-btn"); | |
| const renderMarkdown = (content) => { | |
| if (!content) return ""; | |
| return marked.parse(content); | |
| }; | |
| const updateOverview = async () => { | |
| const res = await fetch("/api/overview"); | |
| const data = await res.json(); | |
| sessionCountEl.textContent = data.session_count || 0; | |
| stepCountEl.textContent = data.step_count || 0; | |
| assetCountEl.textContent = data.asset_count || 0; | |
| }; | |
| const loadHistory = async () => { | |
| const res = await fetch("/api/sessions"); | |
| const data = await res.json(); | |
| historyListEl.innerHTML = ""; | |
| (data.sessions || []).forEach((item) => { | |
| const div = document.createElement("div"); | |
| div.className = "history-item"; | |
| div.innerHTML = ` | |
| <div><strong>${item.name || "未命名会话"}</strong></div> | |
| <div class="muted">${item.created_at || ""}</div> | |
| <div class="muted">目标:${item.target || "未填写"}</div> | |
| `; | |
| div.addEventListener("click", () => loadSession(item.id)); | |
| historyListEl.appendChild(div); | |
| }); | |
| }; | |
| const stepTypeLabel = (type) => { | |
| const map = { | |
| reasoning: "推理决策", | |
| tool_action: "工具行动", | |
| memory: "状态记忆", | |
| validation: "校验验收", | |
| iteration: "迭代复盘", | |
| }; | |
| return map[type] || type || "步骤"; | |
| }; | |
| const stepRoleLabel = (role) => { | |
| const map = { | |
| Planner: "规划智能体", | |
| RouteOps: "线路与运力智能体", | |
| Memory: "资产沉淀智能体", | |
| QualityAuditor: "质量合规智能体", | |
| Optimizer: "迭代优化智能体", | |
| }; | |
| return map[role] || role || "智能体"; | |
| }; | |
| const renderTimeline = (steps = []) => { | |
| timelineEl.innerHTML = ""; | |
| if (!steps.length) { | |
| timelineEl.innerHTML = | |
| "<div class='muted'>暂无闭环步骤,可点击左侧示例按钮快速体验。</div>"; | |
| return; | |
| } | |
| steps.forEach((step) => { | |
| const card = document.createElement("div"); | |
| card.className = "timeline-item"; | |
| card.innerHTML = ` | |
| <div class="timeline-header"> | |
| <div class="timeline-type"> | |
| <strong>${stepTypeLabel(step.step_type)}</strong> | |
| · ${stepRoleLabel(step.role)} | |
| </div> | |
| <span class="badge badge-soft">步骤 ${step.step_order}</span> | |
| </div> | |
| <div class="markdown">${renderMarkdown(step.content)}</div> | |
| `; | |
| timelineEl.appendChild(card); | |
| }); | |
| }; | |
| const renderAssets = (assets = []) => { | |
| assetListEl.innerHTML = ""; | |
| if (!assets.length) { | |
| assetListEl.innerHTML = "<div class='muted'>暂无资产沉淀</div>"; | |
| return; | |
| } | |
| assets.forEach((asset) => { | |
| const card = document.createElement("div"); | |
| card.className = "asset-item"; | |
| const isMetrics = asset.asset_type === "metrics"; | |
| const content = isMetrics | |
| ? `<pre class="muted">${asset.content}</pre>` | |
| : renderMarkdown(asset.content); | |
| card.innerHTML = ` | |
| <div><strong>${asset.title}</strong></div> | |
| <div class="muted">${asset.created_at || ""}</div> | |
| <div class="markdown">${content}</div> | |
| `; | |
| assetListEl.appendChild(card); | |
| }); | |
| }; | |
| const loadSession = async (sessionId) => { | |
| const res = await fetch(`/api/session/${sessionId}`); | |
| const data = await res.json(); | |
| if (data.error) { | |
| statusCardEl.textContent = data.error; | |
| return; | |
| } | |
| const title = data.session?.name || "冷链运营会话"; | |
| statusCardEl.textContent = `正在回放:${title}`; | |
| renderTimeline(data.steps || []); | |
| renderAssets(data.assets || []); | |
| }; | |
| runForm.addEventListener("submit", async (event) => { | |
| event.preventDefault(); | |
| runBtn.disabled = true; | |
| statusCardEl.textContent = "正在生成闭环方案,请稍候..."; | |
| const payload = { | |
| name: document.getElementById("name").value, | |
| scenario: document.getElementById("scenario").value, | |
| target: document.getElementById("target").value, | |
| constraints: document.getElementById("constraints").value, | |
| }; | |
| const res = await fetch("/api/run", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await res.json(); | |
| if (data.error) { | |
| statusCardEl.textContent = data.error; | |
| runBtn.disabled = false; | |
| return; | |
| } | |
| statusCardEl.textContent = "闭环生成完成,可继续迭代"; | |
| renderTimeline(data.steps || []); | |
| renderAssets(data.assets || []); | |
| await updateOverview(); | |
| await loadHistory(); | |
| runBtn.disabled = false; | |
| }); | |
| const fileInput = document.getElementById("file-input"); | |
| const uploadBtn = document.getElementById("upload-btn"); | |
| const uploadStatus = document.getElementById("upload-status"); | |
| uploadBtn.addEventListener("click", async () => { | |
| if (!fileInput.files || !fileInput.files[0]) { | |
| uploadStatus.textContent = "请先选择要上传的文件"; | |
| return; | |
| } | |
| const file = fileInput.files[0]; | |
| uploadStatus.textContent = "正在上传与登记资产..."; | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| try { | |
| const res = await fetch("/api/upload", { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| const data = await res.json(); | |
| if (data.error) { | |
| uploadStatus.textContent = data.error; | |
| return; | |
| } | |
| uploadStatus.textContent = `已登记资产:${data.filename}(${data.size_kb} KB)`; | |
| await updateOverview(); | |
| await loadHistory(); | |
| } catch (e) { | |
| uploadStatus.textContent = "上传失败,请稍后重试"; | |
| } | |
| }); | |
| demoBtn.addEventListener("click", () => { | |
| document.getElementById("name").value = "华南疫苗冷链优化"; | |
| document.getElementById("scenario").value = | |
| "覆盖 4 个省会城市与 16 家疾控中心,要求 2-8℃温控,疫苗需 12 小时内送达;" + | |
| "高峰期需提升运力 30%,并确保可追溯与异常预警。"; | |
| document.getElementById("target").value = | |
| "准时交付率≥98%,温控合规率≥99.7%,运输成本降低 10%"; | |
| document.getElementById("constraints").value = | |
| "夜间限行、高速收费上涨、部分区域冷库容量不足"; | |
| }); | |
| updateOverview(); | |
| loadHistory(); | |
| renderTimeline([]); | |
| renderAssets([]); | |
| </script> | |
| </body> | |
| </html> | |