codebee 0.1.105 → 0.1.106
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 +9 -0
- package/README.md +10 -7
- package/app/core/activity.py +36 -0
- package/app/core/builtin_agent.py +6 -1
- package/app/core/jobs.py +7 -0
- package/app/core/market_remote.py +19 -7
- package/app/core/mcp_client.py +102 -63
- package/app/core/pipeline.py +14 -6
- package/app/core/publish/auto.py +74 -3
- package/app/core/publish/ledger.py +5 -0
- package/app/core/publish/manager.py +5 -1
- package/app/core/runtime_lock.py +75 -0
- package/app/core/selfupdate.py +8 -0
- package/app/core/store.py +32 -2
- package/app/core/usage.py +31 -26
- package/app/core/webhooks.py +17 -13
- package/app/desktop_host.py +190 -0
- package/app/main.py +88 -4
- package/app/pet.py +1 -1
- package/app/ui/activity.css +34 -0
- package/app/ui/activity.js +199 -0
- package/app/ui/app.js +792 -64
- package/app/ui/hive3d.js +180 -61
- package/app/ui/i18n.js +130 -1
- package/app/ui/index.html +154 -75
- package/app/ui/style.css +583 -26
- package/package.json +1 -1
package/app/ui/hive3d.js
CHANGED
|
@@ -5,6 +5,11 @@ window.Hive3D = (function () {
|
|
|
5
5
|
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
6
6
|
const STATUS = { running:"运行中", done:"完成", queued:"排队中", failed:"失败", timeout:"超时", cancelled:"已取消" };
|
|
7
7
|
const STAGES = ["规划","起草","评审","执行","打磨","合成"];
|
|
8
|
+
const WING_OUTLINE=[
|
|
9
|
+
[-.50,0,.008],[-.43,.19,.035],[-.28,.30,.075],[-.06,.34,.10],
|
|
10
|
+
[.18,.30,.086],[.38,.19,.05],[.50,0,.008],[.38,-.19,.05],
|
|
11
|
+
[.18,-.30,.086],[-.06,-.34,.10],[-.28,-.30,.075],[-.43,-.19,.035]
|
|
12
|
+
];
|
|
8
13
|
const SCREENS = [
|
|
9
14
|
[163,441,127,55],[389,441,126,55],[616,441,125,55],[843,441,125,55],
|
|
10
15
|
[1073,441,122,55],[1298,441,125,55],[1531,441,120,55],[1750,441,125,55],
|
|
@@ -114,6 +119,30 @@ window.Hive3D = (function () {
|
|
|
114
119
|
emitSide(1);emitSide(-1);
|
|
115
120
|
return geometry(gl,positions,indices,normals);
|
|
116
121
|
}
|
|
122
|
+
function wingGeometry(gl){
|
|
123
|
+
// Separate convex, oval wing mesh for the bee assistants. Plants retain organicGeometry.
|
|
124
|
+
let outline=WING_OUTLINE.map(p=>p.slice());
|
|
125
|
+
// WebGL back-face culling requires consistent counter-clockwise front faces.
|
|
126
|
+
const wingArea=outline.reduce((sum,p,i)=>{const q=outline[(i+1)%outline.length];return sum+p[0]*q[1]-q[0]*p[1];},0);
|
|
127
|
+
if(wingArea<0)outline=outline.reverse();
|
|
128
|
+
const positions=[],normals=[],indices=[],colors=[];
|
|
129
|
+
const emitSide=(sign)=>{
|
|
130
|
+
const centerIndex=positions.length/3;
|
|
131
|
+
positions.push(0,0,sign*.42);normals.push(0,0,sign);colors.push(.80,.91,.99,.74);
|
|
132
|
+
for(const p of outline){
|
|
133
|
+
positions.push(p[0],p[1],p[2]*sign);
|
|
134
|
+
normals.push(...norm([-.48*p[0],-.48*p[1],sign]));
|
|
135
|
+
const edge=Math.min(1,Math.hypot(p[0]*.82,p[1]*1.15));
|
|
136
|
+
colors.push(.70-.06*edge,.86-.04*edge,.98-.02*edge,.70-.08*edge);
|
|
137
|
+
}
|
|
138
|
+
for(let i=0;i<outline.length;i++){
|
|
139
|
+
const a=centerIndex+1+i,b=centerIndex+1+(i+1)%outline.length;
|
|
140
|
+
if(sign>0)indices.push(centerIndex,a,b);else indices.push(centerIndex,b,a);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
emitSide(1);emitSide(-1);
|
|
144
|
+
const mesh=geometry(gl,positions,indices,normals);mesh.colors=colors;return mesh;
|
|
145
|
+
}
|
|
117
146
|
function hexGroutGeometry(gl){
|
|
118
147
|
// Build the complete staggered flat-top hex lattice as one static mesh. Drawing only
|
|
119
148
|
// three consecutive edges per tile creates crosses and broken hexes; unique full edges
|
|
@@ -145,6 +174,26 @@ window.Hive3D = (function () {
|
|
|
145
174
|
}
|
|
146
175
|
return geometry(gl,positions,indices,normals);
|
|
147
176
|
}
|
|
177
|
+
function radialShadowGeometry(gl,segments=48,rings=7){
|
|
178
|
+
// Smooth, triangulated light falloff avoids the low-poly ellipsoid stamps that read
|
|
179
|
+
// like floating objects on the pale hex floor. Vertex alpha fades completely at the rim.
|
|
180
|
+
const positions=[0,0,0],normals=[0,1,0],colors=[1,1,1,.24],indices=[];
|
|
181
|
+
for(let ring=1;ring<=rings;ring++)for(let i=0;i<segments;i++){
|
|
182
|
+
const a=i/segments*Math.PI*2,r=ring/rings;
|
|
183
|
+
positions.push(Math.cos(a)*r,0,Math.sin(a)*r);normals.push(0,1,0);
|
|
184
|
+
const fade=1-r;colors.push(1,1,1,.24*fade*fade);
|
|
185
|
+
}
|
|
186
|
+
for(let i=0;i<segments;i++){
|
|
187
|
+
const next=(i+1)%segments;
|
|
188
|
+
indices.push(0,1+next,1+i);
|
|
189
|
+
}
|
|
190
|
+
for(let ring=1;ring<rings;ring++)for(let i=0;i<segments;i++){
|
|
191
|
+
const next=(i+1)%segments,a=1+(ring-1)*segments+i,b=1+(ring-1)*segments+next;
|
|
192
|
+
const c=1+ring*segments+i,d=1+ring*segments+next;
|
|
193
|
+
indices.push(a,d,c,a,b,d);
|
|
194
|
+
}
|
|
195
|
+
const mesh=geometry(gl,positions,indices,normals);mesh.colors=colors;return mesh;
|
|
196
|
+
}
|
|
148
197
|
function cylinderGeometry(gl,segments=20){
|
|
149
198
|
const p=[],n=[],ix=[];
|
|
150
199
|
// Side wall has independent normals from the flat end caps, avoiding pinched highlights.
|
|
@@ -171,7 +220,7 @@ window.Hive3D = (function () {
|
|
|
171
220
|
function Scene(opts){
|
|
172
221
|
this.opts=opts;this.canvas=opts.canvas;this.overlay=opts.overlay;this.host=this.canvas.parentElement;
|
|
173
222
|
this.onCellActivate=opts.onCellActivate||function(){};this.cellRefresh=opts.cellRefresh||function(){return{};};
|
|
174
|
-
this.model=null;this.cells=[];this.page=0;this.zoom=1;this.yaw=0;this.pitch=0.
|
|
223
|
+
this.model=null;this.cells=[];this.page=0;this.zoom=1;this.yaw=0;this.pitch=0.48;this.panX=0;this.panY=0;this.active=false;this.raf=0;this.timer=0;this.listeners=[];this.screenMeta=[];this.displayMode="reference";this.eyeDirection=norm([0,3.1+Math.sin(this.pitch)*13.88-.62,25.4-1.3]);
|
|
175
224
|
this.host.dataset.renderer="webgl";this.canvas.hidden=false;this.canvas.classList.add("hive-gl-live");
|
|
176
225
|
this.referenceImage=this.host.querySelector(".hive-reference");
|
|
177
226
|
this.layer=node("div","hive-reference-layer",this.host);
|
|
@@ -190,9 +239,9 @@ window.Hive3D = (function () {
|
|
|
190
239
|
const gl=this.canvas.getContext("webgl",{alpha:true,antialias:true,powerPreference:"high-performance"})||this.canvas.getContext("experimental-webgl");
|
|
191
240
|
if(!gl)throw Error("WebGL unavailable");this.gl=gl;
|
|
192
241
|
const vs="attribute vec3 aPosition; attribute vec3 aNormal; attribute vec4 aColor; uniform mat4 uViewProj; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; void main(){gl_Position=uViewProj*vec4(aPosition,1.0);vColor=aColor;vWorld=aPosition;vNormal=normalize(aNormal);vec3 n=normalize(aNormal);float sky=clamp(n.y*0.5+0.5,0.0,1.0);float side=abs(n.x)*0.035;float heightShade=mix(0.94,1.0,smoothstep(-0.2,3.6,aPosition.y));vShade=(0.88+0.08*sky+side)*heightShade;}";
|
|
193
|
-
const fs="precision mediump float; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; uniform vec3 uEyePosition; void main(){vec3 n=normalize(vNormal);vec3 v=normalize(uEyePosition-vWorld);vec3 key=normalize(vec3(-0.48,0.86,0.42));vec3 fill=normalize(vec3(0.62,0.32,-0.72));vec3 warm=normalize(vec3(0.34,0.46,0.82));float ndl=max(dot(n,key),0.0);float fillN=max(dot(n,fill),0.0);float warmN=max(dot(n,warm),0.0);float hemi=clamp(n.y*0.5+0.5,0.0,1.0);vec3 base=vColor.rgb;float wallBlue=step(0.38,base.b)*step(0.35,base.g)*step(base.r*1.65,base.g)*step(1.7,vWorld.y);float wallGradient=0.
|
|
242
|
+
const fs="precision mediump float; varying vec4 vColor; varying float vShade; varying vec3 vWorld; varying vec3 vNormal; uniform vec3 uEyePosition; void main(){vec3 n=normalize(vNormal);vec3 v=normalize(uEyePosition-vWorld);vec3 key=normalize(vec3(-0.48,0.86,0.42));vec3 fill=normalize(vec3(0.62,0.32,-0.72));vec3 warm=normalize(vec3(0.34,0.46,0.82));float ndl=max(dot(n,key),0.0);float fillN=max(dot(n,fill),0.0);float warmN=max(dot(n,warm),0.0);float hemi=clamp(n.y*0.5+0.5,0.0,1.0);vec3 base=vColor.rgb;float chroma=max(max(base.r,base.g),base.b)-min(min(base.r,base.g),base.b);float wallBlue=step(0.38,base.b)*step(0.35,base.g)*step(base.r*1.65,base.g)*step(1.7,vWorld.y);float wallGradient=0.78+0.12*smoothstep(1.0,4.5,vWorld.y)+0.24*exp(-pow((vWorld.x-0.2)*0.19,2.0));base=mix(base,base*wallGradient,wallBlue);vec3 h=normalize(key+v);vec3 hf=normalize(fill+v);float gloss=mix(0.10,0.34,smoothstep(0.10,0.82,chroma));float spec=pow(max(dot(n,h),0.0),mix(24.0,58.0,gloss))*gloss*0.42;float specFill=pow(max(dot(n,hf),0.0),36.0)*0.075;float fresnel=pow(1.0-max(dot(n,v),0.0),3.0);vec3 ambient=base*(0.55+0.13*hemi);vec3 direct=base*(0.34*ndl+0.16*fillN+0.08*warmN);vec3 highlight=vec3(1.0,0.91,0.78)*spec+vec3(0.52,0.84,1.0)*specFill+vec3(0.18,0.48,0.68)*fresnel*0.075;float cyan=max(0.0,min(base.g,base.b)-base.r)*0.65;vec3 emissive=base*cyan;float honey=step(0.68,base.r)*step(0.34,base.g)*step(base.g*0.96,base.r)*step(base.b*1.4,base.g);vec3 lit=ambient+direct+highlight+emissive+vec3(0.18,0.105,0.018)*honey;lit*=vShade;gl_FragColor=vec4(min(lit,vec3(1.0)),vColor.a);}";
|
|
194
243
|
const program=gl.createProgram();gl.attachShader(program,shader(gl,gl.VERTEX_SHADER,vs));gl.attachShader(program,shader(gl,gl.FRAGMENT_SHADER,fs));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));this.program=program;gl.useProgram(program);
|
|
195
|
-
this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.grout=hexGroutGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.08,.18);this.bandGoldBottom=sphereBandGeometry(gl,-.97,-.61);this.bandSeparator=sphereBandGeometry(gl,-.51,-.38);this.bandGoldTop.smooth=true;this.bandGoldBottom.smooth=true;this.bandSeparator.smooth=true;gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);gl.clearColor(0,0,0,0);
|
|
244
|
+
this.aPosition=gl.getAttribLocation(program,"aPosition");this.aNormal=gl.getAttribLocation(program,"aNormal");this.aColor=gl.getAttribLocation(program,"aColor");this.uViewProj=gl.getUniformLocation(program,"uViewProj");this.uEyePosition=gl.getUniformLocation(program,"uEyePosition");this.box=boxGeometry(gl);this.roundBox=roundedBoxGeometry(gl,6,.12);this.sphere=sphereGeometry(gl,16,24);this.cylinder=cylinderGeometry(gl,20);this.organic=organicGeometry(gl);this.wing=wingGeometry(gl);this.grout=hexGroutGeometry(gl);this.shadow=radialShadowGeometry(gl);this.bandGoldTop=sphereBandGeometry(gl,-.08,.18);this.bandGoldBottom=sphereBandGeometry(gl,-.97,-.61);this.bandSeparator=sphereBandGeometry(gl,-.51,-.38);this.bandGoldTop.smooth=true;this.bandGoldBottom.smooth=true;this.bandSeparator.smooth=true;gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.cullFace(gl.BACK);gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);gl.clearColor(0,0,0,0);
|
|
196
245
|
};
|
|
197
246
|
Scene.prototype.listen=function(el,type,fn,opts){el.addEventListener(type,fn,opts);this.listeners.push(()=>el.removeEventListener(type,fn,opts));};
|
|
198
247
|
Scene.prototype.bindSurface=function(){
|
|
@@ -209,8 +258,11 @@ window.Hive3D = (function () {
|
|
|
209
258
|
Scene.prototype.buildWorld=function(){
|
|
210
259
|
const b=this.box,rb=this.roundBox,s=this.sphere;this.objects=[];
|
|
211
260
|
// Bright, calm blue-white studio with a continuous floor and a single clean feature wall. The free camera is framed wide enough to keep all 14 stations visible at desktop aspect ratios.
|
|
212
|
-
|
|
213
|
-
|
|
261
|
+
// Keep the studio bright and photographic: the reference has a nearly white
|
|
262
|
+
// floor with cool blue bounce, while the feature wall carries the saturated
|
|
263
|
+
// accent. The old values made the whole room read as a blue plastic box.
|
|
264
|
+
this.add(b,0,-.18,5.5,23,.28,34,[.86,.90,.94,1]);
|
|
265
|
+
this.add(b,0,2.70,-6.45,19,3.65,.24,[.19,.53,.83,1]);
|
|
214
266
|
this.add(b,0,4.58,-6.30,19,.075,.12,[.985,.99,1,1]);
|
|
215
267
|
// Wall seams, lower trim, and glass side windows.
|
|
216
268
|
this.add(b,0,.90,-6.28,18.7,.12,.06,[.08,.42,.78,1]);
|
|
@@ -226,8 +278,8 @@ window.Hive3D = (function () {
|
|
|
226
278
|
}
|
|
227
279
|
// Ceiling light fixtures and soft-colored illuminated panels.
|
|
228
280
|
for(let x=-6.3;x<=6.4;x+=6.3){
|
|
229
|
-
this.add(b,x,
|
|
230
|
-
this.add(b,x,
|
|
281
|
+
this.add(b,x,4.66,-3.35,2.65,.065,.30,[.12,.17,.23,1]);
|
|
282
|
+
this.add(b,x,4.595,-3.35,2.40,.045,.24,[1,.99,.91,1]);
|
|
231
283
|
}
|
|
232
284
|
// Six white phase controls are projected on this wall by the live HTML overlay.
|
|
233
285
|
for(let i=0;i<6;i++){
|
|
@@ -240,12 +292,12 @@ window.Hive3D = (function () {
|
|
|
240
292
|
this.add(b,x,2.34,-6.005,.035,.48,.024,[.22,.89,1,1]);
|
|
241
293
|
}
|
|
242
294
|
// Continuous, pale hex grout is one static mesh: no crossed edges and no thousands of scene objects.
|
|
243
|
-
this.add(this.grout,0,0,0,1,1,1,[.
|
|
295
|
+
this.add(this.grout,0,0,0,1,1,1,[.88,.91,.92,1]);
|
|
244
296
|
// Reference layout: eight compact rear stations and six wider front stations.
|
|
245
297
|
// Every monitor overlay is projected from these exact same coordinates below.
|
|
246
298
|
const deskRows=[
|
|
247
|
-
{row:0,z:-1.80,width:2.03,screenWidth:1.28,screenHeight:.
|
|
248
|
-
{row:1,z:5.85,width:2.42,screenWidth:1.
|
|
299
|
+
{row:0,z:-1.80,width:2.03,screenWidth:1.28,screenHeight:.54,xs:[-8.4,-6.0,-3.6,-1.2,1.2,3.6,6.0,8.4]},
|
|
300
|
+
{row:1,z:5.85,width:2.42,screenWidth:1.62,screenHeight:.64,xs:[-6.0,-3.6,-1.2,1.2,3.6,6.0]}
|
|
249
301
|
];
|
|
250
302
|
this.deskPositions=[];
|
|
251
303
|
for(const config of deskRows)for(const x of config.xs){
|
|
@@ -258,16 +310,30 @@ window.Hive3D = (function () {
|
|
|
258
310
|
this.add(b,x,.455,z-.565,width*.64,.028,.024,[.61,.70,.79,1]);
|
|
259
311
|
this.add(rb,x-width*.36,.25,z+.03,.43,.52,1.02,[.90,.93,.97,1]);
|
|
260
312
|
this.add(rb,x+width*.36,.25,z+.03,.43,.52,1.02,[.90,.93,.97,1]);
|
|
313
|
+
// Full-height acoustic partitions give every workstation the enclosed, built-in
|
|
314
|
+
// cubicle silhouette of the reference while preserving a clear sightline to screens.
|
|
315
|
+
for(const side of [-1,1]){
|
|
316
|
+
const dividerX=x+side*(width*.49);
|
|
317
|
+
this.add(rb,dividerX,.88,z+.005,.058,1.06,1.16,[.91,.93,.93,1]);
|
|
318
|
+
this.add(rb,dividerX-side*.012,.90,z-.015,.020,.96,1.08,[.98,.98,.96,1]);
|
|
319
|
+
this.add(rb,dividerX,.88,z-.595,.066,1.04,.045,[.95,.96,.94,1]);
|
|
320
|
+
this.add(rb,dividerX,.88,z+.595,.066,1.04,.045,[.95,.96,.94,1]);
|
|
321
|
+
this.add(b,dividerX-side*.014,.88,z-.55,.009,.76,.010,[.75,.81,.83,1]);
|
|
322
|
+
this.add(b,dividerX-side*.014,.88,z+.55,.009,.76,.010,[.75,.81,.83,1]);
|
|
323
|
+
}
|
|
261
324
|
// A compact three-drawer pedestal gives each station the white office furniture silhouette of the reference.
|
|
262
|
-
this.add(rb,x+width*.30,.27,z+.445,width*.30,.42,.26,[.
|
|
325
|
+
this.add(rb,x+width*.30,.27,z+.445,width*.30,.42,.26,[.97,.965,.94,1]);
|
|
263
326
|
for(const drawerY of [.18,.28,.38])this.add(b,x+width*.30,drawerY,z+.579,width*.22,.012,.012,[.62,.71,.80,1]);
|
|
264
327
|
this.add(b,x+width*.30,.41,z+.581,width*.085,.018,.014,[.45,.58,.70,1]);
|
|
328
|
+
for(const drawerY of [.18,.28,.38])this.add(rb,x+width*.30,drawerY,z+.593,width*.105,.026,.018,[.80,.86,.92,1]);
|
|
265
329
|
this.add(rb,x,.43,z-.22,width*.78,.045,.74,[.89,.93,.97,1]);
|
|
266
|
-
this.add(rb,x,screenY,z-.48,screenWidth+.
|
|
267
|
-
this.add(rb,x,screenY,z-.
|
|
268
|
-
this.add(b,x,screenY,z-.
|
|
269
|
-
this.add(this.cylinder,x,.78,z-.43,.
|
|
270
|
-
this.add(
|
|
330
|
+
this.add(rb,x,screenY,z-.48,screenWidth+.055,screenHeight+.095,.060,[.025,.040,.055,1]);
|
|
331
|
+
this.add(rb,x,screenY,z-.447,screenWidth+.028,screenHeight+.060,.020,[.12,.17,.21,1]);
|
|
332
|
+
this.add(b,x,screenY,z-.408,screenWidth,screenHeight,.018,[.025,.145,.235,1]);
|
|
333
|
+
this.add(this.cylinder,x,.78,z-.43,.034,.36,.034,[.55,.60,.63,1]);
|
|
334
|
+
this.add(rb,x,.60,z-.45,.30,.18,.028,[.76,.79,.79,1]);
|
|
335
|
+
this.add(rb,x,.935,z-.451,.20,.10,.038,[.43,.49,.52,1]);
|
|
336
|
+
this.add(b,x,.49,z-.27,.54,.045,.34,[.12,.17,.20,1]);
|
|
271
337
|
this.add(b,x,screenY-screenHeight*.42,z-.382,screenWidth*.84,.018,.014,[.10,.83,.94,1]);
|
|
272
338
|
// A tiny centered webcam and a restrained power indicator give the monitor a finished hardware read.
|
|
273
339
|
this.add(rb,x,screenY+screenHeight*.5+.064,z-.386,.070,.026,.018,[.025,.035,.045,1]);
|
|
@@ -280,34 +346,43 @@ window.Hive3D = (function () {
|
|
|
280
346
|
const keyColor=(row===0&&col%4===0)?[.54,.61,.68,1]:((row+col)%5===0?[.40,.48,.56,1]:[.22,.27,.32,1]);
|
|
281
347
|
this.add(b,x-.45+col*.049,.659,z+.185+row*.045,.030,.009,.024,keyColor);
|
|
282
348
|
}
|
|
283
|
-
this.add(b,x-.18,.62,z+.25,.66,.012,.24,[.
|
|
349
|
+
this.add(b,x-.18,.62,z+.25,.66,.012,.24,[.78,.82,.83,1]);
|
|
350
|
+
for(let key=0;key<4;key++)this.add(b,x-.43+key*.14,.669,z+.17,.11,.004,.012,[.54,.61,.64,1]);
|
|
351
|
+
for(let key=0;key<4;key++)this.add(b,x-.43+key*.14,.669,z+.32,.11,.004,.012,[.54,.61,.64,1]);
|
|
284
352
|
const binderStart=x+width/2-.48;
|
|
285
353
|
for(let k=0;k<4;k++)this.add(b,binderStart+k*.095,.83,z+.35,.075,.43,.22,[[.13,.43,.78,1],[.95,.42,.24,1],[.16,.66,.46,1],[.93,.72,.28,1]][k]);
|
|
286
354
|
const blueBinderStart=x-width/2+.19;
|
|
287
355
|
for(let k=0;k<3;k++)this.add(b,blueBinderStart+k*.095,.79,z+.34,.075,.35,.18,[[.08,.34,.80,1],[.10,.51,.91,1],[.06,.25,.65,1]][k]);
|
|
288
356
|
this.add(this.cylinder,x,.08,z+1.10,.075,.28,.075,[.12,.15,.18,1]);
|
|
289
357
|
this.add(rb,x,.30,z+1.10,.94,.12,.68,[.11,.14,.17,1]);
|
|
290
|
-
this.add(rb,x,.
|
|
291
|
-
this.add(rb,x,.
|
|
358
|
+
this.add(rb,x,.68,z+.82,.82,.68,.15,[.17,.20,.23,1]);
|
|
359
|
+
this.add(rb,x,.68,z+.91,.62,.56,.024,[.24,.28,.31,1]);
|
|
292
360
|
this.add(rb,x,.42,z+1.10,.48,.12,.12,[.18,.22,.25,1]);
|
|
293
361
|
// Contrasting graphite upholstery separates the chair from the bee chassis.
|
|
294
|
-
this.add(rb,x,.
|
|
295
|
-
for(const ribY of [.
|
|
296
|
-
for(const side of [-1,1])this.add(rb,x+side*.
|
|
362
|
+
this.add(rb,x,.68,z+.90,.62,.56,.050,[.22,.25,.28,1]);
|
|
363
|
+
for(const ribY of [.45,.64,.82,.94])this.add(rb,x,ribY,z+.93,.44,.018,.020,[.38,.41,.43,1]);
|
|
364
|
+
for(const side of [-1,1])this.add(rb,x+side*.40,.68,z+.93,.026,.60,.025,[.29,.34,.38,1]);
|
|
297
365
|
for(const side of [-1,1]){
|
|
298
366
|
this.add(rb,x+side*.39,.63,z+.96,.10,.075,.38,[.14,.18,.21,1]);
|
|
299
367
|
this.add(b,x+side*.39,.48,z+.97,.035,.28,.045,[.10,.13,.16,1]);
|
|
300
368
|
}
|
|
301
369
|
for(let k=0;k<5;k++){const a=k*Math.PI*2/5;const wx=x+Math.cos(a)*.48,wz=z+1.10+Math.sin(a)*.40;this.add(b,x+Math.cos(a)*.38,.035,z+1.10+Math.sin(a)*.32,.30,.06,.075,[.08,.10,.12,1],a);this.add(s,wx,.005,wz,.085,.065,.075,[.035,.045,.055,1]);}
|
|
302
|
-
const by=1.25,bz=z+.98;
|
|
370
|
+
const by=1.25,bz=z+.98,beeObjectStart=this.objects.length;
|
|
303
371
|
// Bee assistant: distinct head, dark chassis, striped abdomen, headset and translucent wings.
|
|
304
|
-
this.add(s,x,by-.48,bz+.005,.
|
|
372
|
+
this.add(s,x,by-.48,bz+.005,.325,.355,.250,[.075,.09,.11,1]);
|
|
305
373
|
// Slightly enlarge the band ellipsoid to avoid depth-buffer z-fighting with the shell.
|
|
306
|
-
const shellX=.
|
|
307
|
-
this.add(this.bandGoldTop,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.
|
|
308
|
-
this.add(this.bandGoldBottom,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[
|
|
374
|
+
const shellX=.330,shellY=.360,shellZ=.255,shellCenterY=by-.48,shellCenterZ=bz+.005;
|
|
375
|
+
this.add(this.bandGoldTop,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.86,.12,1]);
|
|
376
|
+
this.add(this.bandGoldBottom,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[1,.70,.045,1]);
|
|
309
377
|
this.add(this.bandSeparator,x,shellCenterY,shellCenterZ,shellX,shellY,shellZ,[.045,.055,.065,1]);
|
|
310
|
-
this.add(s,x,by,bz,.
|
|
378
|
+
this.add(s,x,by,bz,.515,.465,.415,[1,.94,.22,1]);
|
|
379
|
+
// The reference bee has a clear glossy black cheek/eye patch visible from behind.
|
|
380
|
+
// Put it on the camera-facing quarter of the head so the character reads at scene scale.
|
|
381
|
+
this.add(s,x+.340,by+.025,bz+.265,.190,.205,.074,[.008,.012,.018,1]);
|
|
382
|
+
this.add(s,x+.355,by+.085,bz+.315,.034,.038,.018,[.32,.86,.98,1]);
|
|
383
|
+
// A slim graphite cowl across the crown catches light like the reference's
|
|
384
|
+
// dark headset band and separates the head from the rounded thorax below.
|
|
385
|
+
this.add(rb,x,by+.405,bz+.004,.52,.060,.32,[.12,.14,.15,1]);
|
|
311
386
|
// A forward-facing graphite visor makes the assistant read as a character, not a plain yellow sphere.
|
|
312
387
|
// The workstation monitor is on -Z, so the faceplate and optical details sit on that side of the head.
|
|
313
388
|
this.add(rb,x,by+.012,bz-.166,.258,.142,.026,[.035,.055,.070,1]);
|
|
@@ -321,20 +396,25 @@ window.Hive3D = (function () {
|
|
|
321
396
|
for(const side of [-1,1]){
|
|
322
397
|
this.add(s,x+side*.382,by+.015,bz+.035,.078,.118,.094,[.045,.055,.065,1]);
|
|
323
398
|
this.add(s,x+side*.407,by+.015,bz+.052,.030,.052,.048,[.99,.68,.045,1]);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
399
|
+
const antennaBase=[x+side*.125,by+.39,bz+.14];
|
|
400
|
+
const antennaBend=[x+side*.16,by+.55,bz+.16];
|
|
401
|
+
const antennaTip=[x+side*.205,by+.65,bz+.19];
|
|
402
|
+
this.addLink(antennaBase,antennaBend,.013,[.045,.055,.065,1]);
|
|
403
|
+
this.addLink(antennaBend,antennaTip,.011,[.045,.055,.065,1]);
|
|
404
|
+
this.add(s,antennaTip[0],antennaTip[1],antennaTip[2],.030,.030,.030,[.10,.12,.14,1]);
|
|
405
|
+
this.add(s,antennaTip[0],antennaTip[1]+.012,antennaTip[2]+.012,.012,.012,.012,[1,.73,.10,1]);
|
|
327
406
|
}
|
|
328
407
|
this.add(rb,x,by-.335,bz+.156,.32,.026,.018,[.045,.055,.065,1]);
|
|
329
408
|
this.add(s,x,by+.30,bz+.18,.115,.035,.06,[.045,.055,.065,1]);
|
|
330
409
|
// Short forward-projecting thighs tuck under the desktop and visually anchor the bee to its chair.
|
|
331
410
|
for(const side of [-1,1]){
|
|
332
411
|
this.add(s,x+side*.125,.465,z+.71,.075,.13,.14,[.075,.09,.11,1]);
|
|
333
|
-
this.add(rb,x+side*.13,.315,z+.66,.10,.060,.15,[
|
|
412
|
+
this.add(rb,x+side*.13,.315,z+.66,.10,.060,.15,[1,.86,.12,1]);
|
|
334
413
|
}
|
|
335
|
-
//
|
|
414
|
+
// A wider elbow arc keeps forearms visible around the torso in the rear camera view;
|
|
415
|
+
// the left hand stays over the keyboard and the right hand aligns with the mouse.
|
|
336
416
|
for(const side of [-1,1]){
|
|
337
|
-
const shoulder=[x+side*.
|
|
417
|
+
const shoulder=[x+side*.36,.99,z+.94],elbow=[x+side*.55,.83,z+.82],wrist=[x+(side<0?-.49:.50),.675,z+.54],hand=[x+(side<0?-.42:.42),.655,z+.28];
|
|
338
418
|
this.addLink(shoulder,elbow,.042,[.12,.15,.18,1]);
|
|
339
419
|
this.addLink(elbow,wrist,.036,[.18,.22,.25,1]);
|
|
340
420
|
this.addLink(wrist,hand,.030,[.96,.63,.035,1]);
|
|
@@ -348,48 +428,68 @@ window.Hive3D = (function () {
|
|
|
348
428
|
// Small cyan service badge on the back of the chassis.
|
|
349
429
|
this.add(rb,x,by-.585,bz+.205,.13,.068,.018,[.025,.10,.14,1]);
|
|
350
430
|
this.add(rb,x,by-.585,bz+.218,.082,.016,.010,[.16,.88,.98,1]);
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
this.add(this.
|
|
354
|
-
|
|
431
|
+
// Angled, translucent wings emerge from behind the head; a leading-edge highlight
|
|
432
|
+
// defines the outward/upward sweep without enclosing the wing like a headset ring.
|
|
433
|
+
this.add(this.wing,x-.49,by+.17,bz+.14,.82,.60,.10,[.88,.96,1,.96],0,-.48);
|
|
434
|
+
this.add(this.wing,x+.49,by+.17,bz+.14,.82,.60,.10,[.88,.96,1,.96],0,.48);
|
|
355
435
|
for(const side of [-1,1]){
|
|
356
|
-
const
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
436
|
+
const rotation=side*.48,ca=Math.cos(rotation),sa=Math.sin(rotation);
|
|
437
|
+
const edgePoints=WING_OUTLINE.map(p=>[
|
|
438
|
+
x+side*.49+ca*p[0]*.82-sa*p[1]*.60,
|
|
439
|
+
by+.17+sa*p[0]*.82+ca*p[1]*.60,
|
|
440
|
+
bz+.125
|
|
441
|
+
]);
|
|
442
|
+
for(let i=0;i<6;i++)this.addLink(edgePoints[i],edgePoints[i+1],.0052,[.59,.80,.91,.56]);
|
|
443
|
+
const root=[x+side*.38,by+.04,bz+.17];
|
|
444
|
+
const mid=[x+side*.50,by+.17,bz+.17];
|
|
445
|
+
const tip=[x+side*.76,by+.29,bz+.17];
|
|
446
|
+
this.addLink(root,mid,.0055,[.83,.93,.98,.70]);
|
|
447
|
+
this.addLink(mid,tip,.0042,[.83,.93,.98,.62]);
|
|
448
|
+
this.addLink([x+side*.43,by+.15,bz+.13],[x+side*.55,by+.22,bz+.13],.0032,[.90,.96,1,.54]);
|
|
449
|
+
}
|
|
450
|
+
// Slightly smaller assistants read as seated workers within the furniture scale.
|
|
451
|
+
for(const assistant of this.objects.slice(beeObjectStart)){
|
|
452
|
+
assistant.x=x+(assistant.x-x)*.92;
|
|
453
|
+
assistant.y=by+(assistant.y-by)*.92;
|
|
454
|
+
assistant.z=bz+(assistant.z-bz)*.92;
|
|
455
|
+
assistant.sx*=.84;assistant.sy*=.84;assistant.sz*=.84;
|
|
362
456
|
}
|
|
363
457
|
|
|
364
458
|
}
|
|
365
459
|
// Planters and stylized leaves soften the room edges.
|
|
366
460
|
for(const x of [-9.05,9.05]){
|
|
367
|
-
this.add(s,x,-.032,-4.70,.
|
|
368
|
-
this.add(rb,x,.17,-4.70,.
|
|
461
|
+
this.add(s,x,-.032,-4.70,.78,.012,.82,[.055,.09,.14,.24]);
|
|
462
|
+
this.add(rb,x,.17,-4.70,.68,.34,.68,[.70,.77,.81,1]);
|
|
369
463
|
this.add(this.cylinder,x,.70,-4.70,.065,.78,.065,[.31,.28,.19,1]);
|
|
370
464
|
// Layered foliage with varied yaw and drooping leaf tips, so the planters read as broad indoor plants.
|
|
371
465
|
for(let k=0;k<18;k++){
|
|
372
466
|
const a=k/18*Math.PI*2,spread=Math.sin(a)*.62;
|
|
373
|
-
const y
|
|
374
|
-
const leafColor=[[.
|
|
467
|
+
const y=1.00+(k%5)*.17,rad=.24+(k%4)*.115;
|
|
468
|
+
const leafColor=[[.10,.43,.15,1],[.15,.55,.18,1],[.20,.64,.22,1],[.28,.70,.28,1]][k%4];
|
|
375
469
|
this.add(this.organic,x+Math.sin(a)*rad,y,-4.70+Math.cos(a)*rad*.62,
|
|
376
|
-
.
|
|
470
|
+
.68+(k%3)*.075,.90+(k%4)*.12,.05,leafColor,a*.62-Math.PI*.18,Math.PI/2+spread);
|
|
377
471
|
}
|
|
378
472
|
for(let k=0;k<7;k++){
|
|
379
473
|
const a=k/7*Math.PI*2,spread=Math.sin(a)*.48;
|
|
380
|
-
this.add(this.organic,x+Math.sin(a)*.
|
|
381
|
-
.
|
|
474
|
+
this.add(this.organic,x+Math.sin(a)*.14,1.82+(k%3)*.12,-4.70+Math.cos(a)*.16,
|
|
475
|
+
.44,.72,.035,[.16,.64,.23,1],a*.65,Math.PI/2+spread);
|
|
382
476
|
}
|
|
383
477
|
}
|
|
384
478
|
// Low-opacity contact shadows share the same 8+6 workstation positions.
|
|
385
479
|
for(const desk of this.deskPositions){
|
|
386
480
|
const {x,z,width}=desk;
|
|
387
|
-
|
|
388
|
-
|
|
481
|
+
// Place the soft shadow just above the coplanar hex grout so depth testing can
|
|
482
|
+
// blend the contact shade without z-fighting or the grout rejecting it.
|
|
483
|
+
this.add(this.shadow,x,-.029,z+.02,width*.78,.009,.88,[.20,.29,.39,.90]);
|
|
484
|
+
this.add(this.shadow,x+.02,-.028,z+1.0,.84,.009,.54,[.20,.29,.39,.76]);
|
|
485
|
+
for(const dx of [-.36,.36])for(const dz of [-.39,.38]){
|
|
486
|
+
this.add(this.shadow,x+width*dx,-.028,z+dz,.39,.008,.38,[.20,.29,.39,.66]);
|
|
487
|
+
this.add(this.shadow,x+width*dx,.462,z+dz,.40,.009,.40,[.20,.29,.39,.36]);
|
|
488
|
+
}
|
|
389
489
|
}
|
|
390
490
|
};
|
|
391
491
|
Scene.prototype.buildBatches=function(){
|
|
392
|
-
const channels={opaque:{positions:[],normals:[],colors:[]},transparent:{positions:[],normals:[],colors:[]}};
|
|
492
|
+
const channels={opaque:{positions:[],normals:[],colors:[]},transparent:{positions:[],normals:[],colors:[],triangles:[]}};
|
|
393
493
|
const emit=(target,p,n,color)=>{
|
|
394
494
|
target.positions.push(p[0],p[1],p[2]);target.normals.push(n[0],n[1],n[2]);
|
|
395
495
|
target.colors.push(color[0],color[1],color[2],color[3]==null?1:color[3]);
|
|
@@ -405,19 +505,30 @@ window.Hive3D = (function () {
|
|
|
405
505
|
const world=local.map(v=>worldPoint(o,v));
|
|
406
506
|
const smooth=mesh===this.sphere||mesh.smooth;
|
|
407
507
|
const face=smooth?null:faceNormal(world[0],world[1],world[2]);
|
|
508
|
+
const triangle=[];
|
|
408
509
|
for(let k=0;k<3;k++){
|
|
409
510
|
const ix3=ix[i+k]*3,normal=[normals[ix3],normals[ix3+1],normals[ix3+2]];
|
|
410
|
-
|
|
511
|
+
const color=mesh.colors?mesh.colors.slice(ix[i+k]*4,ix[i+k]*4+4):o.color;
|
|
512
|
+
if(mesh.colors)color[3]*=o.color[3]==null?1:o.color[3];
|
|
513
|
+
const vertex={position:world[k],normal:face||smoothNormal(o,normal),color};
|
|
514
|
+
if(target===channels.transparent)triangle.push(vertex);else emit(target,vertex.position,vertex.normal,vertex.color);
|
|
411
515
|
}
|
|
516
|
+
if(triangle.length)channels.transparent.triangles.push({vertices:triangle,depth:triangle.reduce((sum,v)=>sum+v.position[0]*this.eyeDirection[0]+v.position[1]*this.eyeDirection[1]+v.position[2]*this.eyeDirection[2],0)/3});
|
|
412
517
|
}
|
|
413
518
|
}
|
|
519
|
+
// Transparent objects retain depth testing but disable depth writes. Keep their
|
|
520
|
+
// triangles so the packed draw batch can be depth-sorted again when the camera moves.
|
|
521
|
+
this.transparentTriangles=channels.transparent.triangles;
|
|
522
|
+
for(const triangle of this.transparentTriangles)triangle.depth=triangle.vertices.reduce((sum,v)=>sum+dot(v.position,this.eyeDirection),0)/3;
|
|
523
|
+
this.transparentTriangles.sort((a,b)=>a.depth-b.depth);
|
|
524
|
+
for(const triangle of this.transparentTriangles)for(const vertex of triangle.vertices)emit(channels.transparent,vertex.position,vertex.normal,vertex.color);
|
|
414
525
|
const gl=this.gl,upload=data=>{if(!data.length)return null;const buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);return buffer;};
|
|
415
526
|
this.batchBuffers={};
|
|
416
527
|
for(const key of ["opaque","transparent"]){
|
|
417
528
|
const channel=channels[key];
|
|
418
529
|
this.batchBuffers[key]={positions:upload(channel.positions),normals:upload(channel.normals),colors:upload(channel.colors),count:channel.positions.length/3};
|
|
419
530
|
}
|
|
420
|
-
for(const mesh of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.grout,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(mesh){if(mesh.p)gl.deleteBuffer(mesh.p);if(mesh.ix)gl.deleteBuffer(mesh.ix);mesh.p=null;mesh.ix=null;}
|
|
531
|
+
for(const mesh of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.wing,this.grout,this.shadow,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(mesh){if(mesh.p)gl.deleteBuffer(mesh.p);if(mesh.ix)gl.deleteBuffer(mesh.ix);mesh.p=null;mesh.ix=null;}
|
|
421
532
|
};
|
|
422
533
|
Scene.prototype.drawBatch=function(batch){
|
|
423
534
|
if(!batch||!batch.count)return;
|
|
@@ -427,11 +538,19 @@ window.Hive3D = (function () {
|
|
|
427
538
|
gl.bindBuffer(gl.ARRAY_BUFFER,batch.colors);gl.enableVertexAttribArray(this.aColor);gl.vertexAttribPointer(this.aColor,4,gl.FLOAT,false,0,0);
|
|
428
539
|
gl.drawArrays(gl.TRIANGLES,0,batch.count);
|
|
429
540
|
};
|
|
541
|
+
Scene.prototype.sortTransparentBatch=function(direction){
|
|
542
|
+
if(!this.transparentTriangles||!this.batchBuffers||!this.batchBuffers.transparent)return;
|
|
543
|
+
for(const triangle of this.transparentTriangles)triangle.depth=triangle.vertices.reduce((sum,v)=>sum+dot(v.position,direction),0)/3;
|
|
544
|
+
this.transparentTriangles.sort((a,b)=>a.depth-b.depth);
|
|
545
|
+
const positions=[],normals=[],colors=[];
|
|
546
|
+
for(const triangle of this.transparentTriangles)for(const vertex of triangle.vertices){positions.push(...vertex.position);normals.push(...vertex.normal);colors.push(...vertex.color);}
|
|
547
|
+
const gl=this.gl,batch=this.batchBuffers.transparent;gl.bindBuffer(gl.ARRAY_BUFFER,batch.positions);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(positions),gl.DYNAMIC_DRAW);gl.bindBuffer(gl.ARRAY_BUFFER,batch.normals);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(normals),gl.DYNAMIC_DRAW);gl.bindBuffer(gl.ARRAY_BUFFER,batch.colors);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(colors),gl.DYNAMIC_DRAW);
|
|
548
|
+
};
|
|
430
549
|
Scene.prototype.render=function(){
|
|
431
550
|
if(!this.gl||!this.active||this.displayMode!=="live3d")return;const gl=this.gl;const w=Math.max(1,this.cssW),h=Math.max(1,this.cssH);const dpr=Math.min(window.devicePixelRatio||1,1.6);
|
|
432
551
|
const bw=Math.floor(w*dpr),bh=Math.floor(h*dpr);if(this.canvas.width!==bw||this.canvas.height!==bh){this.canvas.width=bw;this.canvas.height=bh;}
|
|
433
552
|
gl.viewport(0,0,bw,bh);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);gl.useProgram(this.program);
|
|
434
|
-
const radius=
|
|
553
|
+
const radius=25.4/this.zoom;const eye=[Math.sin(this.yaw)*radius,3.1+Math.sin(this.pitch)*radius*.52,Math.cos(this.yaw)*radius-1.3];const view=mat4.lookAt(eye,[0,.62,0],[0,1,0]);const proj=mat4.perspective(.46,w/h,.1,80);const vp=mat4.multiply(mat4.multiply(proj,view),mat4.translate(this.panX,this.panY,0));this.viewProj=vp;this.eyeDirection=norm(sub(eye,[0,.62,0]));this.sortTransparentBatch(this.eyeDirection);
|
|
435
554
|
gl.uniformMatrix4fv(this.uViewProj,false,vp);gl.uniform3f(this.uEyePosition,eye[0],eye[1],eye[2]);
|
|
436
555
|
this.drawBatch(this.batchBuffers&&this.batchBuffers.opaque);
|
|
437
556
|
const translucent=this.batchBuffers&&this.batchBuffers.transparent;
|
|
@@ -520,14 +639,14 @@ window.Hive3D = (function () {
|
|
|
520
639
|
Scene.prototype.setActive=function(active){this.active=!!active;if(this.timer)clearInterval(this.timer);if(this.raf)cancelAnimationFrame(this.raf);this.raf=0;this.layer.hidden=!this.active;this.canvas.hidden=!this.active||this.displayMode!=="live3d";if(this.active){this.updateOverlay();if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();this.timer=setInterval(()=>{if(!document.hidden&&this.host.offsetParent!==null)this.updateOverlay();},1000);}};
|
|
521
640
|
Scene.prototype.resize=function(){this.cssW=this.host.clientWidth;this.cssH=this.host.clientHeight;if(!this.cssW||!this.cssH)return;if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();};
|
|
522
641
|
Scene.prototype.zoomAt=function(factor){this.zoom=clamp(this.zoom/factor,.72,2.4);this.applyArtboardTransform();this.render();};
|
|
523
|
-
Scene.prototype.resetView=function(){this.zoom=1;this.yaw=0;this.pitch=.
|
|
524
|
-
Scene.prototype.setDisplayMode=function(mode){const next=mode==="live3d"?"live3d":mode==="2d"?"2d":"reference";const changed=next!==this.displayMode;this.displayMode=next;if(changed){this.zoom=1;this.yaw=0;this.pitch=.
|
|
642
|
+
Scene.prototype.resetView=function(){this.zoom=1;this.yaw=0;this.pitch=.48;this.panX=0;this.panY=0;this.applyArtboardTransform();this.render();};
|
|
643
|
+
Scene.prototype.setDisplayMode=function(mode){const next=mode==="live3d"?"live3d":mode==="2d"?"2d":"reference";const changed=next!==this.displayMode;this.displayMode=next;if(changed){this.zoom=1;this.yaw=0;this.pitch=.48;this.panX=0;this.panY=0;}this.canvas.hidden=!this.active||this.displayMode!=="live3d";this.updateOverlay();this.applyArtboardTransform();if(this.displayMode==="live3d")this.render();else if(this.displayMode==="reference")this.layoutOverlayPositions();};
|
|
525
644
|
Scene.prototype.applyArtboardTransform=function(){if(!this.artboard)return;this.artboard.style.transform=this.displayMode==="reference"?"translate3d("+(this.panX*100)+"%,"+(this.panY*100)+"%,0) scale("+this.zoom+")":"none";};
|
|
526
645
|
Scene.prototype.info=function(){return{renderer:"webgl",cells:(this.cells||[]).length,screens:SCREENS.length,lanes:this.model?this.model.lanes.length:0,page:this.page,zoom:this.zoom,cssW:this.cssW,cssH:this.cssH,objects:this.objects?this.objects.length:0,deskRows:this.deskPositions?[this.deskPositions.filter(d=>d.row===0).length,this.deskPositions.filter(d=>d.row===1).length]:[]};};
|
|
527
646
|
Scene.prototype.projectCell=function(rel){const i=this.screenMeta.findIndex(c=>c.rel===rel),desk=this.deskPositions&&this.deskPositions[i];if(i<0||!desk)return null;const p=this.projectWorld(desk.x,desk.row===1?1.38:1.205,desk.z-.418);return p&&p.visible?{x:p.x,y:p.y}:null;};
|
|
528
|
-
Scene.prototype.dispose=function(){this.setActive(false);if(this.resizeObserver)this.resizeObserver.disconnect();if(this.languageObserver)this.languageObserver.disconnect();this.listeners.forEach(fn=>fn());if(this.pager)this.pager.remove();if(this.gl){const gl=this.gl;if(this.batchBuffers)for(const pass of Object.values(this.batchBuffers))for(const key of ["positions","normals","colors"])if(pass&&pass[key])gl.deleteBuffer(pass[key]);for(const o of [this.box,this.roundBox,this.sphere])if(o){if(o.p)gl.deleteBuffer(o.p);if(o.ix)gl.deleteBuffer(o.ix);}if(this.program)gl.deleteProgram(this.program);}if(window.__hive3d===this)delete window.__hive3d;};
|
|
647
|
+
Scene.prototype.dispose=function(){this.setActive(false);if(this.resizeObserver)this.resizeObserver.disconnect();if(this.languageObserver)this.languageObserver.disconnect();this.listeners.forEach(fn=>fn());if(this.pager)this.pager.remove();if(this.gl){const gl=this.gl;if(this.batchBuffers)for(const pass of Object.values(this.batchBuffers))for(const key of ["positions","normals","colors"])if(pass&&pass[key])gl.deleteBuffer(pass[key]);for(const o of [this.box,this.roundBox,this.sphere,this.cylinder,this.organic,this.wing,this.grout,this.shadow,this.bandGoldTop,this.bandGoldBottom,this.bandSeparator])if(o){if(o.p)gl.deleteBuffer(o.p);if(o.ix)gl.deleteBuffer(o.ix);}if(this.program)gl.deleteProgram(this.program);}if(window.__hive3d===this)delete window.__hive3d;};
|
|
529
648
|
const originalCreate=(opts)=>new Scene(opts);
|
|
530
649
|
// Initialize geometry after the constructor has successfully acquired WebGL.
|
|
531
650
|
const create=(opts)=>{const scene=originalCreate(opts);if(scene.failed||!scene.gl)return null;scene.buildWorld();scene.buildBatches();scene.render();return scene;};
|
|
532
651
|
return {create};
|
|
533
|
-
})();
|
|
652
|
+
})();
|
package/app/ui/i18n.js
CHANGED
|
@@ -10,6 +10,38 @@
|
|
|
10
10
|
const LANG_KEY = "orch.lang";
|
|
11
11
|
// 字典:键 = 中文原文;值 = 英文译文。未命中即回落中文。
|
|
12
12
|
const EN = {
|
|
13
|
+
"工作区和任务": "Workspaces and tasks",
|
|
14
|
+
"{0} 个工作区 · {1} 个任务": "{0} workspaces · {1} tasks",
|
|
15
|
+
"返回工作区": "Back to workspaces",
|
|
16
|
+
"在此工作区新建任务": "New task in this workspace",
|
|
17
|
+
"未运行": "Not started",
|
|
18
|
+
"长目标": "Long-running goal",
|
|
19
|
+
"把需要多轮推进的工作固定下来,随时暂停或恢复。": "Keep multi-step work in view and pause or resume it at any time.",
|
|
20
|
+
"目标标题": "Goal title",
|
|
21
|
+
"目标说明": "Goal description",
|
|
22
|
+
"例如:逐步完善 CodeBee 桌面体验": "For example: Improve CodeBee's desktop experience",
|
|
23
|
+
"写下完成标准或背景(可选)": "Add context or completion criteria (optional)",
|
|
24
|
+
"创建长目标": "Create goal",
|
|
25
|
+
"目标进度": "Goal progress",
|
|
26
|
+
"已暂停": "Paused",
|
|
27
|
+
"恢复目标": "Resume goal",
|
|
28
|
+
"标记完成": "Mark complete",
|
|
29
|
+
"目标不存在": "Goal not found",
|
|
30
|
+
"无法读取长目标": "Could not load the goal",
|
|
31
|
+
"创建目标失败": "Could not create the goal",
|
|
32
|
+
"更新目标失败": "Could not update the goal",
|
|
33
|
+
"确定将这个长目标标记为完成吗?": "Mark this long-running goal as complete?",
|
|
34
|
+
"活动视图": "Activity view",
|
|
35
|
+
"活动": "Activity",
|
|
36
|
+
"打开活动视图": "Open activity view",
|
|
37
|
+
"关闭活动视图": "Close activity view",
|
|
38
|
+
"需要你处理": "Needs your attention",
|
|
39
|
+
"正在执行": "In progress",
|
|
40
|
+
"更早": "Earlier",
|
|
41
|
+
"全部已读": "Mark all read",
|
|
42
|
+
"将完成任务标为已读": "Mark completed runs as read",
|
|
43
|
+
"{0} 项待处理 · {1} 项完成未读": "{0} need attention · {1} completed unread",
|
|
44
|
+
"暂无活动,开始一个任务后会在这里显示进展。": "No activity yet. Start a task to see its progress here.",
|
|
13
45
|
"蜂巢视图控制": "Hive view controls",
|
|
14
46
|
"全屏": "Fullscreen",
|
|
15
47
|
"进入全屏": "Enter fullscreen",
|
|
@@ -50,6 +82,10 @@
|
|
|
50
82
|
// —— 短片段(拼接用)——
|
|
51
83
|
"检查中…": "Checking…",
|
|
52
84
|
"浏览器": "Browser",
|
|
85
|
+
"新标签页": "New tab",
|
|
86
|
+
"搜索网页,或输入网址": "Search the web or enter a URL",
|
|
87
|
+
"常用网站": "Shortcuts",
|
|
88
|
+
"任务面板": "Task panel",
|
|
53
89
|
"本地浏览器工作区": "Local browser workspace",
|
|
54
90
|
"工作上下文": "Work context",
|
|
55
91
|
"本地": "Local",
|
|
@@ -656,6 +692,46 @@
|
|
|
656
692
|
"皮肤": "Skins",
|
|
657
693
|
"手机连接": "Phone link",
|
|
658
694
|
"设置": "Settings",
|
|
695
|
+
"工作流": "Workflow",
|
|
696
|
+
"集成": "Integrations",
|
|
697
|
+
"智能体与模型": "Agents & models",
|
|
698
|
+
"知识与扩展": "Knowledge & extensions",
|
|
699
|
+
"用量与数据": "Usage & data",
|
|
700
|
+
"外观与软件": "Appearance & software",
|
|
701
|
+
"搜索设置和功能…": "Search settings and features…",
|
|
702
|
+
"搜索设置和功能": "Search settings and features",
|
|
703
|
+
"没有匹配的设置": "No matching settings",
|
|
704
|
+
"运行与工作区": "Runtime & workspace",
|
|
705
|
+
"工具与连接": "Tools & connections",
|
|
706
|
+
"配置 MCP、ACP 与本机命令授权。这些工具会以你的本机权限运行,请只连接可信服务。": "Configure MCP, ACP and local command authorization. These tools run with your local permissions; connect trusted services only.",
|
|
707
|
+
"权限": "permissions",
|
|
708
|
+
"命令授权": "Command authorization",
|
|
709
|
+
"通知": "Notifications",
|
|
710
|
+
"网站权限": "Site permissions",
|
|
711
|
+
"浏览记录和数据": "History and data",
|
|
712
|
+
"本地保存": "Stored locally",
|
|
713
|
+
"浏览记录": "Browsing history",
|
|
714
|
+
"搜索标题或网址": "Search titles or URLs",
|
|
715
|
+
"最多保存最近 500 条,仅存于本机。": "The latest 500 entries are stored on this device.",
|
|
716
|
+
"清理浏览数据": "Clear browsing data",
|
|
717
|
+
"清理浏览数据?": "Clear browsing data?",
|
|
718
|
+
"将删除本机保存的浏览记录、标签页和网站权限配置,并恢复为一个新标签页。": "This removes locally saved history, tabs, and site permission settings, then opens a new tab.",
|
|
719
|
+
"由于网页运行在隔离框架中,CodeBee 无法读取或清理系统浏览器中的 Cookie、缓存及下载记录。": "Pages run in an isolated frame, so CodeBee cannot access or clear system browser cookies, cache, or downloads.",
|
|
720
|
+
"清理本地数据": "Clear local data",
|
|
721
|
+
"按站点控制": "Per-site controls",
|
|
722
|
+
"当前没有打开网站": "No site is open",
|
|
723
|
+
"摄像头": "Camera",
|
|
724
|
+
"允许此站点请求摄像头访问": "Allow this site to request camera access",
|
|
725
|
+
"麦克风": "Microphone",
|
|
726
|
+
"允许此站点请求麦克风访问": "Allow this site to request microphone access",
|
|
727
|
+
"位置信息": "Location",
|
|
728
|
+
"允许此站点请求设备位置": "Allow this site to request device location",
|
|
729
|
+
"读取剪贴板": "Read clipboard",
|
|
730
|
+
"允许此站点请求读取剪贴板": "Allow this site to request clipboard access",
|
|
731
|
+
"网站权限仅对 HTTPS 网站开放。": "Site permissions are available for HTTPS websites only.",
|
|
732
|
+
"只允许此网站请求所选权限;浏览器仍会在网站实际使用时显示系统授权提示。": "Only this site can request the selected permissions. The browser will still ask when the site uses them.",
|
|
733
|
+
"没有匹配的浏览记录": "No matching history",
|
|
734
|
+
"暂无浏览记录": "No browsing history",
|
|
659
735
|
"多端控制权": "Multi-device control",
|
|
660
736
|
"时间范围": "Time range",
|
|
661
737
|
"明暗模式": "Theme",
|
|
@@ -812,7 +888,6 @@
|
|
|
812
888
|
"逐项目标核对:": "Beat-by-beat check: ",
|
|
813
889
|
"第": "Ch.",
|
|
814
890
|
"章": "",
|
|
815
|
-
" 轮": " rounds",
|
|
816
891
|
"只重写上一遍未过线的章节,已过线章节沿用成稿与评分": "Rewrites only the chapters that missed the threshold last run; passing chapters keep their drafts and scores.",
|
|
817
892
|
"再跑一次这个任务;连载任务会断点续跑,已完成章节不重写": "Run this task again; serial novels resume from the breakpoint — finished chapters are kept.",
|
|
818
893
|
"✎ 编辑重试": "✎ Edit & retry",
|
|
@@ -2608,6 +2683,7 @@
|
|
|
2608
2683
|
"找不到任务工作目录": "Task working directory not found",
|
|
2609
2684
|
"列文件失败:": "Failed to list files: ",
|
|
2610
2685
|
"工作目录里没有 .md 章稿": "No .md chapter drafts in the working directory",
|
|
2686
|
+
"暂无待发章节,已发布和已存草稿的章节已排除": "No pending chapters; published and saved-draft chapters are excluded",
|
|
2611
2687
|
"发布全部待发": "Publish all pending",
|
|
2612
2688
|
"按章号顺序逐章填稿(人工模式每点一次填一章,浏览器里提交后再点发下一章)": "Fills chapters one by one in order (manual mode: each click fills one; submit in the browser, then click again for the next)",
|
|
2613
2689
|
"将从最靠前的待发章节开始填稿(共 {0} 章待发)。本轮只填一章并停在表单页,由你在浏览器里确认提交;提交后再点一次即发下一章。护栏(每日上限/连续失败暂停)生效。继续?": "Fills from the earliest pending chapter ({0} pending). Each round fills one chapter and stops at the form page for you to confirm submission in the browser; click again to send the next one. Guardrails (daily cap / consecutive-failure pause) apply. Continue?",
|
|
@@ -2637,6 +2713,26 @@
|
|
|
2637
2713
|
"开始直发": "Start auto-publish",
|
|
2638
2714
|
"清空": "Clear",
|
|
2639
2715
|
"待发 ": "Pending ",
|
|
2716
|
+
"按卷计划自动分卷": "Volumes assigned automatically",
|
|
2717
|
+
"选择章节并发布": "Select chapters to publish",
|
|
2718
|
+
"已过审 · 可直发": "Approved · ready",
|
|
2719
|
+
"已过审 · 发布受限": "Approved · blocked",
|
|
2720
|
+
"审稿未通过": "Review failed",
|
|
2721
|
+
"已修改 · 需重审": "Changed · re-review",
|
|
2722
|
+
"待审或评审不足": "Awaiting valid review",
|
|
2723
|
+
"只选可直发": "Select ready",
|
|
2724
|
+
"章节审核状态说明": "Chapter review status legend",
|
|
2725
|
+
"仅选择单章审稿和发布门禁均已通过的章节": "Select only chapters with approved reviews and passed release gates",
|
|
2726
|
+
"已发布至第 {0} 章": "Published through chapter {0}",
|
|
2727
|
+
"(已发布至 {0} 章)": " (published through chapter {0})",
|
|
2728
|
+
"下一章:第 {0} 章": "Next: chapter {0}",
|
|
2729
|
+
"可直发 {0} 章 · 单章过审 {1} 章": "Ready: {0} · chapter reviews approved: {1}",
|
|
2730
|
+
"已存草稿 {0} 章(未发布)": "{0} drafts saved (unpublished)",
|
|
2731
|
+
"当前没有可选择的待发章节": "No pending chapters are available to select",
|
|
2732
|
+
"发布需逐章确认,直发自动提交,草稿不上线。": "Publish requires confirmation per chapter; auto-publish submits directly; drafts stay unpublished.",
|
|
2733
|
+
"请先选择章节": "Select chapters first",
|
|
2734
|
+
"已选 {0}/{1} 章": "Selected {0}/{1} chapters",
|
|
2735
|
+
"第 {0} 章": "Chapter {0}",
|
|
2640
2736
|
" 章(按卷计划自动分卷)": " chapters (volumes auto-assigned per plan)",
|
|
2641
2737
|
"直发已启动,进度见发布台": "Auto-publish started — track progress in the publish panel",
|
|
2642
2738
|
"所选章节逐章自动提交(无人值守),护栏每日上限/连败退避照常生效": "Selected chapters submit automatically one by one (unattended); guards (daily cap / failure backoff) stay active",
|
|
@@ -3250,6 +3346,39 @@
|
|
|
3250
3346
|
"暂无可注入记忆": "No memory would be injected",
|
|
3251
3347
|
"已使用 {0}/{1} 字符": "Using {0}/{1} characters",
|
|
3252
3348
|
"项目记忆预览失败:": "Memory preview failed: ",
|
|
3349
|
+
"人工评审队列": "Human review queue",
|
|
3350
|
+
"新建评审请求": "New review request",
|
|
3351
|
+
"记录运行成果的人工复核与处理意见。渠道仅作记录;创建请求不会向外部渠道发送消息,批准也不会自动发布成果。": "Track manual reviews and feedback on run outputs. Channels are labels; requests do not send messages and approvals do not publish outputs automatically.",
|
|
3352
|
+
"评审状态": "Review status",
|
|
3353
|
+
"待处理": "Pending",
|
|
3354
|
+
"待分配": "Awaiting assignment",
|
|
3355
|
+
"评审中": "In review",
|
|
3356
|
+
"已批准": "Approved",
|
|
3357
|
+
"要求修改": "Changes requested",
|
|
3358
|
+
"分配评审人": "Assign reviewer",
|
|
3359
|
+
"批准评审": "Approve review",
|
|
3360
|
+
"关闭请求": "Close request",
|
|
3361
|
+
"标记过期": "Mark expired",
|
|
3362
|
+
"显示 {0} / {1} 条": "Showing {0} of {1}",
|
|
3363
|
+
"未填写评审摘要": "No review summary",
|
|
3364
|
+
"运行 ID": "Run ID",
|
|
3365
|
+
"评审渠道": "Review channel",
|
|
3366
|
+
"评审人": "Reviewer",
|
|
3367
|
+
"暂无匹配的评审请求": "No matching review requests",
|
|
3368
|
+
"正在加载评审请求…": "Loading review requests…",
|
|
3369
|
+
"评审数据格式不正确": "Invalid review data",
|
|
3370
|
+
"评审队列加载失败:": "Failed to load review queue: ",
|
|
3371
|
+
"评审摘要": "Review summary",
|
|
3372
|
+
"待评审文件(每行一个路径)": "Files to review (one path per line)",
|
|
3373
|
+
"创建请求": "Create request",
|
|
3374
|
+
"处理意见": "Feedback",
|
|
3375
|
+
"正在保存…": "Saving…",
|
|
3376
|
+
"评审请求已保存": "Review request saved",
|
|
3377
|
+
"评审请求保存失败:": "Failed to save review request: ",
|
|
3378
|
+
"请填写运行 ID": "Enter a run ID",
|
|
3379
|
+
"最多 200 个文件,每个路径最多 500 字符": "At most 200 files, with up to 500 characters per path",
|
|
3380
|
+
"请填写评审人": "Enter a reviewer",
|
|
3381
|
+
"请填写修改意见": "Enter change feedback",
|
|
3253
3382
|
};
|
|
3254
3383
|
|
|
3255
3384
|
// ---------- 工具 ----------
|