1 line
254 KiB
JSON
1 line
254 KiB
JSON
{"repo": "465146/xiaonuanai", "files": {"start.sh": {"d2530a58543b4c059a5a83f8eafce9c879381219": {"t": 1785745955.0963318, "n": 3586, "h": "6fa5d35b8b3cc03b", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\necho \"[start] Installing music API...\"\n\n# 确保 submodule 存在(Railway 可能不会 git clone --recursive)\nif [ ! -f netease-api/app.js ]; then\n echo \"[start] Pulling netease-api submodule...\"\n git submodule update --init --recursive 2>/dev/null || \\\n git clone --depth 1 https://github.com/NeteaseCloudMusicAPIEnhanced/api-enhanced.git netease-api 2>/dev/null || \\\n echo \"[start] WARNING: Cannot fetch netease-api, music fallback to public API\"\nfi\n\n(cd netease-api && npm install 2>&1 | tail -3)\n\necho \"[start] Starting music API (port 3000)...\"\n(cd netease-api && PORT=3000 node app.js) &\nMUSIC_PID=$!\necho \"[start] Music API PID=$MUSIC_PID\"\nsleep 3\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "7277436bcab241747ad1dd7204b44acd42170062": {"t": 1785758247.472025, "n": 3180, "h": "5414f2e1f1ec6130", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\necho \"[start] Installing music API...\"\n(cd netease-api && npm install 2>&1 | tail -3)\n\necho \"[start] Starting music API (port 3000)...\"\n(cd netease-api && PORT=3000 node app.js) &\nMUSIC_PID=$!\necho \"[start] Music API PID=$MUSIC_PID\"\nsleep 3\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "70b03c973b7ba56671d253a7d5b0d88f012f890e": {"t": 1785758248.7871647, "n": 3170, "h": "984be21419d56409", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\necho \"[start] Installing music API...\"\n(cd netease-api && npm install 2>&1 | tail -3)\n\necho \"[start] Starting music API (port 3000)...\"\n(cd netease-api && node app.js) &\nMUSIC_PID=$!\necho \"[start] Music API PID=$MUSIC_PID\"\nsleep 3\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "91e734f056e72d435c7f227c6288492275e7828c": {"t": 1785758252.517796, "n": 3181, "h": "c294ece246c1168a", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\necho \"[start] Installing music API...\"\ncd netease-api && npm install 2>&1 | tail -3 && cd ..\n\necho \"[start] Starting music API (port 3000)...\"\ncd netease-api && node app.js &\nMUSIC_PID=$!\necho \"[start] Music API PID=$MUSIC_PID\"\ncd ..\nsleep 3\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "aaf7d937fcd4ae20708617a818d2ebbfc38602cf": {"t": 1785758253.8934076, "n": 2938, "h": "5e5f1efa96ef2290", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "27381a485be99009bd45ed415a1294a5cf0b6e2f": {"t": 1785758255.615945, "n": 3407, "h": "5fbd968975cf0d72", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw + music API...\"\nnpm install --omit=none openclaw@2026.5.2\nnpm install NeteaseCloudMusicApi 2>/dev/null || echo \"[start] music API skipped\"\n\n# ── Start NeteaseCloudMusicApi (background, port 3000) ──\nif [ -f \"node_modules/NeteaseCloudMusicApi/app.js\" ]; then\n PORT=3000 node node_modules/NeteaseCloudMusicApi/app.js &\n echo \"[start] Music API PID=$! (port 3000)\"\nelif command -v NeteaseCloudMusicApi >/dev/null 2>&1; then\n PORT=3000 NeteaseCloudMusicApi &\n echo \"[start] Music API PID=$! (port 3000)\"\nfi\nsleep 2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "78b90dd23b7188fecb1fe29e3c198139fea0905e": {"t": 1785758257.145053, "n": 3411, "h": "249f063587d58bb7", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw + music API...\"\nnpm install --omit=none openclaw@2026.5.2\nnpm install NeteaseCloudMusicApi 2>/dev/null || echo \"[start] music API skipped\"\n\n# ── Start NeteaseCloudMusicApi (background, port 3000) ──\nif [ -f \"node_modules/NeteaseCloudMusicApi/app.js\" ]; then\n node node_modules/NeteaseCloudMusicApi/app.js --port 3000 &\n echo \"[start] Music API PID=$! (port 3000)\"\nelif command -v NeteaseCloudMusicApi >/dev/null 2>&1; then\n NeteaseCloudMusicApi --port 3000 &\n echo \"[start] Music API PID=$! (port 3000)\"\nfi\nsleep 2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "24668a1bf97be698c2f4aa8e014e7670664ddb4f": {"t": 1785758258.8553782, "n": 3057, "h": "127d29991c2920f0", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw + ncm-cli...\"\nnpm install --omit=none openclaw@2026.5.2\nnpm install -g @music163/ncm-cli 2>/dev/null || echo \"[start] ncm-cli skipped (won't affect core functions)\"\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "436e6d25e62d75bb0eb1b600e9e9c6dcffc893ac": {"t": 1785758260.0933335, "n": 2938, "h": "5e5f1efa96ef2290", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\n# 主配置路径\nmkdir -p \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway/.\" \"$HOME/.openclaw/\"\n# 嵌套路径(Gateway 某些版本会检查 ~/.openclaw/.openclaw/openclaw.json)\nmkdir -p \"$HOME/.openclaw/.openclaw\"\ncp \"$(pwd)/gateway/openclaw.json\" \"$HOME/.openclaw/.openclaw/openclaw.json\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] main: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \"[start] nested: $([ -f \"$OPENCLAW_HOME/.openclaw/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "a48dd63dd07a1945f98fed9c491ac4506378df33": {"t": 1785758261.478334, "n": 2660, "h": "67f8c23f7188621b", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install --omit=none openclaw@2026.5.2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "b94bfca87bcab1f0dd75b5a74673f7bd0d0f3754": {"t": 1785758266.5872657, "n": 2691, "h": "ee4d146fbef49cba", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw (all deps)...\"\nnpm config set production false\nnpm install openclaw@2026.5.2\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "08e84a2eb64176ab90335ccea57137b05c6aa90b": {"t": 1785758268.3789823, "n": 2693, "h": "36a10ccb573ba4b0", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw (including optional deps)...\"\nnpm install --include=optional openclaw@2026.4.8\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "28be2cb1e2afd0b705224666a8b6a8e54dcd7deb": {"t": 1785758270.8722148, "n": 2648, "h": "efc24a3f8830be02", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install openclaw@2026.4.8\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "5f62ccf344a19bad5405c886e5fa50c6a7600e08": {"t": 1785758272.7589943, "n": 2627, "h": "2c1c458adb880cba", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\nexport PORT=\"${PORT:-8000}\"\n\n# ── Install Node.js if not available ──\nif ! command -v node >/dev/null 2>&1; then\n echo \"[start] Node.js not found, downloading...\"\n NODE_VER=\"22.14.0\"\n curl -fsSL \"https://nodejs.org/dist/v${NODE_VER}/node-v${NODE_VER}-linux-x64.tar.xz\" -o /tmp/node.tar.xz\n tar -xJf /tmp/node.tar.xz -C /tmp\n export PATH=\"/tmp/node-v${NODE_VER}-linux-x64/bin:$PATH\"\n echo \"[start] Node.js v${NODE_VER} installed\"\nfi\necho \"[start] node=$(node --version) npm=$(npm --version)\"\n\n# ── Install OpenClaw ──\necho \"[start] Installing openclaw...\"\nnpm install openclaw@2026.4.8\n\n# ── Generate Gateway runtime files ──\nmkdir -p gateway/agents/cbt/agent gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nfi\n\n# ── Start OpenClaw Gateway ──\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\necho \"[start] Config: OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \"[start] openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\n\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n [ $i -eq 30 ] && echo \"[start] WARNING: Gateway not ready after 30s\"\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# nix store fallback (for when provider installs but PATH misses it)\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls -d /nix/store/*python3* 2>/dev/null | head -5 || echo \"(none)\"\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "2a5c0c3c85f932ac920fb1fb7e83c2523a714887": {"t": 1785758274.0778117, "n": 3161, "h": "f2eef42d0417ade4", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Ensure Node modules are installed ──\nif [ ! -f \"node_modules/openclaw/dist/index.js\" ]; then\n echo \"[start] Installing OpenClaw Gateway...\"\n npm install openclaw@latest || true\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nGATEWAY_PID=\"\"\nif command -v npx >/dev/null 2>&1; then\n npx openclaw gateway --port 18789 &\n GATEWAY_PID=$!\nelif [ -f \"node_modules/openclaw/dist/index.js\" ]; then\n node node_modules/openclaw/dist/index.js gateway --port 18789 &\n GATEWAY_PID=$!\nelif command -v openclaw >/dev/null 2>&1; then\n openclaw gateway --port 18789 &\n GATEWAY_PID=$!\nfi\nif [ -n \"$GATEWAY_PID\" ]; then\n echo \"[start] Gateway PID=$GATEWAY_PID\"\nelse\n echo \"[start] WARNING: Could not start Gateway\"\nfi\n\n# Wait for Gateway to be ready\nif [ -n \"$GATEWAY_PID\" ]; then\n for i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\n done\nfi\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\n# Fallback: search nix store for Python\nif [ -z \"$PY\" ]; then\n for d in /nix/store/*python*3.1*/bin /nix/store/*python3*/bin; do\n if [ -x \"$d/python3\" ]; then\n export PATH=\"$d:$PATH\"\n PY=\"python3\"\n echo \"[start] Found Python in nix store: $d\"\n break\n fi\n done 2>/dev/null\nfi\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n ls /nix/store/*python3*/bin/python* 2>/dev/null | head -5 || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "ea1bf4452b471d5b744c8b5375711f9fbc10cd3a": {"t": 1785758278.245557, "n": 2573, "h": "e1d989ea224a14be", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Install Node.js dependencies ──\nif command -v npm >/dev/null 2>&1; then\n echo \"[start] npm available, installing OpenClaw...\"\n npm install openclaw@latest || true\n echo \"[start] npm install exit code: $?\"\n ls node_modules/openclaw/dist/index.js 2>/dev/null && echo \"[start] openclaw dist found\" || echo \"[start] openclaw dist NOT found\"\nelse\n echo \"[start] npm not available\"\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "fac4510161630fb163b4cb6e3a0df6f2c36923d0": {"t": 1785758281.0642073, "n": 2329, "h": "d09028068b6168c1", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Install Node.js dependencies ──\nif command -v npm >/dev/null 2>&1; then\n echo \"[start] Installing OpenClaw Gateway...\"\n npm install openclaw@latest 2>&1 | tail -5\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnpx openclaw gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "ded0a4932d20a5d314ed0f5920366c436e52d0da": {"t": 1785758282.5629258, "n": 2328, "h": "055ef3aa5a578e04", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Install Node.js dependencies ──\nif command -v npm >/dev/null 2>&1; then\n echo \"[start] Installing OpenClaw Gateway...\"\n npm install -g openclaw@latest 2>&1 | tail -5\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nopenclaw gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "806ee0d68ff95357cb8326f51e7dbeeae1e555c7": {"t": 1785758283.9840343, "n": 2431, "h": "c025a1daf9818a2f", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Install Node.js dependencies ──\nif [ -f \"package.json\" ] && command -v npm >/dev/null 2>&1; then\n echo \"[start] Installing npm packages...\"\n npm install --prefer-offline --no-audit --no-fund 2>&1 | tail -3\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnpx openclaw gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "226e12d64295f07d6e71d00ce92e914687d8436e": {"t": 1785758285.5520117, "n": 2214, "h": "a3f919ff164bac5e", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnpx openclaw gateway --port 18789 --allow-unconfigured &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "e344fb898d118d034a65af8d6777a419e79133c8": {"t": 1785758286.735665, "n": 2221, "h": "1c3bb2ac2fe75bb2", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 必须从 ~/.openclaw/ 读取配置(硬拷贝,不用软链接)\nrm -rf \"$HOME/.openclaw\"\ncp -r \"$(pwd)/gateway\" \"$HOME/.openclaw\"\nexport OPENCLAW_HOME=\"$HOME/.openclaw\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" openclaw.json: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" workspace/cbt: $([ -d \"$OPENCLAW_HOME/workspace/cbt\" ] && echo YES || echo NO)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "fb44dc57d150ae18d0e1d3dadfaf17e24bfe5bc3": {"t": 1785758287.966049, "n": 2350, "h": "269056eff848ff70", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Start OpenClaw Gateway (background) ──\n# Gateway 默认从 ~/.openclaw/ 读取配置,创建软链接\nGATEWAY_HOME=\"$HOME/.openclaw\"\nif [ ! -L \"$GATEWAY_HOME\" ] && [ ! -e \"$GATEWAY_HOME\" ]; then\n ln -sf \"$(pwd)/gateway\" \"$GATEWAY_HOME\"\n echo \"[start] Linked gateway -> ~/.openclaw\"\nfi\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\n\necho \"[start] Config check:\"\necho \" OPENCLAW_HOME=$OPENCLAW_HOME\"\necho \" config exists: $([ -f \"$OPENCLAW_HOME/openclaw.json\" ] && echo YES || echo NO)\"\necho \" symlink: $([ -L \"$HOME/.openclaw\" ] && echo '->' \"$(readlink \"$HOME/.openclaw\")\" || echo none)\"\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "196a91f5e1979c9e435f0602c7f3acd30751de05": {"t": 1785758289.216138, "n": 1829, "h": "3f99294b3e779bd1", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\n# ── Environment ──\nexport OPENCLAW_HOME=\"$(pwd)/gateway\"\nexport PORT=\"${PORT:-8000}\"\n\n# ── Generate runtime files from env vars ──\nmkdir -p gateway/agents/cbt/agent gateway/data gateway/workspace/cbt/memory gateway/workspace/cbt/state\nif [ -n \"$DEEPSEEK_API_KEY\" ]; then\n # Gateway auth-profiles.json (agent 使用)\n cat > gateway/agents/cbt/agent/auth-profiles.json << AUTH_EOF\n{\n \"version\": 1,\n \"profiles\": {\n \"deepseek:default\": {\n \"type\": \"api_key\",\n \"provider\": \"deepseek\",\n \"key\": \"${DEEPSEEK_API_KEY}\"\n }\n }\n}\nAUTH_EOF\n # Gateway .env(模板变量替换用)\n echo \"DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\" > gateway/.env\n echo \"[start] Gateway auth files generated\"\nelse\n echo \"[start] WARNING: DEEPSEEK_API_KEY not set, Gateway may fail\"\nfi\n\n# ── Start OpenClaw Gateway (background) ──\necho \"[start] Launching OpenClaw Gateway...\"\nnode node_modules/openclaw/dist/index.js gateway --port 18789 &\nGATEWAY_PID=$!\necho \"[start] Gateway PID=$GATEWAY_PID\"\n\n# Wait for Gateway to be ready\nfor i in $(seq 1 30); do\n if curl -s http://127.0.0.1:18789/health > /dev/null 2>&1; then\n echo \"[start] Gateway ready (port 18789)\"\n break\n fi\n if [ $i -eq 30 ]; then\n echo \"[start] WARNING: Gateway not ready after 30s, continuing anyway\"\n fi\n sleep 1\ndone\n\n# ── Find Python ──\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=$PORT Gateway=18789\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"$PORT\"\n"}, "f4c0c05803f86576825d9ca6110fbca2a31a29ef": {"t": 1785758291.7885563, "n": 545, "h": "b880051e1b2b53fb", "c": "#!/bin/bash\ncd \"$(dirname \"$0\")\"\n\n# 找到可用的 Python\nPY=\"\"\nfor cmd in python3.13 python3.12 python3.11 python3.10 python3 python; do\n if command -v \"$cmd\" >/dev/null 2>&1; then\n PY=\"$cmd\"\n break\n fi\ndone\n\nif [ -z \"$PY\" ]; then\n echo \"ERROR: No Python found!\"\n echo \"PATH=$PATH\"\n ls /usr/bin/python* 2>/dev/null || true\n ls /usr/local/bin/python* 2>/dev/null || true\n exit 1\nfi\n\necho \"=== XiaoNuan CBT ===\"\necho \"Python=$PY PORT=${PORT:-8000}\"\n\nexec \"$PY\" -m uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}, "70d2756841a2f8f3b2a99fbbe93a819592d5862b": {"t": 1785758292.9577823, "n": 300, "h": "bf92785c0edf7f5f", "c": "#!/bin/bash\ncd \"$(dirname \"$0\")\"\n\necho \"=== XiaoNuan CBT ===\"\necho \"PORT=${PORT:-8000}\"\n\n# 诊断(不阻塞启动)\npython3 -c \"import main; print('import ok')\" 2>&1 || echo \"import failed, trying uvicorn anyway...\"\n\necho \"Starting uvicorn...\"\nexec python3 -m uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}, "c7cb6eefc76bd8944a74975f3451ebb71cdb2d64": {"t": 1785758295.4982378, "n": 258, "h": "0ce3c2d3e221523c", "c": "#!/bin/bash\nset -e\ncd \"$(dirname \"$0\")\"\n\necho \"=== XiaoNuan CBT ===\"\necho \"PORT=${PORT:-8000}\"\n\n# 先试 import,失败就打印具体错误\npython -c \"import main; print('OK')\" 2>&1\n\necho \"Starting uvicorn...\"\nexec python -m uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}, "fe40133cff7a09df931c68840f325a91818085c0": {"t": 1785758296.965144, "n": 214, "h": "24ff2dc18935548d", "c": "#!/bin/bash\n# 小暖 CBT 启动脚本 (Linux / Railway)\nset -e\n\necho \"=== XiaoNuan CBT Starting ===\"\necho \"[web] Port: ${PORT:-8000}\"\n\ncd \"$(dirname \"$0\")\"\nexec python -m uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}, "1909fc0f12dc0d14d9947e6ae289e03e62ac2001": {"t": 1785758298.3056011, "n": 819, "h": "d8361098af734e33", "c": "#!/bin/bash\n# 小暖 CBT 启动脚本 (Linux / Railway)\nset -e\n\necho \"=== XiaoNuan CBT Starting ===\"\n\n# ── OpenClaw Gateway(可选)──\n# Railway 上 openclaw 可能无法正常工作(微信插件依赖本地环境)\n# 尝试启动,失败不影响网站功能\nif command -v openclaw &> /dev/null || command -v npx &> /dev/null; then\n echo \"[openclaw] Starting Gateway (best-effort)...\"\n openclaw gateway --port 18789 &>/tmp/openclaw.log &\n sleep 3\n if curl -s http://127.0.0.1:18789/health >/dev/null 2>&1; then\n echo \"[openclaw] Gateway ready\"\n else\n echo \"[openclaw] Gateway failed to start — website will run without enhanced mode\"\n fi\nelse\n echo \"[openclaw] Not available — running in basic mode\"\nfi\n\n# ── FastAPI 网站 ──\necho \"[web] Starting FastAPI on port ${PORT:-8000}...\"\ncd \"$(dirname \"$0\")\"\nexec uv run uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}, "739e2e2261104e253dd2d3852254bdc3fa2f2553": {"t": 1785758299.7009177, "n": 598, "h": "8930913fcde8f48b", "c": "#!/bin/bash\n# 小暖 CBT 启动脚本\n# 1. 安装 OpenClaw(可选,如果 package.json 存在)\n# 2. 启动 OpenClaw Gateway(可选,后台)\n# 3. 安装 Python 依赖\n# 4. 启动 FastAPI 网站\n\nset -e\n\necho \"=== 小暖 CBT 启动中 ===\"\n\n# ── OpenClaw Gateway(可选,如果已安装)──\nif command -v openclaw &> /dev/null || [ -f \"$HOME/.npm-global/bin/openclaw\" ]; then\n echo \"[openclaw] 启动 Gateway...\"\n openclaw gateway --port 18789 &\n sleep 2\n echo \"[openclaw] Gateway 已启动\"\nelse\n echo \"[openclaw] 未安装,跳过 Gateway(网站仍可正常使用)\"\nfi\n\n# ── FastAPI 网站 ──\necho \"[web] 启动 FastAPI...\"\ncd \"$(dirname \"$0\")\"\nexec uv run uvicorn main:app --host 0.0.0.0 --port \"${PORT:-8000}\"\n"}}, "templates/index.html": {"d2530a58543b4c059a5a83f8eafce9c879381219": {"t": 1785746427.3861594, "n": 20210, "h": "ace1a48da557f194", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css?v=2\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=7\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "df56528239fb35ae1cf81659210374bcad48fe9a": {"t": 1785758246.281471, "n": 20210, "h": "ace1a48da557f194", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css?v=2\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=7\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "e338a0de57c70e9572998cede1db2933fbbedf9d": {"t": 1785758247.7614934, "n": 20210, "h": "450b8760c0f41d5a", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css?v=2\">\n <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=6\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "8407dd60e7a687636d5d1f438aefa49b968b6ce1": {"t": 1785758249.7079217, "n": 20022, "h": "46e8b32a1d8e7c9a", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css?v=2\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=5\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "9dc708f7aff54cb2edb33bdea1ba3edc7f5df755": {"t": 1785758252.0553045, "n": 20018, "h": "da2a69447815ebba", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=4\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "24668a1bf97be698c2f4aa8e014e7670664ddb4f": {"t": 1785758253.863635, "n": 20018, "h": "ffd7e283d2a62db7", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=3\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "d03397a11d95872dc08c3adf40fe55a3768987df": {"t": 1785758255.663657, "n": 20018, "h": "71c41a638b6d9317", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 已连接状态 -->\n <div id=\"wechatConnected\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信已连接</h3>\n <p style=\"color:#666;\">Bot ID: <span id=\"connectedBotId\"></span></p>\n <button class=\"btn-primary\" id=\"wechatReconnect\" style=\"margin-top:16px;\">🔄 重新连接</button>\n </div>\n\n <!-- 未连接:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:16px;\">点击按钮,生成你的专属微信二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 获取我的微信二维码\n </button>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\" style=\"margin-top:12px;\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=2\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信页面:每个用户独立扫码连接\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatConnected','wechatInit','wechatQR','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载:检查当前用户是否已连接微信\n (async function checkConnection() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n if (data.connected && data.bot_id) {\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = data.bot_id;\n } else {\n show('wechatInit');\n }\n } catch(e) { show('wechatInit'); }\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成你的专属二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常'); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = '请用微信扫描二维码';\n document.getElementById('wechatQRImg').src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,在微信中确认...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer); pollTimer = null;\n show('wechatConnected');\n document.getElementById('connectedBotId').textContent = sd.bot_id || '';\n }\n if (sd.status === 'expired' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n if (sd.status === 'expired') {\n document.getElementById('wechatFailMsg').textContent = '二维码已过期,请重新获取';\n } else {\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n }\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 获取我的微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n document.getElementById('wechatReconnect').addEventListener('click', reset);\n })();\n </script>\n</body>\n</html>\n"}, "739e2e2261104e253dd2d3852254bdc3fa2f2553": {"t": 1785758257.4013019, "n": 21286, "h": "e113ea4c12aa8f1a", "c": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>小暖 - AI 心理陪伴伙伴</title>\n <link rel=\"stylesheet\" href=\"/static/css/style.css\">\n <script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js\"></script>\n <script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>\n</head>\n<body>\n <div class=\"app-container\">\n <!-- 左侧导航 -->\n <nav class=\"sidebar\" id=\"sidebar\">\n <div class=\"sidebar-brand\">\n <div class=\"brand-avatar\">🫧</div>\n <div class=\"brand-name\">小暖</div>\n <div class=\"brand-sub\">AI 心理陪伴</div>\n <div class=\"brand-status\" id=\"gatewayStatus\" title=\"检测中...\">⚪</div>\n </div>\n <ul class=\"nav-list\">\n <li class=\"nav-item active\" data-page=\"chat\">\n <span class=\"nav-icon\">💬</span>\n <span class=\"nav-text\">小暖对话</span>\n </li>\n <li class=\"nav-item\" data-page=\"dashboard\">\n <span class=\"nav-icon\">📊</span>\n <span class=\"nav-text\">情绪仪表盘</span>\n </li>\n <li class=\"nav-item\" data-page=\"diary\">\n <span class=\"nav-icon\">📝</span>\n <span class=\"nav-text\">情绪日记</span>\n </li>\n <li class=\"nav-item\" data-page=\"phq9\">\n <span class=\"nav-icon\">📋</span>\n <span class=\"nav-text\">PHQ-9 测试</span>\n </li>\n <li class=\"nav-item\" data-page=\"wechat\">\n <span class=\"nav-icon\">📱</span>\n <span class=\"nav-text\">连接微信</span>\n </li>\n <li class=\"nav-item\" data-page=\"settings\">\n <span class=\"nav-icon\">⚙️</span>\n <span class=\"nav-text\">我的设置</span>\n </li>\n </ul>\n <div class=\"sidebar-user\">\n <div class=\"sidebar-user-info\">\n <span class=\"sidebar-user-icon\">👤</span>\n <span class=\"sidebar-user-name\" id=\"userDisplay\">--</span>\n </div>\n <button class=\"btn-sm btn-ghost\" id=\"logoutBtn\">退出</button>\n </div>\n </nav>\n\n <!-- 右侧内容 -->\n <main class=\"main-content\">\n <!-- 聊天页面 -->\n <section class=\"page active\" id=\"page-chat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle\">☰</button>\n <h2>💬 和小暖聊聊</h2>\n <button class=\"btn-sm btn-ghost\" id=\"clearChat\">清空对话</button>\n </div>\n <div class=\"chat-container\">\n <div class=\"chat-messages\" id=\"chatMessages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-emoji\">🫧</div>\n <h3>嗨,我是小暖 👋</h3>\n <p>一个温暖的 AI 心理陪伴伙伴。<br>无论你想聊什么,我都在这里倾听。</p>\n <p class=\"welcome-hint\">今天感觉怎么样?</p>\n </div>\n </div>\n <div class=\"chat-input-area\">\n <input type=\"text\" class=\"chat-input\" id=\"chatInput\"\n placeholder=\"说说你的感受...\" autocomplete=\"off\">\n <button class=\"btn-send\" id=\"sendBtn\" title=\"发送\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M22 2L11 13\"/><path d=\"M22 2l-7 20-4-9-9-4 20-7z\"/>\n </svg>\n </button>\n </div>\n </div>\n </section>\n\n <!-- 仪表盘页面 -->\n <section class=\"page\" id=\"page-dashboard\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle2\">☰</button>\n <h2>📊 情绪仪表盘</h2>\n </div>\n <div class=\"dashboard-content\">\n <div class=\"stats-row\" id=\"statsRow\">\n <div class=\"stat-card\">\n <div class=\"stat-label\">今日情绪</div>\n <div class=\"stat-value\" id=\"statToday\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">本周平均</div>\n <div class=\"stat-value\" id=\"statAvg\">--</div>\n </div>\n <div class=\"stat-card\">\n <div class=\"stat-label\">最新 PHQ-9</div>\n <div class=\"stat-value\" id=\"statPhq9\" style=\"font-size:18px;\">--</div>\n </div>\n </div>\n <div class=\"chart-card\">\n <h3>📈 情绪分数趋势(30天)</h3>\n <div class=\"chart-wrap\" id=\"chartMood\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📋 PHQ-9 测试历史</h3>\n <div class=\"chart-wrap\" id=\"chartPHQ9\"></div>\n </div>\n <div class=\"chart-card\">\n <h3>📝 周度文字报告</h3>\n <pre class=\"report-text\" id=\"weeklyReport\">加载中...</pre>\n </div>\n </div>\n </section>\n\n <!-- 日记页面 -->\n <section class=\"page\" id=\"page-diary\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle3\">☰</button>\n <h2>📝 情绪日记</h2>\n <button class=\"btn-sm btn-primary\" id=\"genDiary\">✨ 生成今日日记</button>\n </div>\n <div class=\"diary-content\">\n <div class=\"diary-list\" id=\"diaryList\">\n <p class=\"empty-hint\">加载中...</p>\n </div>\n <div class=\"diary-detail\" id=\"diaryDetail\">\n <p class=\"empty-hint\">点击左侧日记查看详情</p>\n </div>\n <div class=\"diary-actions\" id=\"diaryActions\" style=\"display:none;\">\n <button class=\"btn-sm btn-primary\" id=\"downloadDiary\">📥 下载此日记</button>\n <select id=\"downloadFormat\" class=\"btn-sm\" style=\"margin-left:8px;\">\n <option value=\"md\">Markdown (.md)</option>\n <option value=\"txt\">纯文本 (.txt)</option>\n </select>\n </div>\n </div>\n </section>\n\n <!-- PHQ-9 页面 -->\n <section class=\"page\" id=\"page-phq9\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle4\">☰</button>\n <h2>📋 PHQ-9 抑郁筛查</h2>\n </div>\n <div class=\"phq9-content\">\n <div class=\"phq9-intro\" id=\"phq9Intro\">\n <div class=\"card\">\n <h3>国际通用情绪状态自评量表</h3>\n <p>PHQ-9 是国际通用的抑郁症状自评量表,基于 DSM-5 诊断标准设计。</p>\n <p><strong>注意:</strong>结果仅作为自我觉察的参考,不能替代专业诊断。</p>\n <p class=\"text-muted\">大约需要 2 分钟完成</p>\n <button class=\"btn-primary\" id=\"startPHQ9\">开始测试</button>\n </div>\n </div>\n <div class=\"phq9-question\" id=\"phq9Question\" style=\"display:none\">\n <div class=\"card\">\n <div class=\"progress-bar\"><div class=\"progress-fill\" id=\"phq9Progress\"></div></div>\n <p class=\"question-num\" id=\"phq9Num\">第 1/9 题</p>\n <p class=\"question-text\" id=\"phq9Text\"></p>\n <div class=\"option-buttons\" id=\"phq9Options\"></div>\n </div>\n </div>\n <div class=\"phq9-result\" id=\"phq9Result\" style=\"display:none\">\n <div class=\"card\" id=\"phq9ResultCard\"></div>\n </div>\n <div class=\"phq9-history\" id=\"phq9History\">\n <h3>📜 历史记录</h3>\n <div id=\"phq9HistoryList\"><p class=\"empty-hint\">加载中...</p></div>\n </div>\n </div>\n </section>\n\n <!-- 微信页面 -->\n <section class=\"page\" id=\"page-wechat\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle5\">☰</button>\n <h2>📱 连接微信</h2>\n </div>\n <div class=\"wechat-content\">\n <div class=\"card wechat-card\">\n <h3>📱 扫码连接微信</h3>\n\n <!-- 初始状态:按钮 -->\n <div id=\"wechatInit\">\n <p style=\"color:#666;margin-bottom:20px;\">点击按钮,获取 Gateway 微信机器人的二维码</p>\n <button class=\"btn-primary\" id=\"startWechatLogin\" style=\"font-size:16px;padding:14px 32px;\">\n 🚀 一键获取微信二维码\n </button>\n <div id=\"gatewayBotStatus\" style=\"margin-top:12px;font-size:13px;color:#999;\"></div>\n </div>\n\n <!-- 二维码展示 -->\n <div id=\"wechatQR\" style=\"display:none;\">\n <div class=\"qrcode-wrapper\">\n <img id=\"wechatQRImg\" src=\"\" alt=\"微信登录二维码\"\n style=\"max-width:250px;border-radius:12px;\">\n </div>\n <p class=\"wechat-desc\" id=\"wechatStatus\">请用微信扫描二维码</p>\n <div id=\"wechatSpinner\" style=\"margin-top:12px;\">\n <span style=\"color:#999;\">等待扫码中</span>\n <span id=\"wechatDots\">...</span>\n </div>\n </div>\n\n <!-- 连接成功 -->\n <div id=\"wechatSuccess\" style=\"display:none;\">\n <div style=\"font-size:48px;margin:20px 0;\">✅</div>\n <h3>微信连接成功!</h3>\n <p style=\"color:#666;\">现在可以通过微信和小暖聊天了</p>\n </div>\n\n <!-- 失败 -->\n <div id=\"wechatFail\" style=\"display:none;\">\n <p style=\"color:#E74C3C;\" id=\"wechatFailMsg\"></p>\n <button class=\"btn-primary\" id=\"wechatRetry\">🔄 重试</button>\n </div>\n\n <!-- 静态配置(兜底) -->\n <details style=\"margin-top:24px;\">\n <summary style=\"cursor:pointer;color:#999;font-size:12px;\">管理员:手动配置静态二维码</summary>\n <div style=\"margin-top:12px;display:flex;flex-direction:column;gap:8px;\">\n <input type=\"text\" id=\"qrcodeUrlInput\" placeholder=\"粘贴二维码图片链接\" class=\"chat-input\">\n <input type=\"text\" id=\"qrcodeDescInput\" placeholder=\"描述文字(可选)\" class=\"chat-input\">\n <button class=\"btn-primary\" id=\"saveWechatConfig\">保存</button>\n </div>\n </details>\n </div>\n </div>\n </section>\n\n <!-- 设置页面 -->\n <section class=\"page\" id=\"page-settings\">\n <div class=\"page-header\">\n <button class=\"menu-toggle\" id=\"menuToggle6\">☰</button>\n <h2>⚙️ 我的设置</h2>\n </div>\n <div class=\"settings-content\">\n <div class=\"settings-grid\">\n <!-- 左列:个人档案 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>👤 个人档案</h3>\n <div class=\"form-group\">\n <label>显示名称</label>\n <input type=\"text\" id=\"prefDisplayName\" class=\"chat-input\" placeholder=\"别人怎么称呼你\">\n </div>\n <div class=\"form-group\">\n <label>我的目标</label>\n <textarea id=\"prefGoals\" class=\"chat-input\" rows=\"2\" placeholder=\"比如:改善睡眠、减少焦虑...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>情绪触发点</label>\n <textarea id=\"prefTriggers\" class=\"chat-input\" rows=\"2\" placeholder=\"什么情况容易让情绪波动...\"></textarea>\n </div>\n <div class=\"form-group\">\n <label>惯用的应对方式</label>\n <textarea id=\"prefCoping\" class=\"chat-input\" rows=\"2\" placeholder=\"你发现哪些方法对自己有效...\"></textarea>\n </div>\n </div>\n\n <div class=\"card\">\n <h3>⏰ 每日提醒</h3>\n <div class=\"form-group\">\n <label style=\"display:flex;align-items:center;gap:8px;cursor:pointer;\">\n <input type=\"checkbox\" id=\"prefReminder\" style=\"width:18px;height:18px;accent-color:var(--orange);\">\n <span>开启每日情绪提醒</span>\n </label>\n </div>\n <div class=\"form-group\">\n <label>提醒时间</label>\n <input type=\"time\" id=\"prefReminderTime\" class=\"chat-input\" style=\"width:160px;\">\n </div>\n </div>\n </div>\n\n <!-- 右列:API + 保存 -->\n <div class=\"settings-col\">\n <div class=\"card\">\n <h3>🤖 大模型 API</h3>\n <p class=\"text-muted\" style=\"margin-bottom:14px;font-size:13px;\">配置你自己的大模型,不填则使用系统默认的 DeepSeek</p>\n <div class=\"form-group\">\n <label>API Key</label>\n <input type=\"password\" id=\"prefApiKey\" class=\"chat-input\" placeholder=\"sk-...\">\n </div>\n <div class=\"form-group\">\n <label>API 地址</label>\n <input type=\"text\" id=\"prefApiBase\" class=\"chat-input\" placeholder=\"https://api.deepseek.com/v1\">\n </div>\n <div class=\"form-group\">\n <label>模型名称</label>\n <input type=\"text\" id=\"prefApiModel\" class=\"chat-input\" placeholder=\"deepseek-chat\">\n </div>\n </div>\n\n <div class=\"card\" style=\"text-align:center;\">\n <p style=\"color:var(--text-muted);font-size:13px;margin-bottom:8px;\">小暖会根据你的档案,更懂你</p>\n <button class=\"btn-primary\" id=\"savePreferences\" style=\"width:100%;\">💾 保存设置</button>\n <span class=\"text-muted\" id=\"prefSaveMsg\" style=\"display:block;margin-top:8px;font-size:13px;\"></span>\n </div>\n </div>\n </div>\n </div>\n </section>\n </main>\n </div>\n\n <script>\n // 检测 OpenClaw Gateway 状态\n (async function() {\n try {\n const res = await fetch('/api/health');\n const data = await res.json();\n const el = document.getElementById('gatewayStatus');\n if (data.enhanced_mode) {\n el.textContent = '🟢';\n el.title = '增强模式:OpenClaw Gateway 已连接';\n } else {\n el.textContent = '🟡';\n el.title = '基础模式:仅网站功能可用';\n }\n } catch(e) {}\n })();\n </script>\n <script src=\"/static/js/auth.js?v=2\"></script>\n <script src=\"/static/js/chat.js?v=2\"></script>\n <script src=\"/static/js/dashboard.js?v=2\"></script>\n <script src=\"/static/js/diary.js?v=2\"></script>\n <script src=\"/static/js/phq9.js?v=2\"></script>\n <script>\n // ── 导航切换 ──\n document.querySelectorAll('.nav-item').forEach(item => {\n item.addEventListener('click', () => {\n document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n item.classList.add('active');\n const page = item.dataset.page;\n document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n document.getElementById('page-' + page).classList.add('active');\n if (page === 'dashboard') loadDashboard();\n if (page === 'diary') loadDiaryList();\n if (page === 'phq9') loadPHQ9History();\n // 移动端:收起侧边栏\n document.getElementById('sidebar').classList.remove('open');\n });\n });\n\n // 移动端菜单切换\n document.querySelectorAll('.menu-toggle').forEach(btn => {\n btn.addEventListener('click', () => {\n document.getElementById('sidebar').classList.toggle('open');\n });\n });\n\n // 设置页面\n (function() {\n const nav = document.querySelector('[data-page=\"settings\"]');\n if (nav) {\n nav.addEventListener('click', async () => {\n try {\n const res = await authFetch('/api/user/preferences');\n const data = await res.json();\n document.getElementById('prefDisplayName').value = data.display_name || '';\n document.getElementById('prefGoals').value = data.goals || '';\n document.getElementById('prefTriggers').value = data.triggers || '';\n document.getElementById('prefCoping').value = data.coping_strategies || '';\n document.getElementById('prefApiKey').value = data.api_key || '';\n document.getElementById('prefApiBase').value = data.api_base_url || 'https://api.deepseek.com/v1';\n document.getElementById('prefApiModel').value = data.api_model || 'deepseek-chat';\n document.getElementById('prefReminder').checked = data.daily_reminder;\n document.getElementById('prefReminderTime').value = data.reminder_time || '21:00';\n } catch (err) {\n console.error('Load preferences error:', err);\n }\n });\n }\n\n document.getElementById('savePreferences').addEventListener('click', async () => {\n const btn = document.getElementById('savePreferences');\n const msg = document.getElementById('prefSaveMsg');\n btn.disabled = true;\n btn.textContent = '保存中...';\n msg.textContent = '';\n\n try {\n await authFetch('/api/user/preferences', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n display_name: document.getElementById('prefDisplayName').value.trim(),\n goals: document.getElementById('prefGoals').value.trim(),\n triggers: document.getElementById('prefTriggers').value.trim(),\n coping_strategies: document.getElementById('prefCoping').value.trim(),\n api_key: document.getElementById('prefApiKey').value.trim(),\n api_base_url: document.getElementById('prefApiBase').value.trim(),\n api_model: document.getElementById('prefApiModel').value.trim(),\n daily_reminder: document.getElementById('prefReminder').checked,\n reminder_time: document.getElementById('prefReminderTime').value,\n }),\n });\n msg.textContent = '✅ 已保存';\n setTimeout(() => { msg.textContent = ''; }, 2000);\n } catch (err) {\n msg.textContent = '❌ 保存失败';\n }\n btn.disabled = false;\n btn.textContent = '💾 保存设置';\n });\n })();\n\n // 微信一键登录(使用 Gateway 已有 bot 认证)\n (function() {\n let pollTimer = null;\n\n function show(elId) {\n ['wechatInit','wechatQR','wechatSuccess','wechatFail'].forEach(id => {\n document.getElementById(id).style.display = id === elId ? 'block' : 'none';\n });\n }\n\n function reset() { show('wechatInit'); if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }\n\n // 页面加载时检测 Gateway bot 状态\n (async function checkBot() {\n try {\n const res = await authFetch('/api/wechat/info');\n const data = await res.json();\n const el = document.getElementById('gatewayBotStatus');\n if (data.gateway_bot && data.gateway_bot.connected) {\n el.innerHTML = '🟢 Gateway bot 已连接 (' + data.gateway_bot.account_id + ')';\n } else {\n el.innerHTML = '🔴 未检测到 Gateway bot,请先启动 OpenClaw Gateway';\n document.getElementById('startWechatLogin').disabled = true;\n document.getElementById('startWechatLogin').textContent = 'Gateway 未连接';\n }\n } catch(e) {}\n })();\n\n document.getElementById('startWechatLogin').addEventListener('click', async () => {\n const btn = document.getElementById('startWechatLogin');\n btn.disabled = true; btn.textContent = '正在生成二维码...';\n\n try {\n const res = await authFetch('/api/wechat/start-login', { method: 'POST' });\n const text = await res.text();\n let data;\n try { data = JSON.parse(text); } catch(e) { throw new Error('服务器返回异常: ' + text.slice(0,100)); }\n if (!res.ok) throw new Error(data.detail || '请求失败');\n if (data.status === 'error') throw new Error(data.message || '获取二维码失败');\n const sid = data.session_id;\n if (!sid) throw new Error('未获取到会话ID');\n\n show('wechatQR');\n document.getElementById('wechatStatus').textContent = data.message || '请用微信扫描二维码';\n const img = document.getElementById('wechatQRImg');\n img.style.display = 'none';\n img.onload = () => { img.style.display = ''; };\n img.onerror = () => { document.getElementById('wechatStatus').textContent = '二维码加载失败,请重试'; };\n img.src = '/api/wechat/login-qrcode/' + sid + '?' + Date.now();\n\n let dots = 0;\n pollTimer = setInterval(async () => {\n try {\n const sr = await authFetch('/api/wechat/login-status/' + sid);\n const sd = await sr.json();\n document.getElementById('wechatStatus').textContent = sd.message || '请用微信扫描二维码';\n document.getElementById('wechatDots').textContent = '.'.repeat((++dots) % 4);\n\n if (sd.status === 'scanned') {\n document.getElementById('wechatStatus').textContent = '已扫码,请在微信中确认授权...';\n }\n if (sd.status === 'connected') {\n clearInterval(pollTimer);\n pollTimer = null;\n show('wechatSuccess');\n }\n if (sd.status === 'failed' || sd.status === 'error') {\n clearInterval(pollTimer); pollTimer = null;\n document.getElementById('wechatFailMsg').textContent = sd.message || '连接失败';\n show('wechatFail');\n }\n } catch(e) {}\n }, 2000);\n } catch(err) {\n document.getElementById('wechatFailMsg').textContent = '启动失败: ' + err.message;\n show('wechatFail');\n }\n btn.disabled = false; btn.textContent = '🚀 一键获取微信二维码';\n });\n\n document.getElementById('wechatRetry').addEventListener('click', reset);\n\n // 管理员:保存静态二维码配置\n document.getElementById('saveWechatConfig').addEventListener('click', async () => {\n const url = document.getElementById('qrcodeUrlInput').value.trim();\n if (!url) return;\n const desc = document.getElementById('qrcodeDescInput').value.trim() || '扫描二维码,添加小暖微信,随时随地聊聊天';\n await authFetch('/api/config/wechat', {\n method: 'PUT',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({qrcode_url: url, description: desc}),\n });\n alert('已保存!');\n });\n })();\n </script>\n</body>\n</html>\n"}}}} |