xuym-codex-collab 0.9.0 → 0.10.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/README.md +1 -1
- package/apps/onboarding/server.mjs +16 -3
- package/apps/onboarding/web/app.js +1 -1
- package/apps/onboarding/web/effects.css +47 -0
- package/apps/onboarding/web/effects.js +323 -0
- package/apps/onboarding/web/index.html +13 -7
- package/apps/onboarding/web/style.css +2 -7
- package/docs/releases/0.10.0.md +20 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ codex-collab setup
|
|
|
26
26
|
|
|
27
27
|
协作远程与业务基准分支自动识别;不要求 `origin/main`。有多个不同远程或默认分支不明确时,管理员选择一次。成员与新增机器人继承签名团队基准,各自可使用不同远程别名。每个新任务固定创建时的 commit,排队和重试期间不会漂移;管理员可在团队设置里为后续任务修改基准分支。
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
工作台首页按项目汇总,同一仓库的多个本机机器人共用一张项目卡片。打开项目后,所有团队机器人展示在同一列表里,自己的标记“我的”;本机已接入的机器人可继续配置或管理,其他机器人的职责来自签名团队记录。分支、诊断、重置和团队分配设置按需展开。首页以“让 Codex 组队工作”为入口:在企业微信群里连接你和同事的 Codex 会话,按角色分工、接力完成任务。界面提供纸白、夜色两种配色,以及从本机加载的 Three.js 星尘背景、轻微鼠标视差与短尾迹;触屏和系统减少动态效果设置下显示静态星点,页面进入后台时暂停动画。
|
|
30
30
|
|
|
31
31
|
同一个真人可以拥有多个 Agent,每个 Agent 使用独立机器人、签名身份、业务 clone 和 Codex 对话。管理员在项目页点击“添加我的机器人”,验证同一个企业微信账号后自动接入,无需下载包或填写包密码;给同事分配时仍使用加密接入包,同事可以领取多份。相同 userid 不会合并 Agent 权限,审核通知会指明应使用的机器人。
|
|
32
32
|
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
5
6
|
import { SetupError } from './errors.mjs';
|
|
6
7
|
import { SetupManager } from './manager.mjs';
|
|
7
|
-
const
|
|
8
|
+
const threeBuild = new URL('.', pathToFileURL(createRequire(import.meta.url).resolve('three')));
|
|
9
|
+
const assets = {
|
|
10
|
+
'/': ['index.html', 'text/html; charset=utf-8'],
|
|
11
|
+
'/favicon.svg': ['favicon.svg', 'image/svg+xml'],
|
|
12
|
+
'/app.js': ['app.js', 'text/javascript; charset=utf-8'],
|
|
13
|
+
'/style.css': ['style.css', 'text/css; charset=utf-8'],
|
|
14
|
+
'/effects.js': ['effects.js', 'text/javascript; charset=utf-8'],
|
|
15
|
+
'/effects.css': ['effects.css', 'text/css; charset=utf-8'],
|
|
16
|
+
'/vendor/three.module.js': [new URL('three.module.js', threeBuild), 'text/javascript; charset=utf-8'],
|
|
17
|
+
'/vendor/three.core.js': [new URL('three.core.js', threeBuild), 'text/javascript; charset=utf-8'],
|
|
18
|
+
};
|
|
8
19
|
const same = (a, b) => typeof a === 'string' && Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
9
20
|
export async function startSetupServer({ manager = new SetupManager(), port = 0 } = {}) {
|
|
10
21
|
const token = randomBytes(32).toString('hex'); const cookieName = `collab_${randomBytes(8).toString('hex')}`; let origin;
|
|
@@ -16,7 +27,9 @@ export async function startSetupServer({ manager = new SetupManager(), port = 0
|
|
|
16
27
|
if (req.headers.host !== new URL(origin).host) return json(403, { error: '本机访问地址无效。' });
|
|
17
28
|
const url = new URL(req.url, origin);
|
|
18
29
|
if (req.method === 'GET' && assets[url.pathname]) {
|
|
19
|
-
const [file, type] = assets[url.pathname];
|
|
30
|
+
const [file, type] = assets[url.pathname];
|
|
31
|
+
const content = await readFile(file instanceof URL ? file : new URL(`./web/${file}`, import.meta.url));
|
|
32
|
+
res.writeHead(200, { 'Content-Type': type }); res.end(content); return;
|
|
20
33
|
}
|
|
21
34
|
if (req.method !== 'GET' && (req.headers.origin !== origin || req.headers['content-type']?.split(';')[0] !== 'application/json')) return json(403, { error: '请通过本机向导操作。' });
|
|
22
35
|
if (req.headers.origin && req.headers.origin !== origin) return json(403, { error: '访问来源无效。' });
|
|
@@ -606,7 +606,7 @@ $('pending-requests').onclick = () => { $('admin-tools').open = true; $('request
|
|
|
606
606
|
function initializeAppearance() {
|
|
607
607
|
if (!document.body) return;
|
|
608
608
|
function apply(theme) {
|
|
609
|
-
if (!['day', '
|
|
609
|
+
if (!['day', 'night'].includes(theme)) theme = 'day';
|
|
610
610
|
document.body.dataset.theme = theme;
|
|
611
611
|
for (const choice of document.querySelectorAll('[data-theme-choice]')) choice.setAttribute('aria-pressed', String(choice.dataset.themeChoice === theme));
|
|
612
612
|
try { localStorage.setItem('codex-collab-theme', theme); } catch {}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/* Three.js star field. Content stays on its own layer with native hit testing. */
|
|
2
|
+
body {
|
|
3
|
+
isolation: isolate;
|
|
4
|
+
--fx-blue: 158 181 222;
|
|
5
|
+
--fx-peach: 237 207 175;
|
|
6
|
+
--fx-line: 102 142 133;
|
|
7
|
+
--fx-cursor: 145 133 187;
|
|
8
|
+
}
|
|
9
|
+
body[data-theme="night"] {
|
|
10
|
+
--fx-blue: 87 111 177;
|
|
11
|
+
--fx-peach: 141 119 83;
|
|
12
|
+
--fx-line: 139 186 176;
|
|
13
|
+
--fx-cursor: 198 186 232;
|
|
14
|
+
}
|
|
15
|
+
#ambient { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: -1; contain: strict; }
|
|
16
|
+
.ambient-light { position: absolute; width: 82vw; height: 88vh; pointer-events: none; will-change: transform; }
|
|
17
|
+
.light-one { left: -31vw; top: -13vh; background: radial-gradient(ellipse, rgb(var(--fx-blue) / .24), transparent 65%); animation: drift-one 24s ease-in-out infinite alternate; }
|
|
18
|
+
.light-two { right: -31vw; top: 1vh; background: radial-gradient(ellipse, rgb(170 153 202 / .19), transparent 68%); animation: drift-two 29s ease-in-out infinite alternate; }
|
|
19
|
+
.light-three { width: 100vw; height: 65vh; left: -6vw; bottom: -33vh; opacity: .28; background: radial-gradient(ellipse, rgb(var(--fx-peach) / .43), transparent 69%); animation: drift-three 31s ease-in-out infinite alternate; }
|
|
20
|
+
body[data-theme="night"] .light-one { opacity: .72; }
|
|
21
|
+
body[data-theme="night"] .light-two { opacity: .8; }
|
|
22
|
+
html:not([data-webgl="ready"]) #ambient::after { content: ""; position: absolute; left: 0; top: 0; width: 2px; height: 2px; border-radius: 50%; color: rgb(var(--fx-line) / .52); box-shadow: 9vw 18vh, 24vw 9vh, 37vw 28vh, 68vw 13vh, 88vw 24vh, 14vw 47vh, 31vw 66vh, 75vw 55vh, 91vw 68vh, 5vw 79vh, 22vw 86vh, 42vw 91vh, 62vw 81vh, 81vw 92vh, 96vw 43vh, 54vw 7vh, 72vw 34vh, 17vw 32vh, 85vw 8vh; }
|
|
23
|
+
#ambient-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
24
|
+
#cursor-follower { position: fixed; top: 0; left: 0; width: 30px; height: 30px; border: 1px solid rgb(var(--fx-cursor) / .35); border-radius: 50%; pointer-events: none; opacity: 0; z-index: 5; will-change: transform; transition: width 180ms, height 180ms, border-color 180ms; }
|
|
25
|
+
#cursor-follower i { display: block; position: absolute; width: 4px; height: 4px; background: rgb(var(--fx-cursor) / .6); border-radius: 50%; right: -2px; top: 12px; }
|
|
26
|
+
.masthead, main, .demo-footer { position: relative; z-index: 1; }
|
|
27
|
+
.effect-note { display: flex; align-items: center; gap: 8px; color: var(--muted); min-width: 0; font-size: 10px; }
|
|
28
|
+
.demo-only { font-size: 8px; opacity: .6; }
|
|
29
|
+
html[data-effects-paused="true"] .ambient-light { animation-play-state: paused; }
|
|
30
|
+
html[data-effects-static="true"] #cursor-follower { visibility: hidden; }
|
|
31
|
+
@keyframes drift-one { from { transform: translate3d(0, 0, 0) rotate(-12deg); } to { transform: translate3d(13vw, 8vh, 0) rotate(14deg); } }
|
|
32
|
+
@keyframes drift-two { from { transform: translate3d(0, 0, 0) rotate(8deg); } to { transform: translate3d(-10vw, 13vh, 0) rotate(-15deg); } }
|
|
33
|
+
@keyframes drift-three { from { transform: translate3d(0, 0, 0) scale(1); } to { transform: translate3d(12vw, -6vh, 0) scale(1.08); } }
|
|
34
|
+
@media (max-width: 600px) {
|
|
35
|
+
.masthead { gap: 10px; align-items: center; }
|
|
36
|
+
.effect-note { font-size: 9px; flex-wrap: wrap; justify-content: center; gap: 5px; }
|
|
37
|
+
.demo-only { flex-basis: 100%; text-align: center; margin: 0; }
|
|
38
|
+
.ambient-light { width: 125vw; }
|
|
39
|
+
}
|
|
40
|
+
@media (hover: none), (pointer: coarse) {
|
|
41
|
+
#cursor-follower { display: none; }
|
|
42
|
+
.ambient-light { animation: none; will-change: auto; }
|
|
43
|
+
}
|
|
44
|
+
@media (prefers-reduced-motion: reduce) {
|
|
45
|
+
.ambient-light { animation: none; will-change: auto; }
|
|
46
|
+
#cursor-follower { display: none; }
|
|
47
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BufferGeometry, Color, Float32BufferAttribute, NormalBlending,
|
|
3
|
+
PerspectiveCamera, Points, Scene, ShaderMaterial, Vector2, WebGLRenderer,
|
|
4
|
+
} from '/vendor/three.module.js';
|
|
5
|
+
|
|
6
|
+
// Stars and pointer dust each render in a single draw call.
|
|
7
|
+
const canvas = document.getElementById('ambient-canvas');
|
|
8
|
+
const follower = document.getElementById('cursor-follower');
|
|
9
|
+
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
|
|
10
|
+
const finePointer = matchMedia('(hover: hover) and (pointer: fine)');
|
|
11
|
+
const palettes = {
|
|
12
|
+
day: ['#728f9e', '#9a8bb4', '#a2b7c5', .88],
|
|
13
|
+
night: ['#8fbdbb', '#b5b0e2', '#c5d6e0', .88],
|
|
14
|
+
};
|
|
15
|
+
const pointer = { x: 0, y: 0, ringX: 0, ringY: 0, fieldX: 0, fieldY: 0, alpha: 0, active: false, hover: false, down: false };
|
|
16
|
+
const trailCount = 44;
|
|
17
|
+
const trailPositions = new Float32Array(trailCount * 3);
|
|
18
|
+
const trailBirths = new Float32Array(trailCount).fill(-100);
|
|
19
|
+
const trailSeeds = new Float32Array(trailCount);
|
|
20
|
+
let width = innerWidth, height = innerHeight, frame = 0, lastFrame = 0, elapsed = 0;
|
|
21
|
+
let renderer, scene, camera, stars, dust, starGeometry, dustGeometry;
|
|
22
|
+
let initialized = false, lost = false, trailIndex = 0, lastTrail = 0, lastTrailX = 0, lastTrailY = 0;
|
|
23
|
+
let density = 1;
|
|
24
|
+
const isStatic = () => reducedMotion.matches || !finePointer.matches;
|
|
25
|
+
const uniforms = {
|
|
26
|
+
uTime: { value: 0 },
|
|
27
|
+
uPixelRatio: { value: 1 },
|
|
28
|
+
uSpan: { value: new Vector2(1, 1) },
|
|
29
|
+
uPointer: { value: new Vector2() },
|
|
30
|
+
uParallax: { value: new Vector2() },
|
|
31
|
+
uPointerAlpha: { value: 0 },
|
|
32
|
+
uColorA: { value: new Color() },
|
|
33
|
+
uColorB: { value: new Color() },
|
|
34
|
+
uColorC: { value: new Color() },
|
|
35
|
+
uOpacity: { value: 1 },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const starVertex = `
|
|
39
|
+
uniform float uTime;
|
|
40
|
+
uniform float uPixelRatio;
|
|
41
|
+
uniform vec2 uSpan;
|
|
42
|
+
uniform vec2 uPointer;
|
|
43
|
+
uniform vec2 uParallax;
|
|
44
|
+
uniform float uPointerAlpha;
|
|
45
|
+
attribute float aSize;
|
|
46
|
+
attribute float aPhase;
|
|
47
|
+
attribute float aTone;
|
|
48
|
+
varying float vTone;
|
|
49
|
+
varying float vAlpha;
|
|
50
|
+
varying float vSparkle;
|
|
51
|
+
void main() {
|
|
52
|
+
float depth = (8.0 - position.z) / 8.0;
|
|
53
|
+
vec3 p = vec3(position.xy * uSpan * depth, position.z);
|
|
54
|
+
p.x += sin(uTime * .085 + aPhase) * .065;
|
|
55
|
+
p.y += cos(uTime * .065 + aPhase * 1.3) * .045;
|
|
56
|
+
// Farther layers respond less, creating subtle depth without camera sway.
|
|
57
|
+
p.xy += uParallax * (.025 + (position.z + 4.0) * .012);
|
|
58
|
+
vec2 delta = uPointer * depth - p.xy;
|
|
59
|
+
float proximity = exp(-dot(delta, delta) / (1.1 * depth * depth));
|
|
60
|
+
p.xy += delta * proximity * .09 * uPointerAlpha;
|
|
61
|
+
vec4 mv = modelViewMatrix * vec4(p, 1.0);
|
|
62
|
+
vec4 projected = projectionMatrix * mv;
|
|
63
|
+
gl_Position = projected;
|
|
64
|
+
gl_PointSize = clamp(aSize * uPixelRatio * (8.0 / -mv.z), 2.0, 28.0 * uPixelRatio);
|
|
65
|
+
vec2 screen = projected.xy / projected.w;
|
|
66
|
+
float quietCenter = 1.0 - exp(-pow(screen.x * 2.2, 2.0) - pow(screen.y * 1.6, 2.0)) * .45;
|
|
67
|
+
vAlpha = (.65 + sin(uTime * .42 + aPhase) * .16) * quietCenter;
|
|
68
|
+
vTone = aTone;
|
|
69
|
+
vSparkle = step(.83, aTone);
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
72
|
+
const starFragment = `
|
|
73
|
+
uniform vec3 uColorA;
|
|
74
|
+
uniform vec3 uColorB;
|
|
75
|
+
uniform vec3 uColorC;
|
|
76
|
+
uniform float uOpacity;
|
|
77
|
+
varying float vTone;
|
|
78
|
+
varying float vAlpha;
|
|
79
|
+
varying float vSparkle;
|
|
80
|
+
void main() {
|
|
81
|
+
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
|
82
|
+
float radius = dot(p, p);
|
|
83
|
+
if (radius > 1.0) discard;
|
|
84
|
+
float core = exp(-radius * 80.0);
|
|
85
|
+
float halo = exp(-radius * 6.0) * .17;
|
|
86
|
+
float cross = (exp(-abs(p.x) * 65.0) + exp(-abs(p.y) * 65.0)) * exp(-radius * 7.0) * .18 * vSparkle;
|
|
87
|
+
float alpha = (core + halo + cross) * vAlpha * uOpacity;
|
|
88
|
+
vec3 color = mix(uColorA, uColorB, smoothstep(.1, .8, vTone));
|
|
89
|
+
color = mix(color, uColorC, vSparkle * .55);
|
|
90
|
+
gl_FragColor = vec4(color, alpha);
|
|
91
|
+
#include <colorspace_fragment>
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
const dustVertex = `
|
|
95
|
+
uniform float uTime;
|
|
96
|
+
uniform float uPixelRatio;
|
|
97
|
+
attribute float aBirth;
|
|
98
|
+
attribute float aSeed;
|
|
99
|
+
varying float vAlpha;
|
|
100
|
+
void main() {
|
|
101
|
+
float age = uTime - aBirth;
|
|
102
|
+
float life = clamp(1.0 - age / 1.25, 0.0, 1.0);
|
|
103
|
+
vec3 p = position;
|
|
104
|
+
p.x += sin(aSeed * 25.0) * age * .042;
|
|
105
|
+
p.y += age * (.035 + aSeed * .035);
|
|
106
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
|
107
|
+
gl_PointSize = (6.0 + aSeed * 7.0) * uPixelRatio * (.7 + life * .3);
|
|
108
|
+
vAlpha = life * life * .65;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
const dustFragment = `
|
|
112
|
+
uniform vec3 uColorB;
|
|
113
|
+
uniform vec3 uColorC;
|
|
114
|
+
uniform float uOpacity;
|
|
115
|
+
varying float vAlpha;
|
|
116
|
+
void main() {
|
|
117
|
+
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
|
118
|
+
float radius = dot(p, p);
|
|
119
|
+
if (radius > 1.0) discard;
|
|
120
|
+
float softPoint = exp(-radius * 35.0) + exp(-radius * 5.0) * .12;
|
|
121
|
+
gl_FragColor = vec4(mix(uColorB, uColorC, .4), softPoint * vAlpha * uOpacity);
|
|
122
|
+
#include <colorspace_fragment>
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
function stop() {
|
|
127
|
+
cancelAnimationFrame(frame);
|
|
128
|
+
frame = 0;
|
|
129
|
+
lastFrame = 0;
|
|
130
|
+
}
|
|
131
|
+
function schedule() {
|
|
132
|
+
if (renderer && !frame && !document.hidden && !isStatic() && !lost) frame = requestAnimationFrame(tick);
|
|
133
|
+
}
|
|
134
|
+
function createScene() {
|
|
135
|
+
if (initialized) return;
|
|
136
|
+
initialized = true;
|
|
137
|
+
try {
|
|
138
|
+
renderer = new WebGLRenderer({ canvas, alpha: true, antialias: false, powerPreference: 'low-power' });
|
|
139
|
+
renderer.setClearColor(0x000000, 0);
|
|
140
|
+
scene = new Scene();
|
|
141
|
+
camera = new PerspectiveCamera(42, width / height, .1, 30);
|
|
142
|
+
camera.position.z = 8;
|
|
143
|
+
starGeometry = new BufferGeometry();
|
|
144
|
+
const positions = [], sizes = [], phases = [], tones = [];
|
|
145
|
+
let seed = 173;
|
|
146
|
+
const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; };
|
|
147
|
+
for (let i = 0; i < 148; i++) {
|
|
148
|
+
positions.push((random() - .5) * 1.18, (random() - .5) * 1.18, random() * 6.5 - 4);
|
|
149
|
+
sizes.push(6 + Math.pow(random(), 2) * 13);
|
|
150
|
+
phases.push(random() * Math.PI * 2);
|
|
151
|
+
tones.push(random());
|
|
152
|
+
}
|
|
153
|
+
starGeometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
154
|
+
starGeometry.setAttribute('aSize', new Float32BufferAttribute(sizes, 1));
|
|
155
|
+
starGeometry.setAttribute('aPhase', new Float32BufferAttribute(phases, 1));
|
|
156
|
+
starGeometry.setAttribute('aTone', new Float32BufferAttribute(tones, 1));
|
|
157
|
+
const settings = { uniforms, transparent: true, depthTest: false, depthWrite: false, blending: NormalBlending };
|
|
158
|
+
stars = new Points(starGeometry, new ShaderMaterial({ ...settings, vertexShader: starVertex, fragmentShader: starFragment }));
|
|
159
|
+
stars.frustumCulled = false;
|
|
160
|
+
dustGeometry = new BufferGeometry();
|
|
161
|
+
dustGeometry.setAttribute('position', new Float32BufferAttribute(trailPositions, 3));
|
|
162
|
+
dustGeometry.setAttribute('aBirth', new Float32BufferAttribute(trailBirths, 1));
|
|
163
|
+
for (let i = 0; i < trailCount; i++) trailSeeds[i] = random();
|
|
164
|
+
dustGeometry.setAttribute('aSeed', new Float32BufferAttribute(trailSeeds, 1));
|
|
165
|
+
dust = new Points(dustGeometry, new ShaderMaterial({ ...settings, vertexShader: dustVertex, fragmentShader: dustFragment }));
|
|
166
|
+
dust.frustumCulled = false;
|
|
167
|
+
scene.add(stars, dust);
|
|
168
|
+
document.documentElement.dataset.webgl = 'ready';
|
|
169
|
+
resizeRenderer();
|
|
170
|
+
} catch {
|
|
171
|
+
disposeScene();
|
|
172
|
+
// CSS keeps a quiet star field if WebGL is unavailable.
|
|
173
|
+
document.documentElement.dataset.webgl = 'fallback';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function disposeScene() {
|
|
177
|
+
starGeometry?.dispose(); dustGeometry?.dispose();
|
|
178
|
+
stars?.material.dispose(); dust?.material.dispose();
|
|
179
|
+
renderer?.dispose();
|
|
180
|
+
renderer = null;
|
|
181
|
+
}
|
|
182
|
+
function resizeRenderer() {
|
|
183
|
+
if (!renderer) return;
|
|
184
|
+
density = Math.min(devicePixelRatio || 1, 1.5, 2048 / Math.max(width, height));
|
|
185
|
+
renderer.setPixelRatio(density);
|
|
186
|
+
renderer.setSize(width, height, false);
|
|
187
|
+
camera.aspect = width / height;
|
|
188
|
+
camera.updateProjectionMatrix();
|
|
189
|
+
const spanY = 2 * Math.tan(42 * Math.PI / 360) * 8;
|
|
190
|
+
uniforms.uSpan.value.set(spanY * camera.aspect, spanY);
|
|
191
|
+
uniforms.uPixelRatio.value = density;
|
|
192
|
+
starGeometry.setDrawRange(0, Math.min(148, Math.max(65, Math.round(width * height / 9500))));
|
|
193
|
+
}
|
|
194
|
+
function render() {
|
|
195
|
+
if (renderer && !lost) renderer.render(scene, camera);
|
|
196
|
+
}
|
|
197
|
+
function updatePalette() {
|
|
198
|
+
const colors = palettes[document.body.dataset.theme] || palettes.day;
|
|
199
|
+
uniforms.uColorA.value.set(colors[0]);
|
|
200
|
+
uniforms.uColorB.value.set(colors[1]);
|
|
201
|
+
uniforms.uColorC.value.set(colors[2]);
|
|
202
|
+
uniforms.uOpacity.value = colors[3];
|
|
203
|
+
render();
|
|
204
|
+
}
|
|
205
|
+
function clearDust() {
|
|
206
|
+
trailBirths.fill(-100);
|
|
207
|
+
if (dustGeometry) {
|
|
208
|
+
dustGeometry.getAttribute('aBirth').array.set(trailBirths);
|
|
209
|
+
dustGeometry.getAttribute('aBirth').needsUpdate = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function updatePreferences() {
|
|
213
|
+
stop();
|
|
214
|
+
const staticMode = isStatic();
|
|
215
|
+
document.documentElement.dataset.effectsPaused = String(document.hidden);
|
|
216
|
+
document.documentElement.dataset.effectsStatic = String(staticMode);
|
|
217
|
+
// Touch/reduced-motion starts with CSS alone; no WebGL context or render loop.
|
|
218
|
+
if (!staticMode) createScene();
|
|
219
|
+
if (staticMode) {
|
|
220
|
+
pointer.alpha = 0;
|
|
221
|
+
uniforms.uPointerAlpha.value = 0;
|
|
222
|
+
clearDust();
|
|
223
|
+
render();
|
|
224
|
+
}
|
|
225
|
+
updatePalette();
|
|
226
|
+
schedule();
|
|
227
|
+
}
|
|
228
|
+
function follow(dt) {
|
|
229
|
+
const ease = 1 - Math.exp(-dt / 90);
|
|
230
|
+
const slow = 1 - Math.exp(-dt / 300);
|
|
231
|
+
pointer.ringX += (pointer.x - pointer.ringX) * ease;
|
|
232
|
+
pointer.ringY += (pointer.y - pointer.ringY) * ease;
|
|
233
|
+
pointer.fieldX += (pointer.x - pointer.fieldX) * slow;
|
|
234
|
+
pointer.fieldY += (pointer.y - pointer.fieldY) * slow;
|
|
235
|
+
pointer.alpha += ((pointer.active ? 1 : 0) - pointer.alpha) * ease;
|
|
236
|
+
const scale = pointer.down ? .72 : pointer.hover ? 1.35 : 1;
|
|
237
|
+
follower.style.transform = `translate3d(${pointer.ringX - 15}px, ${pointer.ringY - 15}px, 0) scale(${scale})`;
|
|
238
|
+
follower.style.opacity = String(pointer.alpha * (pointer.hover ? .35 : .8));
|
|
239
|
+
const span = uniforms.uSpan.value;
|
|
240
|
+
uniforms.uPointer.value.set((pointer.fieldX / width - .5) * span.x, (.5 - pointer.fieldY / height) * span.y);
|
|
241
|
+
uniforms.uPointerAlpha.value = pointer.alpha;
|
|
242
|
+
const targetX = pointer.active ? (pointer.x / width - .5) * span.x : 0;
|
|
243
|
+
const targetY = pointer.active ? (.5 - pointer.y / height) * span.y : 0;
|
|
244
|
+
const parallax = uniforms.uParallax.value;
|
|
245
|
+
parallax.x += (targetX - parallax.x) * slow;
|
|
246
|
+
parallax.y += (targetY - parallax.y) * slow;
|
|
247
|
+
}
|
|
248
|
+
function addDust(now) {
|
|
249
|
+
if (!dustGeometry || now - lastTrail < 28 || Math.hypot(pointer.x - lastTrailX, pointer.y - lastTrailY) < 5) return;
|
|
250
|
+
const span = uniforms.uSpan.value;
|
|
251
|
+
const index = trailIndex++ % trailCount;
|
|
252
|
+
const position = dustGeometry.getAttribute('position');
|
|
253
|
+
position.setXYZ(index, (pointer.x / width - .5) * span.x, (.5 - pointer.y / height) * span.y, 0);
|
|
254
|
+
position.needsUpdate = true;
|
|
255
|
+
const births = dustGeometry.getAttribute('aBirth');
|
|
256
|
+
births.setX(index, elapsed);
|
|
257
|
+
births.needsUpdate = true;
|
|
258
|
+
lastTrail = now; lastTrailX = pointer.x; lastTrailY = pointer.y;
|
|
259
|
+
}
|
|
260
|
+
function tick(now) {
|
|
261
|
+
frame = 0;
|
|
262
|
+
if (document.hidden || isStatic() || lost) return;
|
|
263
|
+
if (lastFrame && now - lastFrame < 31) { schedule(); return; }
|
|
264
|
+
const dt = lastFrame ? Math.min(now - lastFrame, 64) : 33;
|
|
265
|
+
lastFrame = now;
|
|
266
|
+
elapsed += dt / 1000;
|
|
267
|
+
uniforms.uTime.value = elapsed;
|
|
268
|
+
follow(dt);
|
|
269
|
+
render();
|
|
270
|
+
schedule();
|
|
271
|
+
}
|
|
272
|
+
function resize() {
|
|
273
|
+
width = innerWidth; height = innerHeight;
|
|
274
|
+
resizeRenderer();
|
|
275
|
+
clearDust();
|
|
276
|
+
render();
|
|
277
|
+
}
|
|
278
|
+
window.addEventListener('pointermove', event => {
|
|
279
|
+
if (isStatic() || event.pointerType === 'touch') return;
|
|
280
|
+
pointer.x = event.clientX; pointer.y = event.clientY;
|
|
281
|
+
if (!pointer.active) {
|
|
282
|
+
pointer.ringX = pointer.fieldX = pointer.x;
|
|
283
|
+
pointer.ringY = pointer.fieldY = pointer.y;
|
|
284
|
+
lastTrailX = pointer.x; lastTrailY = pointer.y;
|
|
285
|
+
}
|
|
286
|
+
pointer.active = true;
|
|
287
|
+
pointer.hover = !!event.target.closest('a, button, input, textarea, select, summary');
|
|
288
|
+
addDust(performance.now());
|
|
289
|
+
schedule();
|
|
290
|
+
}, { passive: true });
|
|
291
|
+
window.addEventListener('pointerdown', event => {
|
|
292
|
+
if (isStatic() || event.pointerType === 'touch') return;
|
|
293
|
+
pointer.down = true; schedule();
|
|
294
|
+
}, { passive: true });
|
|
295
|
+
window.addEventListener('pointerup', () => { pointer.down = false; schedule(); }, { passive: true });
|
|
296
|
+
const leave = () => { pointer.active = false; pointer.down = false; schedule(); };
|
|
297
|
+
document.documentElement.addEventListener('pointerleave', leave);
|
|
298
|
+
window.addEventListener('blur', leave);
|
|
299
|
+
window.addEventListener('resize', resize, { passive: true });
|
|
300
|
+
document.addEventListener('visibilitychange', () => {
|
|
301
|
+
document.documentElement.dataset.effectsPaused = String(document.hidden);
|
|
302
|
+
if (document.hidden) stop(); else schedule();
|
|
303
|
+
});
|
|
304
|
+
canvas.addEventListener('webglcontextlost', event => {
|
|
305
|
+
event.preventDefault(); lost = true; stop();
|
|
306
|
+
document.documentElement.dataset.webgl = 'fallback';
|
|
307
|
+
});
|
|
308
|
+
canvas.addEventListener('webglcontextrestored', () => {
|
|
309
|
+
lost = false;
|
|
310
|
+
document.documentElement.dataset.webgl = 'ready';
|
|
311
|
+
clearDust(); render(); schedule();
|
|
312
|
+
});
|
|
313
|
+
new MutationObserver(updatePalette).observe(document.body, { attributes: true, attributeFilter: ['data-theme'] });
|
|
314
|
+
reducedMotion.addEventListener('change', updatePreferences);
|
|
315
|
+
finePointer.addEventListener('change', updatePreferences);
|
|
316
|
+
window.addEventListener('pagehide', () => stop());
|
|
317
|
+
window.addEventListener('pageshow', () => schedule());
|
|
318
|
+
// Previous comparison URLs now open this single star field.
|
|
319
|
+
const url = new URL(location.href);
|
|
320
|
+
if (url.searchParams.has('effect')) {
|
|
321
|
+
url.searchParams.delete('effect'); history.replaceState(null, '', url);
|
|
322
|
+
}
|
|
323
|
+
updatePreferences();
|
|
@@ -4,11 +4,19 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<meta name="color-scheme" content="light dark">
|
|
7
|
-
<title>Codex Collab ·
|
|
7
|
+
<title>Codex Collab · 企业微信协作向导</title>
|
|
8
8
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
9
9
|
<link rel="stylesheet" href="/style.css">
|
|
10
|
+
<link rel="stylesheet" href="/effects.css">
|
|
10
11
|
</head>
|
|
11
|
-
<body>
|
|
12
|
+
<body data-theme="day">
|
|
13
|
+
<div id="ambient" aria-hidden="true">
|
|
14
|
+
<div class="ambient-light light-one"></div>
|
|
15
|
+
<div class="ambient-light light-two"></div>
|
|
16
|
+
<div class="ambient-light light-three"></div>
|
|
17
|
+
<canvas id="ambient-canvas"></canvas>
|
|
18
|
+
</div>
|
|
19
|
+
<div id="cursor-follower" aria-hidden="true"><i></i></div>
|
|
12
20
|
<header class="masthead">
|
|
13
21
|
<a class="brand" href="/" data-view="home" aria-label="Codex Collab 首页">
|
|
14
22
|
<span class="brand-mark" aria-hidden="true">⌘</span>Codex Collab</a>
|
|
@@ -31,8 +39,8 @@
|
|
|
31
39
|
</span>
|
|
32
40
|
</div>
|
|
33
41
|
<div class="intro">
|
|
34
|
-
<h1 tabindex="-1"
|
|
35
|
-
<p
|
|
42
|
+
<h1 tabindex="-1">让 Codex 组队工作。</h1>
|
|
43
|
+
<p>在企业微信群里,连接你和同事的 Codex 会话,按角色分工、接力完成任务。</p>
|
|
36
44
|
</div>
|
|
37
45
|
<div id="profiles" class="project-list" aria-label="我的项目">
|
|
38
46
|
</div>
|
|
@@ -659,9 +667,6 @@
|
|
|
659
667
|
<button class="theme-choice" data-theme-choice="day" aria-pressed="true">
|
|
660
668
|
<i aria-hidden="true">
|
|
661
669
|
</i>纸白</button>
|
|
662
|
-
<button class="theme-choice" data-theme-choice="studio" aria-pressed="false">
|
|
663
|
-
<i aria-hidden="true">
|
|
664
|
-
</i>雾绿</button>
|
|
665
670
|
<button class="theme-choice" data-theme-choice="night" aria-pressed="false">
|
|
666
671
|
<i aria-hidden="true">
|
|
667
672
|
</i>夜色</button>
|
|
@@ -669,5 +674,6 @@
|
|
|
669
674
|
</footer>
|
|
670
675
|
<script src="/app.js" defer>
|
|
671
676
|
</script>
|
|
677
|
+
<script src="/effects.js" type="module"></script>
|
|
672
678
|
</body>
|
|
673
679
|
</html>
|
|
@@ -6,7 +6,6 @@ body {
|
|
|
6
6
|
--accent: #355d49; --on-accent: #fff; --wash: #eaf0e6; --hover: #f1f3eb; --shadow: #355d4909;
|
|
7
7
|
color-scheme: light; margin: 0; background: var(--bg); color: var(--ink); min-height: 100dvh; display: flex; flex-direction: column;
|
|
8
8
|
}
|
|
9
|
-
body[data-theme="studio"] { --bg: #f0f4ef; --surface: #f8fbf6; --ink: #2c4338; --muted: #809181; --line: #d8e2d6; --accent: #426c50; --wash: #e0eadd; --hover: #e9f0e5; }
|
|
10
9
|
body[data-theme="night"] { color-scheme: dark; --bg: #131b19; --surface: #1a2420; --ink: #e9eee3; --muted: #82958b; --line: #2e3d34; --accent: #c3d5b0; --on-accent: #23372a; --wash: #2b3b2f; --hover: #1d2b22; --shadow: #0000000b; }
|
|
11
10
|
button, input { font: inherit; }
|
|
12
11
|
a { color: inherit; text-decoration: none; }
|
|
@@ -18,8 +17,8 @@ h1, p { margin: 0; }
|
|
|
18
17
|
h1:focus { outline: none; }
|
|
19
18
|
.icon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
20
19
|
.masthead { display: flex; align-items: center; justify-content: space-between; padding: 30px 44px; gap: 16px; }
|
|
21
|
-
.brand { display: inline-flex; align-items: center; gap:
|
|
22
|
-
.brand-mark { display: inline-grid; place-items: center; width:
|
|
20
|
+
.brand { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; letter-spacing: -.4px; font-weight: 550; }
|
|
21
|
+
.brand-mark { display: inline-grid; place-items: center; width: 24px; height: 27px; font: 24px/1 sans-serif; color: var(--accent); }
|
|
23
22
|
.preview-label { font-size: 10px; color: var(--muted); letter-spacing: 1px; }
|
|
24
23
|
main { width: 100%; flex: 1; padding: 50px 24px 66px; display: flex; justify-content: center; align-items: flex-start; }
|
|
25
24
|
.scene { width: 100%; max-width: 536px; }
|
|
@@ -70,7 +69,6 @@ body[data-theme="night"] .project-icon.sand { color: #cfbc9b; background: #3c372
|
|
|
70
69
|
.theme-picker button[aria-pressed="true"] { color: var(--ink); opacity: 1; }
|
|
71
70
|
.theme-picker button:hover { opacity: 1; }
|
|
72
71
|
.theme-picker i { width: 7px; height: 7px; border-radius: 50%; border: 1px solid #c4cbbd; background: #faf9f6; }
|
|
73
|
-
.theme-picker [data-theme-choice="studio"] i { background: #bac9b5; border-color: #a8bba4; }
|
|
74
72
|
.theme-picker [data-theme-choice="night"] i { background: #344a3e; border-color: #4c6353; }
|
|
75
73
|
.back-link { display: inline-flex; align-items: center; gap: 7px; font-size: 11px; color: var(--muted); padding: 6px 0; margin: 0 0 24px; }
|
|
76
74
|
.back-link:hover { color: var(--accent); }
|
|
@@ -156,8 +154,6 @@ html[data-page-hidden="true"] *, html[data-page-hidden="true"] *::before, html[d
|
|
|
156
154
|
@media (min-height: 950px) and (min-width: 700px) { main { padding-top: 80px; } }
|
|
157
155
|
@media (max-width: 600px) {
|
|
158
156
|
.masthead { padding: 24px; }
|
|
159
|
-
.brand { font-size: 13px; gap: 8px; }
|
|
160
|
-
.brand-mark { font-size: 24px; width: 24px; }
|
|
161
157
|
.preview-label { font-size: 9px; }
|
|
162
158
|
main { padding: 38px 24px 52px; }
|
|
163
159
|
h1 { font-size: 30px; letter-spacing: -1.2px; }
|
|
@@ -216,7 +212,6 @@ html[data-page-hidden="true"] *, html[data-page-hidden="true"] *::before, html[d
|
|
|
216
212
|
|
|
217
213
|
/* Live onboarding uses the same quiet layout as the design preview. */
|
|
218
214
|
body { --muted: #6e7d73; }
|
|
219
|
-
body[data-theme="studio"] { --muted: #687d6d; }
|
|
220
215
|
body[data-theme="night"] { --muted: #a1b1a7; }
|
|
221
216
|
main { flex-direction: column; justify-content: flex-start; align-items: center; }
|
|
222
217
|
main > section { width: 100%; }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 0.10.0:让 Codex 组队工作
|
|
2
|
+
|
|
3
|
+
将已确认的星尘设计接入正式本机向导。首页说明两种协作方式:在企业微信群里连接自己的多个 Codex 会话,或与同事的 Codex 会话按角色分工、接力完成任务。
|
|
4
|
+
|
|
5
|
+
- 首页标题更新为“让 Codex 组队工作。”,桌面与移动端统一使用紧凑 Logo。
|
|
6
|
+
- 配色保留纸白、夜色;旧的雾绿选择自动回落到纸白。
|
|
7
|
+
- 增加 Three.js 星尘背景、轻微鼠标视差、星点牵引与短尾迹,移除鼠标光晕、手动停用按钮和背景说明文案。
|
|
8
|
+
- Three.js 固定版本,模块由本机向导提供,无需 CDN 或额外配置。背景不拦截点击,保留系统鼠标。
|
|
9
|
+
- 触屏与系统减少动态效果设置下使用静态星点,不创建 WebGL 上下文。无 WebGL 或上下文丢失时自动降级;页面隐藏时暂停动画。星点与尾迹各一次绘制,动画约 30 帧,像素比最多 1.5,画布最长边最多 2048 像素。
|
|
10
|
+
|
|
11
|
+
## 升级
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --global xuym-codex-collab@0.10.0
|
|
15
|
+
codex-collab setup
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
已有项目配置和协作记录无需迁移。升级后重启已运行的本机工作台进程,再打开向导,才能加载新代码;已有机器人连接按原有配置使用。
|
|
19
|
+
|
|
20
|
+
本次通过 216 项自动化测试,并在浏览器验证正式页面及临时目录安装后的 npm 包:项目归属、主题切换保留表单、320–3840 像素布局、后台暂停与恢复、触屏与减少动态效果、WebGL 降级与恢复均通过;安装后的 CLI 能启动独立工作台并提供完整背景资源。不新增真实企业微信群或跨机器验收结论。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xuym-codex-collab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Git-backed collaboration tools for Codex teams, with MCP, WeCom, and local runners.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@modelcontextprotocol/server": "2.3.1",
|
|
58
58
|
"@openai/codex-sdk": "0.161.0",
|
|
59
59
|
"@wecom/aibot-node-sdk": "1.0.7",
|
|
60
|
+
"three": "0.186.1",
|
|
60
61
|
"zod": "^4.6.5"
|
|
61
62
|
}
|
|
62
63
|
}
|