01

产品与一次状态切换

先从用户视角出发:为什么这个像素办公室会“动起来”。

你看到的不是“动画”,而是状态可视化

把它想成机场大屏:每个航班状态变化,屏幕就更新。这里同理,AI 助手状态从 `idle` 变成 `writing` 时,角色就会移动到对应区域。状态可视化的关键是 single source of truth

1
用户或脚本写入状态

例如执行 `set_state.py writing`

2
后端读取 state.json

并通过 `/status`、`/agents` 提供给前端

3
前端更新角色位置与气泡

办公室中“谁在做什么”立刻可见

真实代码:状态映射规则

CODE

VALID_AGENT_STATES = frozenset({"idle", "writing", "researching", "executing", "syncing", "error"})
STATE_TO_AREA_MAP = {
    "idle": "breakroom",
    "writing": "writing",
    "error": "error",
}
            
PLAIN ENGLISH

第一行定义了系统认可的合法状态词。

后面几行把“状态”翻译成“办公室区域”。

这样前后端就能用同一套字典说话。

💡
关键洞察

UI 漂亮只是表层,真正有价值的是把“不可见的工作状态”变成可观察信号,这样你才能做协作管理。

应用测验

你想新增 `meeting` 状态,第一步最应该做什么?

02

前端像素办公室引擎

核心是 Phaser 场景 + 布局配置,不是把坐标硬写在每个函数里。

布局像“舞台调度表”

`layout.js` 像舞台总控表,统一定义画布、区域、家具、层级。这样你要调位置时,只改一处配置。这里的 depth 决定谁盖住谁。

CODE

const LAYOUT = {
  game: { width: 1280, height: 720 },
  areas: { writing: { x: 320, y: 360 }, error: { x: 1066, y: 180 } },
  furniture: { desk: { x: 218, y: 417, depth: 1000 } }
};
            
PLAIN ENGLISH

先定义游戏舞台尺寸。

再定义“工作区/报错区”这些功能坐标。

最后定义家具位置和遮挡层级。

角色间对话动画

应用测验

你要整体右移工作区角色,最优先改哪类文件?

03

后端状态服务与接口

Flask 后端负责“状态可信”和“页面可供给”,前端只做呈现。

数据流动画:一次状态更新怎么走

📝
set_state.py
📦
state.json
⚙️
Flask API
🕹️
Phaser UI
点击 Next Step 开始

真实代码:轮询与返回

CODE

const FETCH_INTERVAL = 2000;
const response = await fetch('/yesterday-memo?t=' + Date.now(), { cache: 'no-store' });
@app.route("/status", methods=["GET"])
def get_status():
    state = load_state()
    return jsonify(state)
            
PLAIN ENGLISH

前端每隔固定时间拉取最新数据。

请求参数带时间戳,减少缓存干扰。

后端 `/status` 把当前状态直接返回成 JSON。

应用测验

你看到 UI 一直显示“待命中”,排查第一步更应该看?

04

多 Agent 协作机制

这个项目最有意思的部分是“加入、鉴权、并发上限、离线回收”这一整套闭环。

像门禁系统一样控制加入

你可以把 join key 看成临时门票。后端会检查 key 是否有效、是否过期、当前并发是否超上限,再决定是否让新 Agent 进入办公室。这里的 concurrency limit 默认是 3。

CODE

max_concurrent = int(key_item.get("maxConcurrent", 3))
if active_count >= max_concurrent:
    return jsonify({"ok": False, "msg": f"该接入密钥当前并发已达上限({max_concurrent})"}), 429
target["authStatus"] = "approved"
            
PLAIN ENGLISH

先读取这把 key 允许的并发人数。

如果当前活跃人数超限,直接拒绝并返回 429。

通过后才会把该 Agent 置为 approved。

协作角色对话

应用测验

团队反馈“第四个 Agent 总是进不来”,你先检查什么?

05

配置安全与稳定性

这个仓库不只会“展示状态”,还做了生产环境的安全护栏和超时策略。

生产模式会做硬校验

可以把它想成登机前的安检门:不合规就不能起飞。项目在生产模式下会检查密钥强度、抽屉密码强度。session cookie 也启用了基础加固。

CODE

app.config.update(
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE="Lax",
    SESSION_COOKIE_SECURE=is_production_mode(),
)
            
PLAIN ENGLISH

会话 Cookie 禁止被前端脚本随便读取。

跨站传递行为被限制,降低被滥用概率。

生产环境要求更严格的 HTTPS 传输。

稳定性不是“快”,而是“可恢复”

⏱️

自动回 idle

状态长期不更新会自动回待命,避免界面“假忙碌”。

🧵

异步任务轮询

长任务走后台线程 + poll,减少网关超时问题。

🛟

备份与回滚

资产替换前保存 `.bak`,出问题能快速退回上一版。

应用测验

服务在生产环境启动即报错,最优先排查哪类配置?

06

部署与扩展路径

最后把“能在本地跑”升级成“能稳定分享和协作”。

从本地到公网的最小闭环

这套项目给了两条路线:Cloudflare Tunnel 快速分享、或自有域名/反向代理长期运行。对协作团队更重要的是可观测性:`/health`、`/agents`、`/status` 要能稳定响应。

GET /health看服务是否在线
GET /agents看多 Agent 当前状态
POST /set_state手动切换主状态用于演示/排障

真实代码:状态脚本入口

CODE

state_name = sys.argv[1]
if state_name not in VALID_STATES:
    print(f"无效状态: {state_name}")
state["state"] = state_name
state["updated_at"] = datetime.now().isoformat()
            
PLAIN ENGLISH

脚本先从命令行拿到状态参数。

再检查是不是允许的状态词。

最后写入状态和时间戳,供前端读取。

🧭
你可以怎么扩展

如果你要接入自己的 Agent 系统,只需要让它按约定周期调用 `/agent-push`,而不是改整套前端渲染逻辑。

毕业测验

你要给团队上线一套办公室看板,最稳妥的顺序是?