@iniesta8888/agent-live-pi-adapter 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/adapter.js +2375 -0
- package/package.json +23 -0
- package/web/app.d.ts +1 -0
- package/web/app.js +1317 -0
- package/web/index.html +45 -0
- package/web/office.js +350 -0
- package/web/sprites.js +185 -0
- package/web/style.css +288 -0
- package/web/v2/bootstrap.js +276 -0
- package/web/v2/content/README.md +17 -0
- package/web/v2/content/agent-skins/studio-team.json +33 -0
- package/web/v2/content/agent-skins/tiny-developers.json +33 -0
- package/web/v2/content/atmospheres/default.json +13 -0
- package/web/v2/content/atmospheres/rainy-night.json +37 -0
- package/web/v2/content/catalog.json +14 -0
- package/web/v2/content/component-library/activity-implementations.json +873 -0
- package/web/v2/content/component-library/activity-recipes.json +25 -0
- package/web/v2/content/component-library/agent-profile-templates.json +18 -0
- package/web/v2/content/component-library/catalog.json +38 -0
- package/web/v2/content/component-library/npc-templates.json +42 -0
- package/web/v2/content/component-library/props.json +41 -0
- package/web/v2/content/environments/local-office.json +31 -0
- package/web/v2/content/environments/rainy-night.json +28 -0
- package/web/v2/content/environments/static-office.json +27 -0
- package/web/v2/content/layouts/boardroom-office.json +122 -0
- package/web/v2/content/layouts/demo-office.json +88 -0
- package/web/v2/content/layouts/lively-office.json +96 -0
- package/web/v2/content/layouts/old-school-office.json +154 -0
- package/web/v2/content/layouts/tech-open-office.json +132 -0
- package/web/v2/content/life-activities/boardroom-routines.json +67 -0
- package/web/v2/content/life-activities/none.json +10 -0
- package/web/v2/content/life-activities/office-basics.json +39 -0
- package/web/v2/content/life-activities/old-school-routines.json +74 -0
- package/web/v2/content/life-activities/tech-office-routines.json +148 -0
- package/web/v2/content/npcs/boardroom-staff.json +54 -0
- package/web/v2/content/npcs/none.json +10 -0
- package/web/v2/content/npcs/office-staff.json +25 -0
- package/web/v2/content/npcs/old-school-staff.json +39 -0
- package/web/v2/content/npcs/tech-office-staff.json +40 -0
- package/web/v2/content/official-offices/boardroom-office.json +25 -0
- package/web/v2/content/official-offices/old-school-office.json +25 -0
- package/web/v2/content/official-offices/tech-open-office.json +25 -0
- package/web/v2/content/presets/boardroom-office.json +21 -0
- package/web/v2/content/presets/cozy-studio.json +19 -0
- package/web/v2/content/presets/demo-office.json +19 -0
- package/web/v2/content/presets/lively-office.json +19 -0
- package/web/v2/content/presets/night-shift.json +19 -0
- package/web/v2/content/presets/old-school-office.json +20 -0
- package/web/v2/content/presets/refined-demo.json +20 -0
- package/web/v2/content/presets/tech-open-office.json +21 -0
- package/web/v2/content/props/default-office.json +23 -0
- package/web/v2/content/props/extended-office.json +39 -0
- package/web/v2/content/props/tech-office.json +28 -0
- package/web/v2/content/styles/pixel-classic.json +160 -0
- package/web/v2/content/styles/warm-studio.json +150 -0
- package/web/v2/environment-runtime.d.ts +1 -0
- package/web/v2/environment-runtime.js +138 -0
- package/web/v2/graph-validator.d.ts +55 -0
- package/web/v2/graph-validator.js +225 -0
- package/web/v2/i18n.js +54 -0
- package/web/v2/locales/en.json +209 -0
- package/web/v2/locales/zh-CN.json +52 -0
- package/web/v2/office-engine.d.ts +1 -0
- package/web/v2/office-engine.js +145 -0
- package/web/v2/office-renderer.d.ts +1 -0
- package/web/v2/office-renderer.js +843 -0
- package/web/v2/parity.html +10 -0
- package/web/v2/parity.js +82 -0
- package/web/v2/sprite-renderer.d.ts +1 -0
- package/web/v2/sprite-renderer.js +242 -0
- package/web/v2/static-content.js +48 -0
- package/web/v2/style.css +140 -0
- package/web/v2.html +68 -0
package/web/index.html
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Agent Live</title>
|
|
7
|
+
<link rel="stylesheet" href="/style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app">
|
|
11
|
+
<header id="bar">
|
|
12
|
+
<span class="brand">AGENT LIVE</span>
|
|
13
|
+
<span id="conn" class="pill offline">连接中…</span>
|
|
14
|
+
<span id="model" class="pill">model: —</span>
|
|
15
|
+
<span id="turns" class="pill">turn 0</span>
|
|
16
|
+
<span id="usage" class="pill">0 tok · $0.0000</span>
|
|
17
|
+
<span class="grow"></span>
|
|
18
|
+
<button id="mute" class="btn" title="音效开关">音效: 关</button>
|
|
19
|
+
<button id="demo" class="btn">演示</button>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<main>
|
|
23
|
+
<div id="stageWrap">
|
|
24
|
+
<canvas id="stage"></canvas>
|
|
25
|
+
<div id="taskbar"><span class="k">当前需求</span><span id="task">等待指令…</span></div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<aside id="side">
|
|
29
|
+
<section class="panel">
|
|
30
|
+
<h2>员工</h2>
|
|
31
|
+
<div id="crew" class="crew"></div>
|
|
32
|
+
</section>
|
|
33
|
+
<section class="panel grow">
|
|
34
|
+
<h2>动态</h2>
|
|
35
|
+
<div id="log" class="log"></div>
|
|
36
|
+
</section>
|
|
37
|
+
</aside>
|
|
38
|
+
</main>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<script src="/sprites.js"></script>
|
|
42
|
+
<script src="/office.js"></script>
|
|
43
|
+
<script src="/app.js"></script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
package/web/office.js
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Office map: layout, furniture rendering, and navigation data.
|
|
3
|
+
*
|
|
4
|
+
* Logical resolution is 384x216, drawn in integer pixels. The floor is laid out
|
|
5
|
+
* around three horizontal lanes and four vertical connectors so characters can
|
|
6
|
+
* always walk between any two anchors without crossing furniture.
|
|
7
|
+
*/
|
|
8
|
+
(() => {
|
|
9
|
+
const W = 384;
|
|
10
|
+
const H = 216;
|
|
11
|
+
const WALL_H = 36;
|
|
12
|
+
|
|
13
|
+
/** Horizontal walkways: top aisle, main aisle, lower aisle. */
|
|
14
|
+
const LANES = [46, 100, 162];
|
|
15
|
+
/** Vertical walkways, positioned in the gaps between desks. */
|
|
16
|
+
const VCONN = [76, 140, 204, 276];
|
|
17
|
+
|
|
18
|
+
const C = {
|
|
19
|
+
floorA: "#2f3a45",
|
|
20
|
+
floorB: "#334050",
|
|
21
|
+
floorLine: "#28313b",
|
|
22
|
+
rug: "#3a3150",
|
|
23
|
+
rugEdge: "#4b3f63",
|
|
24
|
+
wall: "#222b39",
|
|
25
|
+
wallTop: "#1a222e",
|
|
26
|
+
wallTrim: "#39455c",
|
|
27
|
+
wood: "#7d5b3a",
|
|
28
|
+
woodDark: "#5d4229",
|
|
29
|
+
deskTop: "#8d6a44",
|
|
30
|
+
deskLite: "#a07d54",
|
|
31
|
+
screen: "#0f1620",
|
|
32
|
+
chair: "#43506b",
|
|
33
|
+
chairDark: "#333d52",
|
|
34
|
+
metal: "#57616f",
|
|
35
|
+
metalDark: "#3f4854",
|
|
36
|
+
rack: "#232b3a",
|
|
37
|
+
board: "#dde3ec",
|
|
38
|
+
boardFrame: "#8f9aab",
|
|
39
|
+
glass: "#5f87a8",
|
|
40
|
+
glassLite: "#7fa8c9",
|
|
41
|
+
plant: "#3f7d52",
|
|
42
|
+
plantDark: "#2f5e3e",
|
|
43
|
+
pot: "#8a5a3c",
|
|
44
|
+
paper: "#e8e2d0",
|
|
45
|
+
door: "#6b4a2f",
|
|
46
|
+
doorDark: "#4c3421",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const px = (c, x, y, w, h, col) => {
|
|
50
|
+
c.fillStyle = col;
|
|
51
|
+
c.fillRect(x | 0, y | 0, w | 0, h | 0);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Eight desks in two rows; characters sit on the near side facing the monitor. */
|
|
55
|
+
const SEATS = [];
|
|
56
|
+
[
|
|
57
|
+
[58, 84, 1],
|
|
58
|
+
[118, 144, 2],
|
|
59
|
+
].forEach(([deskY, sitY, lane], row) => {
|
|
60
|
+
[44, 108, 172, 236].forEach((cx, col) => {
|
|
61
|
+
SEATS.push({
|
|
62
|
+
index: row * 4 + col,
|
|
63
|
+
cx,
|
|
64
|
+
deskX: cx - 24,
|
|
65
|
+
deskY,
|
|
66
|
+
deskW: 48,
|
|
67
|
+
deskH: 16,
|
|
68
|
+
anchor: { x: cx, y: sitY, dir: "up", lane },
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const FURNITURE = {
|
|
74
|
+
archive: { x: 296, y: 56, w: 44, h: 32 },
|
|
75
|
+
server: { x: 296, y: 116, w: 48, h: 32 },
|
|
76
|
+
coffee: { x: 300, y: 176, w: 26, h: 22 },
|
|
77
|
+
phone: { x: 348, y: 40, w: 32, h: 14 },
|
|
78
|
+
whiteboard: { x: 116, y: 8, w: 72, h: 26 },
|
|
79
|
+
meeting: { x: 120, y: 176, w: 112, h: 26 },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const TARGETS = {
|
|
83
|
+
archive: { x: 318, y: 100, dir: "up", lane: 1 },
|
|
84
|
+
server: { x: 320, y: 158, dir: "up", lane: 2 },
|
|
85
|
+
whiteboard: { x: 152, y: 46, dir: "up", lane: 0 },
|
|
86
|
+
phone: { x: 364, y: 64, dir: "up", lane: 1 },
|
|
87
|
+
coffee: { x: 290, y: 188, dir: "right", lane: 2 },
|
|
88
|
+
meetA: { x: 108, y: 190, dir: "right", lane: 2 },
|
|
89
|
+
meetB: { x: 244, y: 190, dir: "left", lane: 2 },
|
|
90
|
+
entry: { x: 38, y: 46, dir: "down", lane: 0 },
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const DOOR = { x: 20, y: 6, w: 36, h: 30 };
|
|
94
|
+
const WINDOWS = [
|
|
95
|
+
{ x: 208, y: 8, w: 44, h: 22 },
|
|
96
|
+
{ x: 268, y: 8, w: 44, h: 22 },
|
|
97
|
+
];
|
|
98
|
+
const PLANTS = [
|
|
99
|
+
{ x: 16, y: 200 },
|
|
100
|
+
{ x: 78, y: 200 },
|
|
101
|
+
{ x: 366, y: 200 },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
function drawFloor(c) {
|
|
105
|
+
px(c, 0, WALL_H, W, H - WALL_H, C.floorA);
|
|
106
|
+
for (let y = WALL_H; y < H; y += 16) {
|
|
107
|
+
for (let x = 0; x < W; x += 16) {
|
|
108
|
+
if (((x / 16) | 0) % 2 === ((y / 16) | 0) % 2) px(c, x, y, 16, 16, C.floorB);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (let y = WALL_H; y < H; y += 16) px(c, 0, y, W, 1, C.floorLine);
|
|
112
|
+
for (let x = 0; x < W; x += 16) px(c, x, WALL_H, 1, H - WALL_H, C.floorLine);
|
|
113
|
+
|
|
114
|
+
const r = { x: 104, y: 170, w: 146, h: 42 };
|
|
115
|
+
px(c, r.x, r.y, r.w, r.h, C.rug);
|
|
116
|
+
px(c, r.x, r.y, r.w, 1, C.rugEdge);
|
|
117
|
+
px(c, r.x, r.y + r.h - 1, r.w, 1, C.rugEdge);
|
|
118
|
+
px(c, r.x, r.y, 1, r.h, C.rugEdge);
|
|
119
|
+
px(c, r.x + r.w - 1, r.y, 1, r.h, C.rugEdge);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function drawWall(c, t) {
|
|
123
|
+
px(c, 0, 0, W, WALL_H, C.wall);
|
|
124
|
+
px(c, 0, 0, W, 6, C.wallTop);
|
|
125
|
+
px(c, 0, WALL_H - 3, W, 3, C.wallTrim);
|
|
126
|
+
|
|
127
|
+
px(c, DOOR.x, DOOR.y, DOOR.w, DOOR.h, C.door);
|
|
128
|
+
px(c, DOOR.x, DOOR.y, DOOR.w, 2, C.doorDark);
|
|
129
|
+
px(c, DOOR.x + DOOR.w - 3, DOOR.y, 3, DOOR.h, C.doorDark);
|
|
130
|
+
px(c, DOOR.x + 4, DOOR.y + 4, DOOR.w - 11, DOOR.h - 8, "#7d5738");
|
|
131
|
+
px(c, DOOR.x + DOOR.w - 9, DOOR.y + 16, 2, 3, C.paper);
|
|
132
|
+
|
|
133
|
+
for (const w of WINDOWS) {
|
|
134
|
+
px(c, w.x - 2, w.y - 2, w.w + 4, w.h + 4, C.wallTrim);
|
|
135
|
+
px(c, w.x, w.y, w.w, w.h, C.glass);
|
|
136
|
+
px(c, w.x, w.y, w.w, 8, C.glassLite);
|
|
137
|
+
px(c, w.x + (w.w >> 1), w.y, 1, w.h, C.wallTrim);
|
|
138
|
+
px(c, w.x, w.y + (w.h >> 1), w.w, 1, C.wallTrim);
|
|
139
|
+
if (Math.sin(t / 900 + w.x) > 0.7) px(c, w.x + 6, w.y + 14, 2, 2, "#ffe9a8");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function drawWhiteboard(c, hot, t) {
|
|
144
|
+
const b = FURNITURE.whiteboard;
|
|
145
|
+
px(c, b.x - 2, b.y - 2, b.w + 4, b.h + 4, C.boardFrame);
|
|
146
|
+
px(c, b.x, b.y, b.w, b.h, C.board);
|
|
147
|
+
px(c, b.x + 5, b.y + 4, 34, 1, "#5f6b7d");
|
|
148
|
+
px(c, b.x + 5, b.y + 8, 46, 1, "#5f6b7d");
|
|
149
|
+
px(c, b.x + 5, b.y + 12, 26, 1, "#7f8b9d");
|
|
150
|
+
if (hot.has("whiteboard")) {
|
|
151
|
+
const n = 4 + (((t / 140) | 0) % 24);
|
|
152
|
+
px(c, b.x + 5, b.y + 16, n, 1, "#e2564a");
|
|
153
|
+
px(c, b.x + 5, b.y + 20, Math.max(2, n - 9), 1, "#4a86e2");
|
|
154
|
+
} else {
|
|
155
|
+
px(c, b.x + 5, b.y + 16, 18, 1, "#a4aebd");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function drawDesk(c, seat, hot) {
|
|
160
|
+
const { deskX: x, deskY: y, deskW: w, deskH: h } = seat;
|
|
161
|
+
px(c, x, y + h, w, 3, C.woodDark);
|
|
162
|
+
px(c, x, y, w, h, C.deskTop);
|
|
163
|
+
px(c, x, y, w, 2, C.deskLite);
|
|
164
|
+
px(c, x + 2, y + h - 2, w - 4, 2, C.woodDark);
|
|
165
|
+
|
|
166
|
+
const mx = seat.cx - 9;
|
|
167
|
+
const my = y - 11;
|
|
168
|
+
px(c, mx - 1, my - 1, 20, 13, C.metalDark);
|
|
169
|
+
px(c, mx, my, 18, 11, C.screen);
|
|
170
|
+
if (hot.has(`desk${seat.index}`)) {
|
|
171
|
+
px(c, mx + 2, my + 2, 14, 1, "#4fd6a0");
|
|
172
|
+
px(c, mx + 2, my + 4, 10, 1, "#8fb8ff");
|
|
173
|
+
px(c, mx + 2, my + 6, 12, 1, "#8fb8ff");
|
|
174
|
+
px(c, mx + 2, my + 8, 6, 1, "#ffcc4d");
|
|
175
|
+
} else {
|
|
176
|
+
px(c, mx + 2, my + 3, 8, 1, "#2c3a4c");
|
|
177
|
+
px(c, mx + 2, my + 6, 11, 1, "#2c3a4c");
|
|
178
|
+
}
|
|
179
|
+
px(c, seat.cx - 2, my + 12, 4, 2, C.metalDark);
|
|
180
|
+
px(c, seat.cx + 12, y + 4, 7, 5, C.paper);
|
|
181
|
+
px(c, seat.cx + 12, y + 4, 7, 1, "#c9c2ad");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function drawChair(c, seat, occupied) {
|
|
185
|
+
const x = seat.cx - 7;
|
|
186
|
+
const y = seat.deskY + 21;
|
|
187
|
+
px(c, x, y, 14, 9, occupied ? C.chairDark : C.chair);
|
|
188
|
+
px(c, x, y + 9, 14, 2, C.chairDark);
|
|
189
|
+
px(c, x + 5, y + 11, 4, 3, C.metalDark);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function drawArchive(c, hot, t) {
|
|
193
|
+
const a = FURNITURE.archive;
|
|
194
|
+
px(c, a.x, a.y, a.w, a.h, C.metal);
|
|
195
|
+
px(c, a.x, a.y, a.w, 2, "#697485");
|
|
196
|
+
const open = hot.has("archive") ? ((t / 260) | 0) % 3 : -1;
|
|
197
|
+
for (let i = 0; i < 3; i++) {
|
|
198
|
+
const dy = a.y + 4 + i * 9;
|
|
199
|
+
px(c, a.x + 3, dy, a.w - 6, 7, C.metalDark);
|
|
200
|
+
px(c, a.x + 3, dy, a.w - 6, 1, "#727d8e");
|
|
201
|
+
px(c, a.x + (a.w >> 1) - 5, dy + 3, 10, 1, "#8b95a5");
|
|
202
|
+
if (i === open) {
|
|
203
|
+
px(c, a.x + 3, dy, a.w - 6, 7, "#2a323c");
|
|
204
|
+
px(c, a.x + 5, dy + 1, a.w - 10, 5, C.paper);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawServerRack(c, hot, t) {
|
|
210
|
+
const s = FURNITURE.server;
|
|
211
|
+
px(c, s.x, s.y, s.w, s.h, C.rack);
|
|
212
|
+
px(c, s.x, s.y, s.w, 2, "#3a4557");
|
|
213
|
+
const fast = hot.has("server");
|
|
214
|
+
for (let i = 0; i < 3; i++) {
|
|
215
|
+
const dy = s.y + 5 + i * 9;
|
|
216
|
+
px(c, s.x + 3, dy, s.w - 6, 7, "#1a212c");
|
|
217
|
+
px(c, s.x + 5, dy + 3, s.w - 24, 1, "#2f3a49");
|
|
218
|
+
const on = Math.sin((fast ? t / 90 : t / 520) + i * 1.7) > 0;
|
|
219
|
+
px(c, s.x + s.w - 12, dy + 2, 2, 2, on ? "#6ee7a8" : "#24513c");
|
|
220
|
+
px(c, s.x + s.w - 8, dy + 2, 2, 2, on ? "#24513c" : "#6ee7a8");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function drawCoffee(c, hot, t) {
|
|
225
|
+
const m = FURNITURE.coffee;
|
|
226
|
+
px(c, m.x, m.y, m.w, m.h, C.metalDark);
|
|
227
|
+
px(c, m.x, m.y, m.w, 2, C.metal);
|
|
228
|
+
px(c, m.x + 4, m.y + 5, m.w - 8, 8, "#1c2330");
|
|
229
|
+
px(c, m.x + 6, m.y + 7, 6, 4, hot.has("coffee") ? "#c98a4b" : "#2a3340");
|
|
230
|
+
px(c, m.x + 5, m.y + m.h - 5, m.w - 10, 3, C.metal);
|
|
231
|
+
if (hot.has("coffee") && ((t / 240) | 0) % 2) px(c, m.x + 9, m.y - 3, 2, 3, "#8fa3b8");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function drawPhoneTable(c, hot, t) {
|
|
235
|
+
const p = FURNITURE.phone;
|
|
236
|
+
px(c, p.x, p.y, p.w, p.h, C.wood);
|
|
237
|
+
px(c, p.x, p.y, p.w, 2, "#96704a");
|
|
238
|
+
px(c, p.x + 8, p.y - 7, 16, 8, "#1f2735");
|
|
239
|
+
px(c, p.x + 10, p.y - 5, 12, 3, hot.has("phone") ? "#6ee7a8" : "#39455c");
|
|
240
|
+
if (hot.has("phone") && ((t / 200) | 0) % 2) {
|
|
241
|
+
px(c, p.x + 4, p.y - 11, 2, 2, "#ffcc4d");
|
|
242
|
+
px(c, p.x + 26, p.y - 11, 2, 2, "#ffcc4d");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function drawMeetingTable(c) {
|
|
247
|
+
const m = FURNITURE.meeting;
|
|
248
|
+
px(c, m.x, m.y + m.h, m.w, 3, C.woodDark);
|
|
249
|
+
px(c, m.x, m.y, m.w, m.h, C.deskTop);
|
|
250
|
+
px(c, m.x, m.y, m.w, 2, C.deskLite);
|
|
251
|
+
px(c, m.x + 8, m.y + 8, 12, 8, C.paper);
|
|
252
|
+
px(c, m.x + m.w - 24, m.y + 6, 10, 10, "#cfd6e2");
|
|
253
|
+
px(c, m.x + (m.w >> 1) - 4, m.y + 9, 8, 6, "#4a5668");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function drawPlant(c, p) {
|
|
257
|
+
px(c, p.x - 4, p.y, 8, 7, C.pot);
|
|
258
|
+
px(c, p.x - 4, p.y, 8, 2, "#a06c48");
|
|
259
|
+
px(c, p.x - 1, p.y - 5, 2, 5, C.plantDark);
|
|
260
|
+
px(c, p.x - 6, p.y - 11, 12, 7, C.plant);
|
|
261
|
+
px(c, p.x - 4, p.y - 14, 8, 4, C.plant);
|
|
262
|
+
px(c, p.x - 6, p.y - 11, 5, 3, C.plantDark);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function drawRoom(c, t, hot, occupiedSeats) {
|
|
266
|
+
drawFloor(c);
|
|
267
|
+
drawWall(c, t);
|
|
268
|
+
drawWhiteboard(c, hot, t);
|
|
269
|
+
drawPhoneTable(c, hot, t);
|
|
270
|
+
drawArchive(c, hot, t);
|
|
271
|
+
drawServerRack(c, hot, t);
|
|
272
|
+
drawCoffee(c, hot, t);
|
|
273
|
+
drawMeetingTable(c);
|
|
274
|
+
for (const p of PLANTS) drawPlant(c, p);
|
|
275
|
+
for (const seat of SEATS) {
|
|
276
|
+
drawDesk(c, seat, hot);
|
|
277
|
+
drawChair(c, seat, occupiedSeats.has(seat.index));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function seatAnchor(i) {
|
|
282
|
+
return SEATS[(i ?? 0) % SEATS.length].anchor;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Where a character stands to perform a given office action. */
|
|
286
|
+
function anchorFor(action, seatIndex) {
|
|
287
|
+
switch (action) {
|
|
288
|
+
case "archive":
|
|
289
|
+
case "server":
|
|
290
|
+
case "whiteboard":
|
|
291
|
+
case "phone":
|
|
292
|
+
case "coffee":
|
|
293
|
+
return TARGETS[action];
|
|
294
|
+
default:
|
|
295
|
+
return seatAnchor(seatIndex);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Axis-aligned waypoints from a position on `fromLane` to a target anchor.
|
|
301
|
+
* Route: onto own lane, across a vertical connector, along the target lane,
|
|
302
|
+
* then into the anchor.
|
|
303
|
+
*/
|
|
304
|
+
function path(from, target) {
|
|
305
|
+
const fromLane = from.lane ?? 1;
|
|
306
|
+
const toLane = target.lane ?? 1;
|
|
307
|
+
const pts = [];
|
|
308
|
+
const laneY = LANES[fromLane];
|
|
309
|
+
|
|
310
|
+
if (Math.abs(from.y - laneY) > 0.5) pts.push({ x: from.x, y: laneY });
|
|
311
|
+
|
|
312
|
+
if (fromLane !== toLane) {
|
|
313
|
+
const vx = VCONN.reduce((best, x) =>
|
|
314
|
+
Math.abs(x - from.x) + Math.abs(x - target.x) <
|
|
315
|
+
Math.abs(best - from.x) + Math.abs(best - target.x)
|
|
316
|
+
? x
|
|
317
|
+
: best,
|
|
318
|
+
);
|
|
319
|
+
pts.push({ x: vx, y: laneY });
|
|
320
|
+
pts.push({ x: vx, y: LANES[toLane] });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
pts.push({ x: target.x, y: LANES[toLane] });
|
|
324
|
+
pts.push({ x: target.x, y: target.y });
|
|
325
|
+
return pts;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Station id used to highlight furniture while an action runs. */
|
|
329
|
+
function stationKey(action, seatIndex) {
|
|
330
|
+
if (!action || action === "type" || action === "delegate") return `desk${seatIndex ?? 0}`;
|
|
331
|
+
return action;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
window.Office = {
|
|
335
|
+
W,
|
|
336
|
+
H,
|
|
337
|
+
WALL_H,
|
|
338
|
+
LANES,
|
|
339
|
+
SEATS,
|
|
340
|
+
TARGETS,
|
|
341
|
+
FURNITURE,
|
|
342
|
+
colors: C,
|
|
343
|
+
px,
|
|
344
|
+
drawRoom,
|
|
345
|
+
anchorFor,
|
|
346
|
+
seatAnchor,
|
|
347
|
+
path,
|
|
348
|
+
stationKey,
|
|
349
|
+
};
|
|
350
|
+
})();
|
package/web/sprites.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedural pixel characters and effect particles.
|
|
3
|
+
* No image assets: every character is a handful of fillRect calls so the look
|
|
4
|
+
* stays consistent while the role/agent set is still changing.
|
|
5
|
+
*/
|
|
6
|
+
(() => {
|
|
7
|
+
const px = (c, x, y, w, h, col) => {
|
|
8
|
+
c.fillStyle = col;
|
|
9
|
+
c.fillRect(x | 0, y | 0, w | 0, h | 0);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Job titles and outfits. Covers pi's subagent names plus the ChatDev-style
|
|
14
|
+
* company roles, so a crew from either world shows up correctly dressed.
|
|
15
|
+
*/
|
|
16
|
+
const ROLES = {
|
|
17
|
+
scout: { title: "侦察员", shirt: "#4f9d69", trim: "#37714b" },
|
|
18
|
+
planner: { title: "规划师", shirt: "#4a78c8", trim: "#33569a" },
|
|
19
|
+
reviewer: { title: "评审员", shirt: "#b8574f", trim: "#8d3f38" },
|
|
20
|
+
worker: { title: "工程师", shirt: "#c8913f", trim: "#9a6c28" },
|
|
21
|
+
tester: { title: "测试员", shirt: "#7b57b8", trim: "#5b3d8d" },
|
|
22
|
+
writer: { title: "文案", shirt: "#c85f9b", trim: "#9a4577" },
|
|
23
|
+
ceo: { title: "CEO", shirt: "#d8d2c4", trim: "#a8a294" },
|
|
24
|
+
cto: { title: "CTO", shirt: "#5f7fa8", trim: "#455f80" },
|
|
25
|
+
cpo: { title: "CPO", shirt: "#a87fc8", trim: "#7f5c9a" },
|
|
26
|
+
programmer: { title: "程序员", shirt: "#c8913f", trim: "#9a6c28" },
|
|
27
|
+
designer: { title: "设计师", shirt: "#4fb8b0", trim: "#379088" },
|
|
28
|
+
counselor: { title: "顾问", shirt: "#8f8f9f", trim: "#6b6b7a" },
|
|
29
|
+
hr: { title: "HR", shirt: "#c8a04f", trim: "#9a7a33" },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const SKINS = ["#f0cba3", "#e8b98f", "#d69f70", "#b87a4f"];
|
|
33
|
+
const HAIRS = ["#2f2418", "#1f2430", "#4a2f22", "#3b3b45", "#5c3b1e"];
|
|
34
|
+
const FALLBACK_SHIRTS = ["#4a78c8", "#4f9d69", "#c8913f", "#b8574f", "#7b57b8", "#4fb8b0"];
|
|
35
|
+
|
|
36
|
+
function hash(text) {
|
|
37
|
+
let h = 2166136261;
|
|
38
|
+
for (let i = 0; i < text.length; i++) {
|
|
39
|
+
h ^= text.charCodeAt(i);
|
|
40
|
+
h = (h * 16777619) >>> 0;
|
|
41
|
+
}
|
|
42
|
+
return h;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function roleFor(name) {
|
|
46
|
+
return ROLES[String(name ?? "").toLowerCase()] ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Stable per-agent look, keyed by id so a character never changes clothes. */
|
|
50
|
+
function paletteFor(id, name, isLead) {
|
|
51
|
+
const h = hash(`${id}|${name}`);
|
|
52
|
+
const role = roleFor(name);
|
|
53
|
+
return {
|
|
54
|
+
skin: SKINS[h % SKINS.length],
|
|
55
|
+
hair: HAIRS[(h >>> 3) % HAIRS.length],
|
|
56
|
+
shirt: role ? role.shirt : FALLBACK_SHIRTS[(h >>> 6) % FALLBACK_SHIRTS.length],
|
|
57
|
+
trim: role ? role.trim : "#2b3242",
|
|
58
|
+
badge: isLead ? "#ffcc4d" : "#8fb8ff",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const PIPS = {
|
|
63
|
+
thinking: "#8fb8ff",
|
|
64
|
+
working: "#ffcc4d",
|
|
65
|
+
waiting: "#c79bff",
|
|
66
|
+
talking: "#6ee7a8",
|
|
67
|
+
done: "#6ee7a8",
|
|
68
|
+
error: "#ff8f6b",
|
|
69
|
+
idle: null,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param a {x,y} is the ground point (between the feet).
|
|
74
|
+
*/
|
|
75
|
+
function drawCharacter(c, a, t) {
|
|
76
|
+
const p = a.palette;
|
|
77
|
+
const walking = a.pose === "walk";
|
|
78
|
+
const sitting = a.pose === "sit" || a.pose === "type";
|
|
79
|
+
const step = walking ? (((a.walkPhase / 0.16) | 0) % 2) : 0;
|
|
80
|
+
const bob = walking ? (step ? 1 : 0) : Math.sin(t / 620 + a.seed) > 0.6 ? 1 : 0;
|
|
81
|
+
|
|
82
|
+
const y = (a.y | 0) - (sitting ? 3 : 0) + (sitting ? 0 : bob);
|
|
83
|
+
const x = a.x | 0;
|
|
84
|
+
const dir = a.dir ?? "down";
|
|
85
|
+
|
|
86
|
+
px(c, x - 5, y - 1, 10, 2, "rgba(0,0,0,0.28)");
|
|
87
|
+
|
|
88
|
+
if (!sitting) {
|
|
89
|
+
const l1 = step ? 1 : 0;
|
|
90
|
+
const l2 = step ? 0 : 1;
|
|
91
|
+
px(c, x - 3, y - 5 + l1, 2, 5 - l1, "#2b3242");
|
|
92
|
+
px(c, x + 1, y - 5 + l2, 2, 5 - l2, "#2b3242");
|
|
93
|
+
px(c, x - 3, y - 1, 2, 1, "#1b202b");
|
|
94
|
+
px(c, x + 1, y - 1, 2, 1, "#1b202b");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
px(c, x - 4, y - 12, 8, 8, p.shirt);
|
|
98
|
+
px(c, x - 4, y - 12, 8, 1, p.trim);
|
|
99
|
+
px(c, x - 4, y - 6, 8, 2, p.trim);
|
|
100
|
+
|
|
101
|
+
const armUp = a.pose === "type" ? (((t / 90) | 0) % 2) : a.pose === "reach" ? 1 : 0;
|
|
102
|
+
if (dir === "left") {
|
|
103
|
+
px(c, x - 6, y - 11 - armUp, 2, 5, p.shirt);
|
|
104
|
+
} else if (dir === "right") {
|
|
105
|
+
px(c, x + 4, y - 11 - armUp, 2, 5, p.shirt);
|
|
106
|
+
} else {
|
|
107
|
+
px(c, x - 6, y - 11 - armUp, 2, 5, p.shirt);
|
|
108
|
+
px(c, x + 4, y - 11 - (a.pose === "type" ? 1 - armUp : armUp), 2, 5, p.shirt);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
px(c, x - 3, y - 19, 6, 7, p.skin);
|
|
112
|
+
px(c, x - 4, y - 20, 8, 4, p.hair);
|
|
113
|
+
px(c, x - 4, y - 17, 1, 2, p.hair);
|
|
114
|
+
px(c, x + 3, y - 17, 1, 2, p.hair);
|
|
115
|
+
|
|
116
|
+
if (dir === "down") {
|
|
117
|
+
px(c, x - 2, y - 16, 1, 2, "#232a36");
|
|
118
|
+
px(c, x + 1, y - 16, 1, 2, "#232a36");
|
|
119
|
+
if (a.pose === "talk") px(c, x - 1, y - 13, 2, 1, "#8d4a44");
|
|
120
|
+
} else if (dir === "left") {
|
|
121
|
+
px(c, x - 2, y - 16, 1, 2, "#232a36");
|
|
122
|
+
px(c, x - 4, y - 20, 5, 5, p.hair);
|
|
123
|
+
px(c, x - 3, y - 19, 2, 1, p.skin);
|
|
124
|
+
} else if (dir === "right") {
|
|
125
|
+
px(c, x + 1, y - 16, 1, 2, "#232a36");
|
|
126
|
+
px(c, x - 1, y - 20, 5, 5, p.hair);
|
|
127
|
+
px(c, x + 1, y - 19, 2, 1, p.skin);
|
|
128
|
+
} else {
|
|
129
|
+
px(c, x - 4, y - 20, 8, 6, p.hair);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
px(c, x - 1, y - 11, 2, 2, p.badge);
|
|
133
|
+
|
|
134
|
+
const pip = PIPS[a.state ?? "idle"];
|
|
135
|
+
if (pip) {
|
|
136
|
+
const pulse = a.state === "thinking" ? (Math.sin(t / 240) > -0.2 ? 0 : 1) : 0;
|
|
137
|
+
if (!pulse) {
|
|
138
|
+
px(c, x - 1, y - 25, 3, 3, pip);
|
|
139
|
+
px(c, x, y - 26, 1, 1, pip);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function drawParticle(c, p) {
|
|
145
|
+
const a = Math.max(0, Math.min(1, p.life / p.maxLife));
|
|
146
|
+
c.globalAlpha = a;
|
|
147
|
+
const x = p.x | 0;
|
|
148
|
+
const y = p.y | 0;
|
|
149
|
+
switch (p.kind) {
|
|
150
|
+
case "key":
|
|
151
|
+
px(c, x, y, 3, 3, "#8fb8ff");
|
|
152
|
+
px(c, x + 1, y + 1, 1, 1, "#0f1620");
|
|
153
|
+
break;
|
|
154
|
+
case "paper":
|
|
155
|
+
px(c, x - 3, y - 2, 7, 5, "#e8e2d0");
|
|
156
|
+
px(c, x - 2, y - 1, 5, 1, "#b9b3a1");
|
|
157
|
+
px(c, x - 2, y + 1, 3, 1, "#b9b3a1");
|
|
158
|
+
break;
|
|
159
|
+
case "check":
|
|
160
|
+
px(c, x - 3, y, 2, 2, "#6ee7a8");
|
|
161
|
+
px(c, x - 1, y + 2, 2, 2, "#6ee7a8");
|
|
162
|
+
px(c, x + 1, y, 2, 2, "#6ee7a8");
|
|
163
|
+
px(c, x + 3, y - 2, 2, 2, "#6ee7a8");
|
|
164
|
+
break;
|
|
165
|
+
case "bang":
|
|
166
|
+
px(c, x, y - 5, 2, 4, "#ffcc4d");
|
|
167
|
+
px(c, x, y, 2, 2, "#ffcc4d");
|
|
168
|
+
break;
|
|
169
|
+
case "cross":
|
|
170
|
+
px(c, x - 2, y - 2, 2, 2, "#ff8f6b");
|
|
171
|
+
px(c, x + 1, y + 1, 2, 2, "#ff8f6b");
|
|
172
|
+
px(c, x + 1, y - 2, 2, 2, "#ff8f6b");
|
|
173
|
+
px(c, x - 2, y + 1, 2, 2, "#ff8f6b");
|
|
174
|
+
break;
|
|
175
|
+
case "spark":
|
|
176
|
+
px(c, x, y, 2, 2, p.color ?? "#6ee7a8");
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
px(c, x, y, 2, 2, "#d7dee9");
|
|
180
|
+
}
|
|
181
|
+
c.globalAlpha = 1;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
window.Sprites = { ROLES, roleFor, paletteFor, drawCharacter, drawParticle, hash };
|
|
185
|
+
})();
|