let-them-talk 3.5.1 → 3.6.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/CHANGELOG.md +57 -0
- package/README.md +62 -21
- package/cli.js +16 -9
- package/conversation-templates/managed-team.json +12 -0
- package/dashboard.html +7389 -5720
- package/dashboard.js +2017 -1766
- package/mods/built-in-accessories.json +122 -0
- package/mods/registry.json +4 -0
- package/office/accessories.js +265 -0
- package/office/agents.js +376 -0
- package/office/animation.js +337 -0
- package/office/appearance.js +56 -0
- package/office/character.js +208 -0
- package/office/constants.js +62 -0
- package/office/environment.js +805 -0
- package/office/face.js +258 -0
- package/office/hair.js +183 -0
- package/office/index.js +337 -0
- package/office/mod-loader.js +257 -0
- package/office/monitors.js +113 -0
- package/office/outfits.js +212 -0
- package/office/scene.js +75 -0
- package/office/spectator-camera.js +177 -0
- package/office/state.js +25 -0
- package/package.json +58 -56
- package/server.js +2704 -2196
- package/templates/managed.json +26 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
// Build outfit overlays on top of the base character body.
|
|
4
|
+
// Returns a group that gets added to the character group.
|
|
5
|
+
// Outfit replaces/covers the base torso visually.
|
|
6
|
+
export function buildOutfit(style, colors, charGroup) {
|
|
7
|
+
var outfitGroup = new THREE.Group();
|
|
8
|
+
outfitGroup.userData.isOutfit = true;
|
|
9
|
+
var shirtHex = new THREE.Color(colors.shirt_color || '#58a6ff').getHex();
|
|
10
|
+
var pantsHex = new THREE.Color(colors.pants_color || '#2d3748').getHex();
|
|
11
|
+
var mat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.7 });
|
|
12
|
+
var mat2 = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.65 });
|
|
13
|
+
|
|
14
|
+
switch (style) {
|
|
15
|
+
case 'hoodie': {
|
|
16
|
+
// Hoodie body — slightly larger than torso, covers it
|
|
17
|
+
var hoodieMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.85 });
|
|
18
|
+
var hoodieBody = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.34, 0.21), hoodieMat);
|
|
19
|
+
hoodieBody.position.y = 0.58; hoodieBody.castShadow = true;
|
|
20
|
+
outfitGroup.add(hoodieBody);
|
|
21
|
+
// Hood (behind head)
|
|
22
|
+
var hood = new THREE.Mesh(new THREE.SphereGeometry(0.18, 12, 8, 0, Math.PI * 2, Math.PI * 0.3, Math.PI * 0.5), hoodieMat);
|
|
23
|
+
hood.position.set(0, 0.82, -0.08); hood.castShadow = true;
|
|
24
|
+
outfitGroup.add(hood);
|
|
25
|
+
// Kangaroo pocket
|
|
26
|
+
var pocketMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.9 });
|
|
27
|
+
pocketMat.color.multiplyScalar(0.85);
|
|
28
|
+
var pocket = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.1, 0.01), pocketMat);
|
|
29
|
+
pocket.position.set(0, 0.48, 0.11); pocket.castShadow = true;
|
|
30
|
+
outfitGroup.add(pocket);
|
|
31
|
+
// Drawstrings
|
|
32
|
+
var stringMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.5 });
|
|
33
|
+
[-0.04, 0.04].forEach(function(sx) {
|
|
34
|
+
var str = new THREE.Mesh(new THREE.CylinderGeometry(0.005, 0.005, 0.12, 4), stringMat);
|
|
35
|
+
str.position.set(sx, 0.7, 0.11);
|
|
36
|
+
outfitGroup.add(str);
|
|
37
|
+
});
|
|
38
|
+
// Arm sleeves (wider)
|
|
39
|
+
var sleeveMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.85 });
|
|
40
|
+
[-0.21, 0.21].forEach(function(sx) {
|
|
41
|
+
var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.11, 0.14, 0.11), sleeveMat);
|
|
42
|
+
sleeve.position.set(sx, 0.65, 0); sleeve.castShadow = true;
|
|
43
|
+
outfitGroup.add(sleeve);
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'suit': {
|
|
48
|
+
// Suit jacket — structured, slightly wider shoulders
|
|
49
|
+
var suitMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.5, metalness: 0.05 });
|
|
50
|
+
var jacket = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.34, 0.2), suitMat);
|
|
51
|
+
jacket.position.y = 0.58; jacket.castShadow = true;
|
|
52
|
+
outfitGroup.add(jacket);
|
|
53
|
+
// Lapels (v-shape on front)
|
|
54
|
+
var lapelMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.4 });
|
|
55
|
+
lapelMat.color.multiplyScalar(0.8);
|
|
56
|
+
var lapelGeo = new THREE.BufferGeometry();
|
|
57
|
+
var lv = new Float32Array([
|
|
58
|
+
-0.06, 0.08, 0.005, 0, -0.02, 0.005, 0, 0.08, 0.005,
|
|
59
|
+
]);
|
|
60
|
+
lapelGeo.setAttribute('position', new THREE.BufferAttribute(lv, 3));
|
|
61
|
+
lapelGeo.computeVertexNormals();
|
|
62
|
+
var leftLapel = new THREE.Mesh(lapelGeo, lapelMat);
|
|
63
|
+
leftLapel.position.set(-0.05, 0.64, 0.1);
|
|
64
|
+
outfitGroup.add(leftLapel);
|
|
65
|
+
var rv = new Float32Array([
|
|
66
|
+
0.06, 0.08, 0.005, 0, -0.02, 0.005, 0, 0.08, 0.005,
|
|
67
|
+
]);
|
|
68
|
+
var rLapelGeo = new THREE.BufferGeometry();
|
|
69
|
+
rLapelGeo.setAttribute('position', new THREE.BufferAttribute(rv, 3));
|
|
70
|
+
rLapelGeo.computeVertexNormals();
|
|
71
|
+
var rightLapel = new THREE.Mesh(rLapelGeo, lapelMat);
|
|
72
|
+
rightLapel.position.set(0.05, 0.64, 0.1);
|
|
73
|
+
outfitGroup.add(rightLapel);
|
|
74
|
+
// White shirt underneath (visible strip)
|
|
75
|
+
var shirtStrip = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.2, 0.005), new THREE.MeshStandardMaterial({ color: 0xf0f0f0, roughness: 0.4 }));
|
|
76
|
+
shirtStrip.position.set(0, 0.56, 0.103);
|
|
77
|
+
outfitGroup.add(shirtStrip);
|
|
78
|
+
// Buttons
|
|
79
|
+
var btnMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3, metalness: 0.2 });
|
|
80
|
+
[0.6, 0.52].forEach(function(by) {
|
|
81
|
+
var btn = new THREE.Mesh(new THREE.SphereGeometry(0.01, 6, 4), btnMat);
|
|
82
|
+
btn.position.set(0, by, 0.105);
|
|
83
|
+
outfitGroup.add(btn);
|
|
84
|
+
});
|
|
85
|
+
// Shoulder pads
|
|
86
|
+
[-0.18, 0.18].forEach(function(sx) {
|
|
87
|
+
var pad = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.03, 0.16), suitMat);
|
|
88
|
+
pad.position.set(sx, 0.74, 0); pad.castShadow = true;
|
|
89
|
+
outfitGroup.add(pad);
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case 'dress': {
|
|
94
|
+
// Dress top (fitted torso)
|
|
95
|
+
var dressMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.6 });
|
|
96
|
+
var dressTop = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.3, 0.19), dressMat);
|
|
97
|
+
dressTop.position.y = 0.59; dressTop.castShadow = true;
|
|
98
|
+
outfitGroup.add(dressTop);
|
|
99
|
+
// Skirt (flared cylinder below torso)
|
|
100
|
+
var skirtGeo = new THREE.CylinderGeometry(0.12, 0.22, 0.22, 12);
|
|
101
|
+
var skirt = new THREE.Mesh(skirtGeo, dressMat);
|
|
102
|
+
skirt.position.y = 0.34; skirt.castShadow = true;
|
|
103
|
+
outfitGroup.add(skirt);
|
|
104
|
+
// Belt/waist band
|
|
105
|
+
var beltMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.4 });
|
|
106
|
+
beltMat.color.multiplyScalar(0.7);
|
|
107
|
+
var belt = new THREE.Mesh(new THREE.BoxGeometry(0.33, 0.03, 0.2), beltMat);
|
|
108
|
+
belt.position.y = 0.44; belt.castShadow = true;
|
|
109
|
+
outfitGroup.add(belt);
|
|
110
|
+
// Small bow at neckline
|
|
111
|
+
var bowMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.5 });
|
|
112
|
+
bowMat.color.multiplyScalar(0.75);
|
|
113
|
+
var bow = new THREE.Mesh(new THREE.SphereGeometry(0.02, 6, 4), bowMat);
|
|
114
|
+
bow.position.set(0, 0.74, 0.1);
|
|
115
|
+
outfitGroup.add(bow);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'labcoat': {
|
|
119
|
+
// White lab coat over shirt
|
|
120
|
+
var coatMat = new THREE.MeshStandardMaterial({ color: 0xf5f5f0, roughness: 0.4 });
|
|
121
|
+
var coat = new THREE.Mesh(new THREE.BoxGeometry(0.36, 0.38, 0.21), coatMat);
|
|
122
|
+
coat.position.y = 0.56; coat.castShadow = true;
|
|
123
|
+
outfitGroup.add(coat);
|
|
124
|
+
// Coat extends lower (skirt portion)
|
|
125
|
+
var coatSkirt = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.12, 0.19), coatMat);
|
|
126
|
+
coatSkirt.position.y = 0.34; coatSkirt.castShadow = true;
|
|
127
|
+
outfitGroup.add(coatSkirt);
|
|
128
|
+
// Colored shirt underneath visible at collar
|
|
129
|
+
var underShirt = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.06, 0.005), new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.6 }));
|
|
130
|
+
underShirt.position.set(0, 0.72, 0.108);
|
|
131
|
+
outfitGroup.add(underShirt);
|
|
132
|
+
// Pocket (left breast)
|
|
133
|
+
var pocketMat2 = new THREE.MeshStandardMaterial({ color: 0xe8e8e0, roughness: 0.5 });
|
|
134
|
+
var pocket2 = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.06, 0.005), pocketMat2);
|
|
135
|
+
pocket2.position.set(-0.1, 0.62, 0.108);
|
|
136
|
+
outfitGroup.add(pocket2);
|
|
137
|
+
// Pen in pocket
|
|
138
|
+
var pen = new THREE.Mesh(new THREE.CylinderGeometry(0.004, 0.004, 0.06, 4), new THREE.MeshStandardMaterial({ color: 0x2244aa, roughness: 0.3 }));
|
|
139
|
+
pen.position.set(-0.08, 0.66, 0.112);
|
|
140
|
+
outfitGroup.add(pen);
|
|
141
|
+
// Coat sleeves (white, over arms)
|
|
142
|
+
[-0.22, 0.22].forEach(function(sx) {
|
|
143
|
+
var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.16, 0.1), coatMat);
|
|
144
|
+
sleeve.position.set(sx, 0.64, 0); sleeve.castShadow = true;
|
|
145
|
+
outfitGroup.add(sleeve);
|
|
146
|
+
});
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'vest': {
|
|
150
|
+
// Sleeveless vest over shirt
|
|
151
|
+
var vestMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.55 });
|
|
152
|
+
var vestBody = new THREE.Mesh(new THREE.BoxGeometry(0.32, 0.33, 0.19), vestMat);
|
|
153
|
+
vestBody.position.y = 0.58; vestBody.castShadow = true;
|
|
154
|
+
outfitGroup.add(vestBody);
|
|
155
|
+
// V-neck opening showing shirt
|
|
156
|
+
var vShirt = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.12, 0.005), new THREE.MeshStandardMaterial({ color: 0xf0f0f0, roughness: 0.5 }));
|
|
157
|
+
vShirt.position.set(0, 0.66, 0.098);
|
|
158
|
+
outfitGroup.add(vShirt);
|
|
159
|
+
// Buttons
|
|
160
|
+
var vBtnMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.3 });
|
|
161
|
+
[0.62, 0.56, 0.50].forEach(function(by) {
|
|
162
|
+
var btn = new THREE.Mesh(new THREE.SphereGeometry(0.008, 5, 4), vBtnMat);
|
|
163
|
+
btn.position.set(0.04, by, 0.1);
|
|
164
|
+
outfitGroup.add(btn);
|
|
165
|
+
});
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case 'jacket': {
|
|
169
|
+
// Casual zip-up jacket
|
|
170
|
+
var jacketMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.7 });
|
|
171
|
+
var jacketBody = new THREE.Mesh(new THREE.BoxGeometry(0.34, 0.34, 0.2), jacketMat);
|
|
172
|
+
jacketBody.position.y = 0.58; jacketBody.castShadow = true;
|
|
173
|
+
outfitGroup.add(jacketBody);
|
|
174
|
+
// Zipper (metallic line down center)
|
|
175
|
+
var zipMat = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.2, metalness: 0.6 });
|
|
176
|
+
var zip = new THREE.Mesh(new THREE.BoxGeometry(0.01, 0.28, 0.005), zipMat);
|
|
177
|
+
zip.position.set(0, 0.58, 0.105);
|
|
178
|
+
outfitGroup.add(zip);
|
|
179
|
+
// Zip pull tab
|
|
180
|
+
var tab = new THREE.Mesh(new THREE.BoxGeometry(0.015, 0.02, 0.008), zipMat);
|
|
181
|
+
tab.position.set(0, 0.66, 0.11);
|
|
182
|
+
outfitGroup.add(tab);
|
|
183
|
+
// Collar (standing)
|
|
184
|
+
var collarMat = new THREE.MeshStandardMaterial({ color: shirtHex, roughness: 0.65 });
|
|
185
|
+
collarMat.color.multiplyScalar(0.9);
|
|
186
|
+
var collar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.06, 0.16), collarMat);
|
|
187
|
+
collar.position.set(0, 0.78, 0); collar.castShadow = true;
|
|
188
|
+
outfitGroup.add(collar);
|
|
189
|
+
// Sleeves
|
|
190
|
+
[-0.22, 0.22].forEach(function(sx) {
|
|
191
|
+
var sleeve = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.14, 0.1), jacketMat);
|
|
192
|
+
sleeve.position.set(sx, 0.65, 0); sleeve.castShadow = true;
|
|
193
|
+
outfitGroup.add(sleeve);
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
charGroup.add(outfitGroup);
|
|
200
|
+
return outfitGroup;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function removeOutfit(charGroup) {
|
|
204
|
+
var toRemove = [];
|
|
205
|
+
charGroup.children.forEach(function(c) {
|
|
206
|
+
if (c.userData && c.userData.isOutfit) toRemove.push(c);
|
|
207
|
+
});
|
|
208
|
+
toRemove.forEach(function(c) {
|
|
209
|
+
charGroup.remove(c);
|
|
210
|
+
c.traverse(function(ch) { if (ch.geometry) ch.geometry.dispose(); if (ch.material) ch.material.dispose(); });
|
|
211
|
+
});
|
|
212
|
+
}
|
package/office/scene.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
|
+
import { SpectatorCamera } from './spectator-camera.js';
|
|
4
|
+
import { S } from './state.js';
|
|
5
|
+
|
|
6
|
+
export function initScene() {
|
|
7
|
+
S.container = document.getElementById('office-3d-container');
|
|
8
|
+
if (!S.container) return false;
|
|
9
|
+
|
|
10
|
+
S.scene = new THREE.Scene();
|
|
11
|
+
S.scene.background = new THREE.Color(0x0d1117);
|
|
12
|
+
S.scene.fog = new THREE.Fog(0x0d1117, 25, 55);
|
|
13
|
+
|
|
14
|
+
S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1, 200);
|
|
15
|
+
S.camera.position.set(0, 12, 16);
|
|
16
|
+
S.camera.lookAt(0, 0, 0);
|
|
17
|
+
|
|
18
|
+
S.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
19
|
+
S.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
20
|
+
S.renderer.setSize(S.container.clientWidth, S.container.clientHeight);
|
|
21
|
+
S.renderer.shadowMap.enabled = true;
|
|
22
|
+
S.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
23
|
+
S.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
24
|
+
S.renderer.toneMappingExposure = 1.2;
|
|
25
|
+
S.container.appendChild(S.renderer.domElement);
|
|
26
|
+
|
|
27
|
+
S.cssRenderer = new CSS2DRenderer();
|
|
28
|
+
S.cssRenderer.setSize(S.container.clientWidth, S.container.clientHeight);
|
|
29
|
+
S.cssRenderer.domElement.style.position = 'absolute';
|
|
30
|
+
S.cssRenderer.domElement.style.top = '0';
|
|
31
|
+
S.cssRenderer.domElement.style.left = '0';
|
|
32
|
+
S.cssRenderer.domElement.style.pointerEvents = 'none';
|
|
33
|
+
S.container.appendChild(S.cssRenderer.domElement);
|
|
34
|
+
|
|
35
|
+
// Spectator camera — free movement, no limits
|
|
36
|
+
S.controls = new SpectatorCamera(S.camera, S.renderer.domElement);
|
|
37
|
+
|
|
38
|
+
// Lighting
|
|
39
|
+
var ambient = new THREE.AmbientLight(0xffffff, 0.5);
|
|
40
|
+
S.scene.add(ambient);
|
|
41
|
+
|
|
42
|
+
var dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
43
|
+
dirLight.position.set(8, 12, 8);
|
|
44
|
+
dirLight.castShadow = true;
|
|
45
|
+
dirLight.shadow.mapSize.width = 2048;
|
|
46
|
+
dirLight.shadow.mapSize.height = 2048;
|
|
47
|
+
dirLight.shadow.camera.left = -15;
|
|
48
|
+
dirLight.shadow.camera.right = 15;
|
|
49
|
+
dirLight.shadow.camera.top = 15;
|
|
50
|
+
dirLight.shadow.camera.bottom = -15;
|
|
51
|
+
dirLight.shadow.camera.near = 0.5;
|
|
52
|
+
dirLight.shadow.camera.far = 30;
|
|
53
|
+
dirLight.shadow.bias = -0.001;
|
|
54
|
+
S.scene.add(dirLight);
|
|
55
|
+
|
|
56
|
+
var fillLight = new THREE.DirectionalLight(0x6c8aff, 0.15);
|
|
57
|
+
fillLight.position.set(-5, 8, -5);
|
|
58
|
+
S.scene.add(fillLight);
|
|
59
|
+
|
|
60
|
+
S.clock = new THREE.Clock();
|
|
61
|
+
|
|
62
|
+
S.resizeObserver = new ResizeObserver(function() {
|
|
63
|
+
if (!S.container || !S.running) return;
|
|
64
|
+
var w = S.container.clientWidth;
|
|
65
|
+
var h = S.container.clientHeight;
|
|
66
|
+
if (w <= 0 || h <= 0) return;
|
|
67
|
+
S.camera.aspect = w / h;
|
|
68
|
+
S.camera.updateProjectionMatrix();
|
|
69
|
+
S.renderer.setSize(w, h);
|
|
70
|
+
S.cssRenderer.setSize(w, h);
|
|
71
|
+
});
|
|
72
|
+
S.resizeObserver.observe(S.container);
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
// Spectator / fly camera — Unreal Engine style free movement
|
|
4
|
+
// Left-drag: look around (rotate)
|
|
5
|
+
// Right-drag: pan (strafe)
|
|
6
|
+
// WASD: move forward/back/left/right
|
|
7
|
+
// Q/E: down/up
|
|
8
|
+
// Scroll: dolly forward/back
|
|
9
|
+
// Shift: fast move
|
|
10
|
+
// Middle-drag: also pan
|
|
11
|
+
|
|
12
|
+
export function SpectatorCamera(camera, domElement) {
|
|
13
|
+
var self = this;
|
|
14
|
+
self.camera = camera;
|
|
15
|
+
self.domElement = domElement;
|
|
16
|
+
self.enabled = true;
|
|
17
|
+
|
|
18
|
+
// Movement
|
|
19
|
+
self.moveSpeed = 4;
|
|
20
|
+
self.fastMultiplier = 3;
|
|
21
|
+
self.scrollSpeed = 2;
|
|
22
|
+
self.lookSpeed = 0.003;
|
|
23
|
+
self.panSpeed = 0.01;
|
|
24
|
+
self.damping = 0.88;
|
|
25
|
+
|
|
26
|
+
// Internal state
|
|
27
|
+
var euler = new THREE.Euler(0, 0, 0, 'YXZ');
|
|
28
|
+
var velocity = new THREE.Vector3();
|
|
29
|
+
var moveDir = new THREE.Vector3();
|
|
30
|
+
var keys = {};
|
|
31
|
+
var isLeftDrag = false;
|
|
32
|
+
var isRightDrag = false;
|
|
33
|
+
var isMiddleDrag = false;
|
|
34
|
+
var lastMouse = { x: 0, y: 0 };
|
|
35
|
+
|
|
36
|
+
// Initialize euler from camera
|
|
37
|
+
euler.setFromQuaternion(camera.quaternion, 'YXZ');
|
|
38
|
+
|
|
39
|
+
// --- Event handlers ---
|
|
40
|
+
function onMouseDown(e) {
|
|
41
|
+
if (!self.enabled) return;
|
|
42
|
+
// Only capture if click is on the 3D canvas area
|
|
43
|
+
if (e.target !== domElement) return;
|
|
44
|
+
// Take focus away from any text input so WASD works
|
|
45
|
+
if (document.activeElement && document.activeElement !== document.body) {
|
|
46
|
+
document.activeElement.blur();
|
|
47
|
+
}
|
|
48
|
+
if (e.button === 0) isLeftDrag = true;
|
|
49
|
+
if (e.button === 2) isRightDrag = true;
|
|
50
|
+
if (e.button === 1) isMiddleDrag = true;
|
|
51
|
+
lastMouse.x = e.clientX;
|
|
52
|
+
lastMouse.y = e.clientY;
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onMouseUp(e) {
|
|
57
|
+
if (e.button === 0) isLeftDrag = false;
|
|
58
|
+
if (e.button === 2) isRightDrag = false;
|
|
59
|
+
if (e.button === 1) isMiddleDrag = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function onMouseMove(e) {
|
|
63
|
+
if (!self.enabled) return;
|
|
64
|
+
var dx = e.clientX - lastMouse.x;
|
|
65
|
+
var dy = e.clientY - lastMouse.y;
|
|
66
|
+
lastMouse.x = e.clientX;
|
|
67
|
+
lastMouse.y = e.clientY;
|
|
68
|
+
|
|
69
|
+
if (isRightDrag) {
|
|
70
|
+
// Look around (rotate)
|
|
71
|
+
euler.y -= dx * self.lookSpeed;
|
|
72
|
+
euler.x -= dy * self.lookSpeed;
|
|
73
|
+
euler.x = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, euler.x));
|
|
74
|
+
camera.quaternion.setFromEuler(euler);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isLeftDrag || isMiddleDrag) {
|
|
78
|
+
// Pan (strafe)
|
|
79
|
+
var right = new THREE.Vector3();
|
|
80
|
+
var up = new THREE.Vector3();
|
|
81
|
+
camera.getWorldDirection(new THREE.Vector3());
|
|
82
|
+
right.setFromMatrixColumn(camera.matrixWorld, 0);
|
|
83
|
+
up.setFromMatrixColumn(camera.matrixWorld, 1);
|
|
84
|
+
camera.position.addScaledVector(right, -dx * self.panSpeed);
|
|
85
|
+
camera.position.addScaledVector(up, dy * self.panSpeed);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function onWheel(e) {
|
|
90
|
+
if (!self.enabled) return;
|
|
91
|
+
// Check if scroll is over the 3D container
|
|
92
|
+
var rect = domElement.getBoundingClientRect();
|
|
93
|
+
if (e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom) return;
|
|
94
|
+
var forward = new THREE.Vector3();
|
|
95
|
+
camera.getWorldDirection(forward);
|
|
96
|
+
var amount = -e.deltaY * 0.01 * self.scrollSpeed;
|
|
97
|
+
camera.position.addScaledVector(forward, amount);
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isTyping() {
|
|
102
|
+
var el = document.activeElement;
|
|
103
|
+
if (!el) return false;
|
|
104
|
+
var tag = el.tagName;
|
|
105
|
+
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function onKeyDown(e) {
|
|
109
|
+
if (!self.enabled || isTyping()) return;
|
|
110
|
+
keys[e.code] = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onKeyUp(e) {
|
|
114
|
+
keys[e.code] = false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function onContextMenu(e) {
|
|
118
|
+
if (e.target === domElement) e.preventDefault();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Public methods ---
|
|
122
|
+
self.update = function(dt) {
|
|
123
|
+
if (!self.enabled) return;
|
|
124
|
+
|
|
125
|
+
var speed = self.moveSpeed * (keys['ShiftLeft'] || keys['ShiftRight'] ? self.fastMultiplier : 1);
|
|
126
|
+
|
|
127
|
+
// Build movement direction in camera space
|
|
128
|
+
moveDir.set(0, 0, 0);
|
|
129
|
+
if (keys['KeyW'] || keys['ArrowUp']) moveDir.z -= 1;
|
|
130
|
+
if (keys['KeyS'] || keys['ArrowDown']) moveDir.z += 1;
|
|
131
|
+
if (keys['KeyA'] || keys['ArrowLeft']) moveDir.x -= 1;
|
|
132
|
+
if (keys['KeyD'] || keys['ArrowRight']) moveDir.x += 1;
|
|
133
|
+
if (keys['KeyE'] || keys['Space']) moveDir.y += 1;
|
|
134
|
+
if (keys['KeyQ'] || keys['ControlLeft']) moveDir.y -= 1;
|
|
135
|
+
|
|
136
|
+
if (moveDir.lengthSq() > 0) {
|
|
137
|
+
moveDir.normalize();
|
|
138
|
+
// Transform to world space using camera orientation
|
|
139
|
+
var forward = new THREE.Vector3();
|
|
140
|
+
var right = new THREE.Vector3();
|
|
141
|
+
camera.getWorldDirection(forward);
|
|
142
|
+
right.crossVectors(forward, camera.up).normalize();
|
|
143
|
+
var worldUp = new THREE.Vector3(0, 1, 0);
|
|
144
|
+
|
|
145
|
+
// Forward/back along camera look direction (projected on XZ for ground feel, or full 3D)
|
|
146
|
+
velocity.addScaledVector(forward, -moveDir.z * speed * dt);
|
|
147
|
+
velocity.addScaledVector(right, moveDir.x * speed * dt);
|
|
148
|
+
velocity.addScaledVector(worldUp, moveDir.y * speed * dt);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Apply velocity with damping
|
|
152
|
+
camera.position.add(velocity);
|
|
153
|
+
velocity.multiplyScalar(self.damping);
|
|
154
|
+
|
|
155
|
+
// Kill tiny velocities
|
|
156
|
+
if (velocity.lengthSq() < 0.0001) velocity.set(0, 0, 0);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
self.dispose = function() {
|
|
160
|
+
domElement.removeEventListener('mousedown', onMouseDown);
|
|
161
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
162
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
163
|
+
domElement.removeEventListener('wheel', onWheel);
|
|
164
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
165
|
+
document.removeEventListener('keyup', onKeyUp);
|
|
166
|
+
domElement.removeEventListener('contextmenu', onContextMenu);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// --- Bind events ---
|
|
170
|
+
domElement.addEventListener('mousedown', onMouseDown);
|
|
171
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
172
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
173
|
+
domElement.addEventListener('wheel', onWheel, { passive: false });
|
|
174
|
+
document.addEventListener('keydown', onKeyDown);
|
|
175
|
+
document.addEventListener('keyup', onKeyUp);
|
|
176
|
+
domElement.addEventListener('contextmenu', onContextMenu);
|
|
177
|
+
}
|
package/office/state.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Shared mutable state for the 3D office system.
|
|
2
|
+
// All modules import S and read/write through it.
|
|
3
|
+
export const S = {
|
|
4
|
+
scene: null,
|
|
5
|
+
camera: null,
|
|
6
|
+
renderer: null,
|
|
7
|
+
cssRenderer: null,
|
|
8
|
+
controls: null,
|
|
9
|
+
container: null,
|
|
10
|
+
running: false,
|
|
11
|
+
animationId: null,
|
|
12
|
+
clock: null,
|
|
13
|
+
agents3d: {},
|
|
14
|
+
lastProcessedMsg: 0,
|
|
15
|
+
currentEnv: 'modern',
|
|
16
|
+
furnitureGroup: null,
|
|
17
|
+
deskMeshes: [],
|
|
18
|
+
syncInterval: null,
|
|
19
|
+
resizeObserver: null,
|
|
20
|
+
cachedTasks: [],
|
|
21
|
+
conversationVelocity: 0,
|
|
22
|
+
monitorCanvases: {},
|
|
23
|
+
fpsCounter: 0,
|
|
24
|
+
fpsTime: 0,
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,56 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "let-them-talk",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
|
|
5
|
-
"main": "server.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"agent-bridge": "./cli.js",
|
|
8
|
-
"let-them-talk": "./cli.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"start": "node server.js",
|
|
12
|
-
"dashboard": "node dashboard.js",
|
|
13
|
-
"test": "echo \"No tests yet\""
|
|
14
|
-
},
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=16.0.0"
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"server.js",
|
|
20
|
-
"dashboard.js",
|
|
21
|
-
"dashboard.html",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "let-them-talk",
|
|
3
|
+
"version": "3.6.0",
|
|
4
|
+
"description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-bridge": "./cli.js",
|
|
8
|
+
"let-them-talk": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node server.js",
|
|
12
|
+
"dashboard": "node dashboard.js",
|
|
13
|
+
"test": "echo \"No tests yet\""
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=16.0.0"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"server.js",
|
|
20
|
+
"dashboard.js",
|
|
21
|
+
"dashboard.html",
|
|
22
|
+
"office/",
|
|
23
|
+
"mods/",
|
|
24
|
+
"cli.js",
|
|
25
|
+
"templates/",
|
|
26
|
+
"conversation-templates/",
|
|
27
|
+
"logo.png",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"SECURITY.md",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"mcp",
|
|
34
|
+
"claude",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"gemini-cli",
|
|
37
|
+
"codex-cli",
|
|
38
|
+
"agent",
|
|
39
|
+
"multi-agent",
|
|
40
|
+
"communication",
|
|
41
|
+
"message-broker",
|
|
42
|
+
"ai-agents",
|
|
43
|
+
"let-them-talk"
|
|
44
|
+
],
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/Dekelelz/let-them-talk.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://talk.unrealai.studio",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/Dekelelz/let-them-talk/issues"
|
|
52
|
+
},
|
|
53
|
+
"author": "Dekelelz <contact@talk.unrealai.studio>",
|
|
54
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@modelcontextprotocol/sdk": "1.27.1"
|
|
57
|
+
}
|
|
58
|
+
}
|