Files

1 line
254 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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"}}}}