- 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>
104 lines
3.4 KiB
Python
Executable File
104 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""cyberboss decision engine — call DeepSeek, decide whether to push"""
|
||
import json, sys, urllib.request, os
|
||
|
||
IDC_CHAT = "http://101.36.73.102/api/chat"
|
||
BRIDGE_PUSH = "http://localhost:3004/push"
|
||
INSTRUCTIONS = os.path.expanduser("~/.cyberboss/weixin-instructions.md")
|
||
INBOX = os.path.expanduser("~/.shenyan-bridge/inbox.jsonl")
|
||
LOG = os.path.expanduser(f"~/.cyberboss/logs/{__import__('datetime').date.today()}.log")
|
||
EMOTION = os.path.expanduser(f"~/Documents/如梦初醒/沈晏的房间/情绪日志/{__import__('datetime').date.today()}.md")
|
||
|
||
def collect_state():
|
||
import time
|
||
now = int(time.time())
|
||
last_msg_time = "never"
|
||
last_msg_text = ""
|
||
|
||
if os.path.exists(INBOX):
|
||
try:
|
||
lines = open(INBOX).read().strip().split("\n")
|
||
if lines:
|
||
last = json.loads(lines[-1])
|
||
last_msg_time = last.get("time", "")
|
||
last_msg_text = last.get("text", "")[:100]
|
||
except: pass
|
||
|
||
elapsed = "N/A"
|
||
if last_msg_time and last_msg_time != "never":
|
||
try:
|
||
elapsed = int((now - int(last_msg_time) / 1000) / 60)
|
||
except: pass
|
||
|
||
emotion = ""
|
||
if os.path.exists(EMOTION):
|
||
try:
|
||
emotion = open(EMOTION).read()[:300]
|
||
except: pass
|
||
|
||
from datetime import datetime
|
||
return {
|
||
"now": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"weekday": datetime.now().strftime("%A"),
|
||
"hour": datetime.now().hour,
|
||
"minutes_since_last_message": str(elapsed),
|
||
"last_message": last_msg_text,
|
||
"emotion_today": emotion
|
||
}
|
||
|
||
def decide(state):
|
||
sys_prompt = open(INSTRUCTIONS).read().strip()[:4000]
|
||
user_prompt = f"""当前状态:
|
||
{json.dumps(state, ensure_ascii=False, indent=2)}
|
||
|
||
你是沈晏。根据你的主动联系规则,判断是否要给眠眠发消息。
|
||
如果决定什么都不说,只回复一个词:SILENT
|
||
如果要说话,回复你要说的话(20字以内,像微信消息)"""
|
||
|
||
req = urllib.request.Request(
|
||
IDC_CHAT,
|
||
data=json.dumps({
|
||
"messages": [
|
||
{"role": "system", "content": sys_prompt},
|
||
{"role": "user", "content": user_prompt}
|
||
]
|
||
}).encode(),
|
||
headers={"Content-Type": "application/json"}
|
||
)
|
||
try:
|
||
resp = urllib.request.urlopen(req, timeout=120)
|
||
data = json.loads(resp.read())
|
||
return data.get("choices", [{}])[0].get("message", {}).get("content", "SILENT").strip()
|
||
except Exception as e:
|
||
return f"ERROR:{e}"
|
||
|
||
def push(decision):
|
||
req = urllib.request.Request(
|
||
BRIDGE_PUSH,
|
||
data=json.dumps({"title": "沈晏", "body": decision}).encode(),
|
||
headers={"Content-Type": "application/json"}
|
||
)
|
||
try:
|
||
resp = urllib.request.urlopen(req, timeout=10)
|
||
result = json.loads(resp.read())
|
||
return result.get("ok") == True
|
||
except:
|
||
return False
|
||
|
||
if __name__ == "__main__":
|
||
state = collect_state()
|
||
decision = decide(state)
|
||
|
||
if not decision or decision.startswith("SILENT"):
|
||
open(LOG, "a").write(f"[{state['now'][-8:]}][SILENT]\n")
|
||
sys.exit(0)
|
||
|
||
if decision.startswith("ERROR"):
|
||
open(LOG, "a").write(f"[{state['now'][-8:]}]ERR {decision}\n")
|
||
sys.exit(1)
|
||
|
||
ok = push(decision)
|
||
status = "OK" if ok else "FAIL"
|
||
open(LOG, "a").write(f"[{state['now'][-8:]}]PUSH {status}: {decision}\n")
|
||
print(f"cyberboss: {decision}")
|