更新 shenyan-app: HealthKit+WeatherKit+冰箱贴+pare+三区布局

- HealthKitBridge.m: 自定义原生模块,心率/步数/睡眠采集
- WeatherKitBridge.swift/m: 天气模块,当前+每日+每小时预报
- pare.py: 身体数据陡度监控,阈值告警→inbox+冰箱贴
- cyberboss: 5-20分钟随机唤醒,pare→decide→push
- 冰箱贴: 纸质感卡片,黑字多级透明度,独立输入框
- 首页三区无视觉布局: 左门/中记录/右冰箱
- 端口3003→3004(VS Code占用)
- 聊天屏锚定底部滚动

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 19:14:32 +08:00
parent d07d50dde5
commit 2a053ab88e
12 changed files with 791 additions and 184 deletions

View File

@@ -3,16 +3,17 @@ const fs = require('fs');
const path = require('path');
const { sendAPNs } = require('./apns');
const PORT = 3003;
const PORT = 3004;
const DATA_DIR = path.join(require('os').homedir(), '.shenyan-bridge');
const INBOX = path.join(DATA_DIR, 'inbox.jsonl');
const OUTBOX = path.join(DATA_DIR, 'outbox.jsonl');
const PUSHBOX = path.join(DATA_DIR, 'pushbox.jsonl');
const NOTES = path.join(DATA_DIR, 'notes.jsonl');
const DEVICE_TOKEN_FILE = path.join(DATA_DIR, 'device-token.txt');
// ensure data dir and files
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
[INBOX, OUTBOX, PUSHBOX].forEach(f => { if (!fs.existsSync(f)) fs.writeFileSync(f, ''); });
[INBOX, OUTBOX, PUSHBOX, NOTES].forEach(f => { if (!fs.existsSync(f)) fs.writeFileSync(f, ''); });
function json(res, data, code = 200) {
res.writeHead(code, { 'Content-Type': 'application/json; charset=utf-8' });
@@ -190,6 +191,48 @@ const server = http.createServer((req, res) => {
return;
}
// ---- fridge notes ----
if (req.method === 'GET' && req.url === '/notes') {
json(res, readJSONL(NOTES));
return;
}
if (req.method === 'POST' && req.url === '/notes/add') {
let body = '';
req.on('data', c => body += c);
req.on('end', () => {
try {
const { title, body: noteBody } = JSON.parse(body);
if (!title && !noteBody) return json(res, { error: 'empty' }, 400);
const entry = { id: getNextId(NOTES), title: title || '', body: noteBody || '', time: Date.now(), replies: [] };
appendJSONL(NOTES, entry);
json(res, { ok: true, id: entry.id });
} catch (e) { json(res, { error: e.message }, 400); }
});
return;
}
if (req.method === 'POST' && req.url === '/notes/reply') {
let body = '';
req.on('data', c => body += c);
req.on('end', () => {
try {
const { noteId, text } = JSON.parse(body);
if (!text) return json(res, { error: 'empty' }, 400);
const notes = readJSONL(NOTES);
const idx = notes.findIndex(n => n.id === noteId);
if (idx < 0) return json(res, { error: 'not found' }, 404);
notes[idx].replies = notes[idx].replies || [];
notes[idx].replies.push({ text, time: Date.now() });
writeJSONL(NOTES, notes);
// also notify inbox so 沈晏 sees the reply
appendJSONL(INBOX, { id: getNextId(INBOX), type: 'fridge_reply', text: `[冰箱贴回复] ${notes[idx].title}: ${text}`, time: Date.now(), status: 'pending' });
json(res, { ok: true });
} catch (e) { json(res, { error: e.message }, 400); }
});
return;
}
res.writeHead(404); res.end('not found');
});