privateboard 0.1.38 → 0.1.40
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/dist/boot.js +323 -50
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +323 -50
- package/dist/cli.js.map +1 -1
- package/dist/server.js +200 -40
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/public/__avatar3d_test.html +156 -0
- package/public/agent-profile.css +3 -9
- package/public/agent-profile.js +85 -32
- package/public/app.js +469 -526
- package/public/avatar-3d-snap.js +205 -0
- package/public/avatar-3d.js +792 -0
- package/public/avatar-customizer.html +274 -0
- package/public/avatar3d-editor.css +240 -0
- package/public/avatar3d-editor.js +481 -0
- package/public/avatars/3d/chair.png +0 -0
- package/public/avatars/3d/first-principles.png +0 -0
- package/public/avatars/3d/historian.png +0 -0
- package/public/avatars/3d/long-horizon.png +0 -0
- package/public/avatars/3d/phenomenologist.png +0 -0
- package/public/avatars/3d/socrates.png +0 -0
- package/public/avatars/3d/user-empathy.png +0 -0
- package/public/avatars/3d/value-investor.png +0 -0
- package/public/core-avatars.js +86 -0
- package/public/home-3d-loader.js +15 -4
- package/public/home-3d-mock.js +18 -7
- package/public/home.html +78 -16
- package/public/i18n.js +12 -4
- package/public/icons/avatar_1779855104027.glb +0 -0
- package/public/icons/logo.png +0 -0
- package/public/icons/new-style.glb +0 -0
- package/public/icons/new-style2.glb +0 -0
- package/public/icons/new-style3.glb +0 -0
- package/public/icons/new-style4.glb +0 -0
- package/public/icons/new-style5.glb +0 -0
- package/public/icons/office.glb +0 -0
- package/public/icons/stuff.glb +0 -0
- package/public/index.html +148 -121
- package/public/new-agent.js +46 -20
- package/public/office-viewer.html +340 -0
- package/public/stuff-viewer.html +330 -0
- package/public/thread.css +8 -8
- package/public/user-settings.css +10 -13
- package/public/user-settings.js +86 -78
- package/public/vendor/BufferGeometryUtils.js +1434 -0
- package/public/vendor/DRACOLoader.js +739 -0
- package/public/vendor/GLTFLoader.js +4860 -0
- package/public/vendor/RoomEnvironment.js +185 -0
- package/public/vendor/SkeletonUtils.js +496 -0
- package/public/vendor/draco/draco_decoder.js +34 -0
- package/public/vendor/draco/draco_decoder.wasm +0 -0
- package/public/vendor/draco/draco_encoder.js +33 -0
- package/public/vendor/draco/draco_wasm_wrapper.js +117 -0
- package/public/vendor/meshopt_decoder.module.js +196 -0
- package/public/voice-3d-banner.js +12 -0
- package/public/voice-3d.js +1407 -432
- package/public/voice-replay.js +21 -0
- package/public/avatar-skill.js +0 -629
- package/public/icons/folded-sidebar.png +0 -0
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * Single source of truth for the app version.\n *\n * Imported by `cli.ts` (CLI banner / `--version`), `server.ts` (the\n * `/health` payload + the `/api/version` endpoint), and bundled into\n * the frontend via the version endpoint. Bump alongside `package.json`\n * on every release — the existing `npm version <patch|minor|major>`\n * + commit pattern updates package.json automatically; this file\n * needs the matching manual bump.\n *\n * If two strings drift (bumped one but not the other), the wrong\n * number ends up surfaced in the user-facing footer or banner. Keep\n * this file as the canonical source — every callsite reads from here.\n */\nexport const VERSION = \"0.1.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * Single source of truth for the app version.\n *\n * Imported by `cli.ts` (CLI banner / `--version`), `server.ts` (the\n * `/health` payload + the `/api/version` endpoint), and bundled into\n * the frontend via the version endpoint. Bump alongside `package.json`\n * on every release — the existing `npm version <patch|minor|major>`\n * + commit pattern updates package.json automatically; this file\n * needs the matching manual bump.\n *\n * If two strings drift (bumped one but not the other), the wrong\n * number ends up surfaced in the user-facing footer or banner. Keep\n * this file as the canonical source — every callsite reads from here.\n */\nexport const VERSION = \"0.1.40\";\n"],"mappings":";;;AAcO,IAAM,UAAU;","names":[]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html><head><meta charset="utf-8"><title>avatar3d test</title>
|
|
3
|
+
<style>
|
|
4
|
+
html,body{margin:0;height:100%;background:#1a1a1a;overflow:hidden}
|
|
5
|
+
#status{position:fixed;top:8px;left:10px;color:#9fe;font:12px/1.5 monospace;z-index:9;white-space:pre-wrap;max-width:92vw}
|
|
6
|
+
#panel{position:fixed;top:8px;right:10px;z-index:9;background:rgba(0,0,0,.55);border:1px solid #444;border-radius:8px;padding:10px 12px;color:#cde;font:11px/1.6 monospace;width:230px}
|
|
7
|
+
#panel label{display:block;margin:6px 0 2px}
|
|
8
|
+
#panel input[type=range]{width:100%}
|
|
9
|
+
#panel b{color:#9fe}
|
|
10
|
+
#copy{margin-top:8px;width:100%;padding:5px;background:#2a3340;color:#cde;border:1px solid #556;border-radius:5px;cursor:pointer;font:11px monospace}
|
|
11
|
+
</style>
|
|
12
|
+
</head><body>
|
|
13
|
+
<div id="status">booting…</div>
|
|
14
|
+
<!-- Live brightness controls · drag to taste, then read the values off
|
|
15
|
+
the readout (or click "复制参数") and tell me — I'll bake them into
|
|
16
|
+
the room. -->
|
|
17
|
+
<div id="panel">
|
|
18
|
+
<label>曝光 exposure · <b id="vExp">0.70</b></label>
|
|
19
|
+
<input type="range" id="exp" min="0.2" max="1.4" step="0.01" value="0.70">
|
|
20
|
+
<label>环境强度 envIntensity · <b id="vEnv">0.35</b></label>
|
|
21
|
+
<input type="range" id="env" min="0" max="1.5" step="0.01" value="0.35">
|
|
22
|
+
<label>主光 key · <b id="vKey">0.70</b></label>
|
|
23
|
+
<input type="range" id="key" min="0" max="3" step="0.01" value="0.70">
|
|
24
|
+
<button id="copy">复制参数</button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Classic script FIRST · catches module/import/protocol failures that a
|
|
28
|
+
top-level `import` would otherwise swallow into the console only. -->
|
|
29
|
+
<script>
|
|
30
|
+
window.__setStatus = function (msg, isErr) {
|
|
31
|
+
var el = document.getElementById("status");
|
|
32
|
+
if (!el) return;
|
|
33
|
+
el.textContent = msg;
|
|
34
|
+
el.style.color = isErr ? "#f88" : "#9fe";
|
|
35
|
+
};
|
|
36
|
+
if (location.protocol === "file:") {
|
|
37
|
+
window.__setStatus("⚠ 你是用 file:// 打开的 — 模块和 GLB 都加载不了。\n请在【运行中的 app】里访问 http://localhost:<端口>/__avatar3d_test.html", true);
|
|
38
|
+
}
|
|
39
|
+
window.addEventListener("error", function (e) {
|
|
40
|
+
// Resource (script/module) load failures arrive here as ErrorEvents.
|
|
41
|
+
var what = e.message || (e.target && (e.target.src || e.target.href)) || "unknown";
|
|
42
|
+
window.__setStatus("JS/资源错误: " + what, true);
|
|
43
|
+
}, true);
|
|
44
|
+
window.addEventListener("unhandledrejection", function (e) {
|
|
45
|
+
var r = e.reason; window.__setStatus("未处理的 Promise 拒绝: " + ((r && r.message) || r), true);
|
|
46
|
+
});
|
|
47
|
+
setTimeout(function () {
|
|
48
|
+
var el = document.getElementById("status");
|
|
49
|
+
if (el && /booting|importing|loading|building/i.test(el.textContent)) {
|
|
50
|
+
window.__setStatus(el.textContent + "\n(>6s 仍未完成 — 打开 DevTools Console 看具体报错)", true);
|
|
51
|
+
}
|
|
52
|
+
}, 6000);
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<script type="module">
|
|
56
|
+
const S = window.__setStatus;
|
|
57
|
+
(async () => {
|
|
58
|
+
try {
|
|
59
|
+
// Step 0 · is the GLB even reachable on this server? (isolates
|
|
60
|
+
// "asset not served" from "module failed to load".)
|
|
61
|
+
S("检查 GLB 可达性…");
|
|
62
|
+
const GLB = "/icons/avatar_1779855104027.glb";
|
|
63
|
+
const head = await fetch(GLB, { method: "GET", headers: { Range: "bytes=0-0" } }).catch((e) => ({ ok: false, status: "fetch失败 " + e.message }));
|
|
64
|
+
if (!head.ok && head.status !== 206 && head.status !== 200) {
|
|
65
|
+
S("GLB 拿不到: " + GLB + " → " + head.status + "(文件没被服务?)", true);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
S("加载 three…");
|
|
70
|
+
const THREE = await import("/vendor/three.module.min.js");
|
|
71
|
+
S("加载 OrbitControls…");
|
|
72
|
+
const { OrbitControls } = await import("/vendor/OrbitControls.js");
|
|
73
|
+
const { RoomEnvironment } = await import("/vendor/RoomEnvironment.js");
|
|
74
|
+
S("加载 avatar-3d skill…");
|
|
75
|
+
const av = await import("/avatar-3d.js");
|
|
76
|
+
|
|
77
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
|
78
|
+
renderer.setSize(innerWidth, innerHeight);
|
|
79
|
+
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
|
80
|
+
renderer.shadowMap.enabled = true;
|
|
81
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
82
|
+
// ACES tone mapping + sRGB output · the standard "make PBR look
|
|
83
|
+
// good" pair. Without tone mapping the env reflections clip / look
|
|
84
|
+
// flat; with it skin + hair get that soft filmic sheen.
|
|
85
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
86
|
+
renderer.toneMappingExposure = 0.7;
|
|
87
|
+
document.body.appendChild(renderer.domElement);
|
|
88
|
+
|
|
89
|
+
const scene = new THREE.Scene();
|
|
90
|
+
scene.background = new THREE.Color(0x222428);
|
|
91
|
+
const camera = new THREE.PerspectiveCamera(35, innerWidth / innerHeight, 0.1, 100);
|
|
92
|
+
camera.position.set(1.6, 1.7, 3.0);
|
|
93
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
94
|
+
controls.target.set(0, 0.9, 0);
|
|
95
|
+
controls.update();
|
|
96
|
+
|
|
97
|
+
// Image-based lighting · a PMREM-filtered procedural RoomEnvironment
|
|
98
|
+
// is what gives the materials real reflections / gloss (no HDRI
|
|
99
|
+
// file needed). This is the single biggest lever for "光泽 / 质感".
|
|
100
|
+
const pmrem = new THREE.PMREMGenerator(renderer);
|
|
101
|
+
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
102
|
+
// RoomEnvironment is studio-bright · dim its contribution so it
|
|
103
|
+
// lights + reflects without washing the skin out. Keeps the gloss
|
|
104
|
+
// shape (reflections) while pulling overall exposure down.
|
|
105
|
+
scene.environmentIntensity = 0.35;
|
|
106
|
+
|
|
107
|
+
// The env map already provides ambient fill, so direct lights stay
|
|
108
|
+
// modest · a strong key + rim on top of IBL blew the faces out to
|
|
109
|
+
// pure white. One soft key for shape + a faint rim for separation.
|
|
110
|
+
scene.add(new THREE.HemisphereLight(0xffffff, 0x33384a, 0.18));
|
|
111
|
+
const dir = new THREE.DirectionalLight(0xffffff, 0.7);
|
|
112
|
+
dir.position.set(3, 6, 4); dir.castShadow = true; scene.add(dir);
|
|
113
|
+
const rim = new THREE.DirectionalLight(0xbfd4ff, 0.3);
|
|
114
|
+
rim.position.set(-4, 3, -3); scene.add(rim);
|
|
115
|
+
const ground = new THREE.Mesh(
|
|
116
|
+
new THREE.CircleGeometry(2.4, 48),
|
|
117
|
+
new THREE.MeshStandardMaterial({ color: 0x33363c, roughness: 1 }),
|
|
118
|
+
);
|
|
119
|
+
ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground);
|
|
120
|
+
|
|
121
|
+
renderer.setAnimationLoop(() => { controls.update(); renderer.render(scene, camera); });
|
|
122
|
+
|
|
123
|
+
// Live brightness sliders → renderer / scene / key-light.
|
|
124
|
+
const $ = (id) => document.getElementById(id);
|
|
125
|
+
const wire = (id, vid, fn, fmt) => {
|
|
126
|
+
const el = $(id); const lab = $(vid);
|
|
127
|
+
const upd = () => { const v = parseFloat(el.value); fn(v); lab.textContent = fmt(v); };
|
|
128
|
+
el.addEventListener("input", upd); upd();
|
|
129
|
+
};
|
|
130
|
+
wire("exp", "vExp", (v) => { renderer.toneMappingExposure = v; }, (v) => v.toFixed(2));
|
|
131
|
+
wire("env", "vEnv", (v) => { scene.environmentIntensity = v; }, (v) => v.toFixed(2));
|
|
132
|
+
wire("key", "vKey", (v) => { dir.intensity = v; }, (v) => v.toFixed(2));
|
|
133
|
+
$("copy").addEventListener("click", () => {
|
|
134
|
+
const t = `exposure=${$("exp").value} envIntensity=${$("env").value} key=${$("key").value}`;
|
|
135
|
+
navigator.clipboard && navigator.clipboard.writeText(t);
|
|
136
|
+
$("copy").textContent = "已复制: " + t;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
S("下载 + 解析 GLB…");
|
|
140
|
+
await av.loadAvatar3D(GLB);
|
|
141
|
+
|
|
142
|
+
S("生成 avatar 实例…");
|
|
143
|
+
const seeds = ["yang-tianzhen", "socrates", "first-principles"];
|
|
144
|
+
seeds.forEach((seed, i) => {
|
|
145
|
+
const a = av.buildAvatar3D(seed, { height: 1.7 });
|
|
146
|
+
a.position.x = (i - 1) * 1.1;
|
|
147
|
+
a.rotation.y = 0;
|
|
148
|
+
scene.add(a);
|
|
149
|
+
});
|
|
150
|
+
S("✓ 加载完成 · " + seeds.length + " 个 avatar(拖动可旋转)");
|
|
151
|
+
} catch (e) {
|
|
152
|
+
S("ERROR: " + (e && e.stack ? e.stack : (e && e.message) || e), true);
|
|
153
|
+
}
|
|
154
|
+
})();
|
|
155
|
+
</script>
|
|
156
|
+
</body></html>
|
package/public/agent-profile.css
CHANGED
|
@@ -1639,13 +1639,6 @@
|
|
|
1639
1639
|
height: 76px;
|
|
1640
1640
|
margin-top: -46px;
|
|
1641
1641
|
margin-bottom: 0;
|
|
1642
|
-
border-radius: 50%;
|
|
1643
|
-
background: var(--panel-2);
|
|
1644
|
-
/* Ring color follows the page bg so the avatar reads as separated
|
|
1645
|
-
from the cover gradient on every theme · in light themes the ring
|
|
1646
|
-
is white against the gray cover; in dark themes the ring is the
|
|
1647
|
-
near-black bg against the panel-toned cover. */
|
|
1648
|
-
border: 4px solid var(--bg);
|
|
1649
1642
|
overflow: hidden;
|
|
1650
1643
|
display: flex;
|
|
1651
1644
|
align-items: center;
|
|
@@ -1657,8 +1650,9 @@
|
|
|
1657
1650
|
width: 100%;
|
|
1658
1651
|
height: 100%;
|
|
1659
1652
|
object-fit: cover;
|
|
1660
|
-
|
|
1661
|
-
|
|
1653
|
+
/* The 3D-avatar portrait is a smooth downscaled PNG · keep it smooth
|
|
1654
|
+
(pixelated would render it blocky at this size). */
|
|
1655
|
+
image-rendering: auto;
|
|
1662
1656
|
}
|
|
1663
1657
|
/* Text column · sits to the right of the avatar in the same row,
|
|
1664
1658
|
so name + meta read as one tight identifier block. */
|
package/public/agent-profile.js
CHANGED
|
@@ -760,39 +760,81 @@
|
|
|
760
760
|
return tag.toUpperCase().slice(0, 8);
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
-
/* Per-agent rules
|
|
763
|
+
/* Per-agent rules · PERSISTED SERVER-SIDE (agent.userRules) so the
|
|
764
|
+
orchestrator can inject them into the director's turn prompt. (They
|
|
765
|
+
used to be localStorage-only / "visual" — which is why a rule like
|
|
766
|
+
"不要谈及范冰冰" had zero effect: it never reached the model.)
|
|
767
|
+
A per-slug working copy backs the inputs for snappy editing; changes
|
|
768
|
+
debounce-flush to PATCH /api/agents/:id. */
|
|
764
769
|
const RULES_MAX = 5;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
770
|
+
const _rules = Object.create(null); // slug -> string[] working copy
|
|
771
|
+
const _rulesTimer = Object.create(null); // slug -> debounce timer
|
|
772
|
+
function _legacyRulesKey(slug) { return "boardroom.agent.rules." + slug; }
|
|
773
|
+
function _liveAgentFor(slug) {
|
|
774
|
+
return (window.app && window.app.agentsById) ? window.app.agentsById[slug] : null;
|
|
775
|
+
}
|
|
776
|
+
// Seed the working copy once per slug: prefer the server value
|
|
777
|
+
// (agent.userRules); else migrate any legacy localStorage rules up to
|
|
778
|
+
// the server so a user who set rules in the old "visual-only" era
|
|
779
|
+
// doesn't lose them (and they start actually working).
|
|
780
|
+
function seedRules(slug) {
|
|
781
|
+
if (_rules[slug]) return _rules[slug];
|
|
782
|
+
const live = _liveAgentFor(slug);
|
|
783
|
+
let arr = (live && Array.isArray(live.userRules)) ? live.userRules.slice() : [];
|
|
784
|
+
if (arr.length === 0) {
|
|
785
|
+
try {
|
|
786
|
+
const raw = localStorage.getItem(_legacyRulesKey(slug));
|
|
787
|
+
if (raw) {
|
|
788
|
+
const a = JSON.parse(raw);
|
|
789
|
+
if (Array.isArray(a)) {
|
|
790
|
+
const legacy = a.map((x) => String(x).trim()).filter((x) => x.length > 0);
|
|
791
|
+
if (legacy.length > 0) { arr = legacy; _rules[slug] = arr; persistRules(slug); }
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
} catch (e) { /* */ }
|
|
795
|
+
}
|
|
796
|
+
_rules[slug] = arr;
|
|
797
|
+
return _rules[slug];
|
|
798
|
+
}
|
|
799
|
+
function rulesForAgent(slug) { return seedRules(slug); }
|
|
800
|
+
function _cleanRules(slug) {
|
|
801
|
+
return (_rules[slug] || []).map((x) => String(x).trim()).filter((x) => x.length > 0).slice(0, RULES_MAX);
|
|
802
|
+
}
|
|
803
|
+
function persistRules(slug) {
|
|
804
|
+
const arr = _cleanRules(slug);
|
|
805
|
+
const live = _liveAgentFor(slug);
|
|
806
|
+
if (live) live.userRules = arr.slice(); // optimistic
|
|
807
|
+
fetch("/api/agents/" + encodeURIComponent(slug), {
|
|
808
|
+
method: "PATCH",
|
|
809
|
+
headers: { "content-type": "application/json" },
|
|
810
|
+
body: JSON.stringify({ userRules: arr }),
|
|
811
|
+
}).then((r) => (r.ok ? r.json() : null)).then((updated) => {
|
|
812
|
+
if (updated && Array.isArray(updated.userRules) && live) {
|
|
813
|
+
live.userRules = updated.userRules.slice();
|
|
772
814
|
}
|
|
773
|
-
|
|
774
|
-
|
|
815
|
+
try { localStorage.removeItem(_legacyRulesKey(slug)); } catch (e) { /* */ }
|
|
816
|
+
}).catch(() => { /* offline · working copy keeps the edit */ });
|
|
775
817
|
}
|
|
776
|
-
function
|
|
777
|
-
|
|
818
|
+
function persistRulesSoon(slug) {
|
|
819
|
+
if (_rulesTimer[slug]) clearTimeout(_rulesTimer[slug]);
|
|
820
|
+
_rulesTimer[slug] = setTimeout(() => { _rulesTimer[slug] = null; persistRules(slug); }, 600);
|
|
778
821
|
}
|
|
779
822
|
function addRuleFor(slug) {
|
|
780
|
-
const rules =
|
|
823
|
+
const rules = seedRules(slug);
|
|
781
824
|
if (rules.length >= RULES_MAX) return;
|
|
782
|
-
rules.push("");
|
|
783
|
-
setRulesFor(slug, rules);
|
|
825
|
+
rules.push(""); // empty rows aren't persisted until typed into
|
|
784
826
|
}
|
|
785
827
|
function setRuleAt(slug, idx, body) {
|
|
786
|
-
const rules =
|
|
828
|
+
const rules = seedRules(slug);
|
|
787
829
|
if (idx < 0 || idx >= rules.length) return;
|
|
788
830
|
rules[idx] = body;
|
|
789
|
-
|
|
831
|
+
persistRulesSoon(slug);
|
|
790
832
|
}
|
|
791
833
|
function removeRuleFor(slug, idx) {
|
|
792
|
-
const rules =
|
|
834
|
+
const rules = seedRules(slug);
|
|
793
835
|
if (idx < 0 || idx >= rules.length) return;
|
|
794
836
|
rules.splice(idx, 1);
|
|
795
|
-
|
|
837
|
+
persistRules(slug); // immediate on remove
|
|
796
838
|
}
|
|
797
839
|
function repaintProfileRules(slug) {
|
|
798
840
|
const card = document.querySelector(`.ap-card[data-ap-card-slug="${slug}"]`);
|
|
@@ -1123,8 +1165,9 @@
|
|
|
1123
1165
|
/** Render the RULES block · editable list of numbered constraints.
|
|
1124
1166
|
* Mirrors the new-agent overlay UX: each row is a numbered input
|
|
1125
1167
|
* with a trailing remove button; an "add rule" button below the
|
|
1126
|
-
* list (hidden when the cap of 5 is reached).
|
|
1127
|
-
*
|
|
1168
|
+
* list (hidden when the cap of 5 is reached). Mutations persist
|
|
1169
|
+
* server-side via PATCH /api/agents/:id (see setRuleAt / removeRuleFor)
|
|
1170
|
+
* so the orchestrator injects them into the director's prompt. */
|
|
1128
1171
|
function renderRulesBlock(slug) {
|
|
1129
1172
|
return `<div class="ap-rules-block" data-ap-rules-block data-slug="${escape(slug)}">${renderRulesInner(slug)}</div>`;
|
|
1130
1173
|
}
|
|
@@ -2402,6 +2445,11 @@
|
|
|
2402
2445
|
<span class="ap-id-menu-mark">◆</span>
|
|
2403
2446
|
<span>Regenerate 8-bit avatar</span>
|
|
2404
2447
|
</button>`);
|
|
2448
|
+
parts.push(`
|
|
2449
|
+
<button type="button" class="ap-id-menu-item" data-ap-menu-action="edit-avatar3d">
|
|
2450
|
+
<span class="ap-id-menu-mark">◈</span>
|
|
2451
|
+
<span>Customize 3D avatar</span>
|
|
2452
|
+
</button>`);
|
|
2405
2453
|
}
|
|
2406
2454
|
// Persona MD download · only present for Full-mode agents (those
|
|
2407
2455
|
// built via the deep persona-builder pipeline). Their `personaSpec`
|
|
@@ -2440,17 +2488,19 @@
|
|
|
2440
2488
|
if (el) el.remove();
|
|
2441
2489
|
}
|
|
2442
2490
|
|
|
2443
|
-
/**
|
|
2491
|
+
/** Render a fresh 3D voxel portrait and persist it as the agent's
|
|
2444
2492
|
* avatar. Updates the live store so subsequent renders use the
|
|
2445
|
-
* new image, then repaints the profile in place.
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2493
|
+
* new image, then repaints the profile in place. Uses the shared
|
|
2494
|
+
* Avatar3DSnap helper (same pipeline the agent-profile capture
|
|
2495
|
+
* and home / new-agent flows go through) — no more 8-bit SVG.
|
|
2496
|
+
* Seeded directors fall back to a localStorage override (the
|
|
2497
|
+
* server only stores user-created agents). */
|
|
2448
2498
|
async function regenerateProfileAvatar(slug) {
|
|
2449
|
-
const
|
|
2450
|
-
if (!
|
|
2451
|
-
const seed =
|
|
2452
|
-
const
|
|
2453
|
-
|
|
2499
|
+
const snap = window.Avatar3DSnap;
|
|
2500
|
+
if (!snap || typeof snap.generate !== "function") return;
|
|
2501
|
+
const seed = snap.randomSeed();
|
|
2502
|
+
const dataUrl = await snap.generate(seed);
|
|
2503
|
+
if (!dataUrl) return;
|
|
2454
2504
|
const live = window.app && window.app.agentsById ? window.app.agentsById[slug] : null;
|
|
2455
2505
|
if (live) {
|
|
2456
2506
|
try {
|
|
@@ -4358,6 +4408,9 @@
|
|
|
4358
4408
|
e.preventDefault();
|
|
4359
4409
|
closeProfileIdMenu();
|
|
4360
4410
|
if (action === "regen-avatar" && slug) regenerateProfileAvatar(slug);
|
|
4411
|
+
if (action === "edit-avatar3d" && slug && typeof window.openAvatar3DEditor === "function") {
|
|
4412
|
+
window.openAvatar3DEditor(slug);
|
|
4413
|
+
}
|
|
4361
4414
|
if (action === "delete" && slug && window.app && typeof window.app.deleteAgent === "function") {
|
|
4362
4415
|
// deleteAgent handles confirm + DELETE call + closes the
|
|
4363
4416
|
// profile + refreshes the sidebar. No-op for seed/chair
|
|
@@ -5050,8 +5103,8 @@
|
|
|
5050
5103
|
}
|
|
5051
5104
|
});
|
|
5052
5105
|
|
|
5053
|
-
// Rules · persist edits as the user types
|
|
5054
|
-
//
|
|
5106
|
+
// Rules · persist edits as the user types · setRuleAt debounce-
|
|
5107
|
+
// flushes to PATCH /api/agents/:id so the orchestrator picks them up.
|
|
5055
5108
|
document.addEventListener("input", (e) => {
|
|
5056
5109
|
const ri = e.target.closest("[data-ap-rule-input]");
|
|
5057
5110
|
if (!ri) return;
|