@clowder-ai/companion 0.1.0-alpha.6 → 0.1.0-alpha.8
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/NOTICE +7 -2
- package/README.md +10 -3
- package/manifest.json +2 -2
- package/package.json +1 -1
- package/plugin.yaml +2 -2
- package/renderer/index.html +22 -1
- package/renderer/living-body.mjs +84 -0
- package/renderer/living-edge.mjs +10 -0
- package/renderer/pet-motion.mjs +60 -9
- package/renderer/skins/xianxian-living/clips/carry.webm +0 -0
- package/renderer/skins/xianxian-living/clips/peek.webm +0 -0
- package/renderer/skins/xianxian-living/clips/pounce.webm +0 -0
- package/renderer/skins/xianxian-living/clips/sleep.webm +0 -0
- package/renderer/skins/xianxian-living/clips/think.webm +0 -0
- package/renderer/skins/xianxian-living/clips/trot.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wait.webm +0 -0
- package/renderer/skins/xianxian-living/clips/wake.webm +0 -0
- package/renderer/skins/xianxian-living/layers/body.webp +0 -0
- package/renderer/skins/xianxian-living/layers/closed.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/earR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/head.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/irisR.webp +0 -0
- package/renderer/skins/xianxian-living/layers/meta.json +88 -0
- package/renderer/skins/xianxian-living/layers/mouth.webp +0 -0
- package/renderer/skins/xianxian-living/layers/scared.webp +0 -0
- package/renderer/skins/xianxian-living/layers/tail.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteL.webp +0 -0
- package/renderer/skins/xianxian-living/layers/whiteR.webp +0 -0
- package/renderer/style.css +35 -3
- package/renderer/surface.mjs +32 -6
- package/source-lock.json +92 -1
package/NOTICE
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
See source-lock.json for
|
|
1
|
+
The original companion atlases and theme tokens originate in Clowder AI (MIT).
|
|
2
|
+
See source-lock.json for their exact public source revision and content hashes.
|
|
3
3
|
The legacy ragdoll-v1 and yarn-ball stills are AI-assisted derivatives of the
|
|
4
4
|
listed public reference, restoring the complete cat silhouette and transparent
|
|
5
5
|
background. Their reference and resulting hashes are recorded separately.
|
|
6
|
+
The living Xianxian layers and clips are F317 assets: CVO-generated source video,
|
|
7
|
+
keyframes by codex-astra, and deterministic extraction/alignment by opus55.
|
|
8
|
+
source-lock.json pins the 20 bundled distributable files, their geometry file,
|
|
9
|
+
and the source package manifest digest. Source video and private workspace paths
|
|
10
|
+
are not bundled.
|
|
6
11
|
The conversation transport and controls continue the F317 companion implementation
|
|
7
12
|
by the Clowder AI contributors. No private runtime data or pet metadata is included.
|
package/README.md
CHANGED
|
@@ -17,15 +17,22 @@ No second transcript database, credentials or selectable Host identity live here
|
|
|
17
17
|
|
|
18
18
|
## Host integration
|
|
19
19
|
|
|
20
|
-
This surface requires the additive desktop bridge **1.
|
|
20
|
+
This surface requires the additive desktop bridge **1.3.0**. The Host computes
|
|
21
21
|
placement within the display work area, keeps the cat as its stable anchor, and
|
|
22
22
|
owns bounded drag gestures and transparent-area hit testing. Older 1.0-only
|
|
23
23
|
Hosts reject this manifest before installation. Media admission and capture
|
|
24
24
|
remain in the trusted Host; this package receives neither streams nor provider
|
|
25
25
|
SDP. Hiding releases media; the existing Clowder **聊聊** entry restores the cat.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
Xianxian uses one layered sitting body or one VP9 clip at a time. Actual Host
|
|
28
|
+
activity, durable answers and owner-scoped pending decisions select thought,
|
|
29
|
+
delivery and waiting actions. Dragging selects the running clip; an explicit
|
|
30
|
+
play gesture selects pounce. Reduced motion keeps the body static. Other cats
|
|
31
|
+
retain their own skins. The v2 living assets and their hashes are pinned in
|
|
32
|
+
`source-lock.json`; the installable renderer carries all 20 assets itself.
|
|
33
|
+
|
|
34
|
+
The Host keeps a small transparent margin left of the pet hit target so the
|
|
35
|
+
pounce's tail is visible without turning empty space into a click target.
|
|
29
36
|
|
|
30
37
|
## Verification
|
|
31
38
|
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.3.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-I7pGRaZJIHoBrsk0si+5MAm7VCAjc3AzQNjeo+ra3Is="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"pluginId": "official.companion",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"contractVersion": "0.1.0",
|
|
5
5
|
"name": "猫猫球",
|
|
6
6
|
"icon": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"bridgeVersion": "1.3.0",
|
|
41
41
|
"surface": {
|
|
42
42
|
"entrypoint": "renderer/index.html",
|
|
43
|
-
"integrity": "sha256-
|
|
43
|
+
"integrity": "sha256-I7pGRaZJIHoBrsk0si+5MAm7VCAjc3AzQNjeo+ra3Is="
|
|
44
44
|
},
|
|
45
45
|
"presentation": {
|
|
46
46
|
"width": 300,
|
package/renderer/index.html
CHANGED
|
@@ -13,7 +13,27 @@
|
|
|
13
13
|
</svg>
|
|
14
14
|
<main id="desktop" aria-label="猫猫球">
|
|
15
15
|
<div id="anchor">
|
|
16
|
-
<button id="pet" aria-label="猫猫:点击交流,右键更多,拖动移动" aria-expanded="false" aria-controls="actions menu chat"
|
|
16
|
+
<button id="pet" aria-label="猫猫:点击交流,右键更多,拖动移动" aria-expanded="false" aria-controls="actions menu chat">
|
|
17
|
+
<svg id="living-sit" viewBox="0 0 600 600" aria-hidden="true" hidden>
|
|
18
|
+
<defs>
|
|
19
|
+
<mask id="living-eye-left" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="600"><image href="./skins/xianxian-living/layers/whiteL.webp" width="600" height="600"/></mask>
|
|
20
|
+
<mask id="living-eye-right" maskUnits="userSpaceOnUse" x="0" y="0" width="600" height="600"><image href="./skins/xianxian-living/layers/whiteR.webp" width="600" height="600"/></mask>
|
|
21
|
+
</defs>
|
|
22
|
+
<g data-layer="tail"><image href="./skins/xianxian-living/layers/tail.webp" width="600" height="600"/></g>
|
|
23
|
+
<image href="./skins/xianxian-living/layers/body.webp" width="600" height="600"/>
|
|
24
|
+
<g data-layer="head">
|
|
25
|
+
<g data-layer="ear-left"><image href="./skins/xianxian-living/layers/earL.webp" width="600" height="600"/></g>
|
|
26
|
+
<g data-layer="ear-right"><image href="./skins/xianxian-living/layers/earR.webp" width="600" height="600"/></g>
|
|
27
|
+
<image href="./skins/xianxian-living/layers/head.webp" width="600" height="600"/>
|
|
28
|
+
<g mask="url(#living-eye-left)"><image data-layer="iris-left" href="./skins/xianxian-living/layers/irisL.webp" width="600" height="600"/></g>
|
|
29
|
+
<g mask="url(#living-eye-right)"><image data-layer="iris-right" href="./skins/xianxian-living/layers/irisR.webp" width="600" height="600"/></g>
|
|
30
|
+
<image data-layer="closed" href="./skins/xianxian-living/layers/closed.webp" width="600" height="600"/>
|
|
31
|
+
<image data-layer="mouth" href="./skins/xianxian-living/layers/mouth.webp" width="600" height="600"/>
|
|
32
|
+
<image data-layer="scared" href="./skins/xianxian-living/layers/scared.webp" width="600" height="600"/>
|
|
33
|
+
</g>
|
|
34
|
+
</svg>
|
|
35
|
+
<video id="living-video" muted playsinline preload="metadata" aria-hidden="true" hidden></video>
|
|
36
|
+
</button>
|
|
17
37
|
<div id="indicators">
|
|
18
38
|
<button id="call-badge" hidden aria-label="结束语音" title="结束语音"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0 0 14 0M12 18v4M8 22h8"/></svg></button>
|
|
19
39
|
<button id="share-badge" hidden aria-label="停止共享" title="停止共享"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg></button>
|
|
@@ -40,6 +60,7 @@
|
|
|
40
60
|
<button data-action="history"><svg class="icon" aria-hidden="true"><use href="#i-history"/></svg><span>聊天记录</span></button>
|
|
41
61
|
<button data-action="decisions"><span aria-hidden="true">◇</span><span>待你看</span><span id="menu-pending" class="menu-status">—</span></button>
|
|
42
62
|
<button data-action="write"><svg class="icon" aria-hidden="true"><use href="#i-keyboard"/></svg><span>打字</span></button>
|
|
63
|
+
<button id="menu-play" data-action="play" hidden><span aria-hidden="true">✦</span><span>逗宪宪</span></button>
|
|
43
64
|
<div class="menu-divider" role="separator"></div>
|
|
44
65
|
<button id="speaker" data-action="speaker" hidden><svg class="icon" aria-hidden="true"><use href="#i-mic"/></svg><span class="label">关闭播音</span></button>
|
|
45
66
|
<button id="menu-share" data-action="share" title="语音连接后可共享画面"><svg class="icon" aria-hidden="true"><use href="#i-screen"/></svg><span class="label">共享画面</span></button>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// F317 living Xianxian package v1. All clips share the layered sit's ground line.
|
|
2
|
+
// Values are the package-v1 CSS anchors scaled from its 290px stage to the Host pet.
|
|
3
|
+
const scale = 120 / 290;
|
|
4
|
+
const groundY = 10 + 262.5 * scale;
|
|
5
|
+
const clips = {
|
|
6
|
+
'running-left': { name: 'trot', size: [265, 236], ground: 234.8, centre: 132.5, loop: true },
|
|
7
|
+
'running-right': { name: 'trot', size: [265, 236], ground: 234.8, centre: 132.5, loop: true },
|
|
8
|
+
running: { name: 'trot', size: [265, 236], ground: 234.8, centre: 132.5, loop: true },
|
|
9
|
+
play: { name: 'pounce', size: [304.8, 312.6], ground: 303.9, centre: 201.9, rate: 1.5 },
|
|
10
|
+
sleeping: { name: 'sleep', size: [468.5, 262.1], ground: 256.4, centre: 238.9, loop: true },
|
|
11
|
+
waking: { name: 'wake', size: [468.5, 262.1], ground: 256.4, centre: 238.9 },
|
|
12
|
+
peek: { name: 'peek', size: [153.5, 234.3], ground: 234.3, centre: 76.75, loop: true },
|
|
13
|
+
review: { name: 'carry', size: [344.3, 290.9], ground: 263, centre: 198.3 },
|
|
14
|
+
pending_decision: { name: 'wait', size: [197.9, 252.8], ground: 249.6, centre: 98.4, loop: true },
|
|
15
|
+
working: { name: 'think', size: [266.4, 256.9], ground: 253.7, centre: 132.1, loop: true },
|
|
16
|
+
staged_thought: { name: 'think', size: [266.4, 256.9], ground: 253.7, centre: 132.1, loop: true },
|
|
17
|
+
};
|
|
18
|
+
export const isLivingClip = action => Object.hasOwn(clips, action);
|
|
19
|
+
const setHidden = (node, hidden) => {
|
|
20
|
+
// SVGElement.hidden is not consistently reflected to the HTML hidden
|
|
21
|
+
// attribute; the renderer's [hidden] rule reads the attribute itself.
|
|
22
|
+
node.toggleAttribute?.('hidden', hidden);
|
|
23
|
+
node.hidden = hidden;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** One visible body: a layered sit or one muted clip, never both. */
|
|
27
|
+
export class LivingBody {
|
|
28
|
+
constructor({ root, sit, video }) {
|
|
29
|
+
this.root = root;
|
|
30
|
+
this.sit = sit;
|
|
31
|
+
this.video = video;
|
|
32
|
+
this.action = '';
|
|
33
|
+
this.revision = 0;
|
|
34
|
+
}
|
|
35
|
+
show(action, { reducedMotion = false, onEnded = () => {} } = {}) {
|
|
36
|
+
const dockEdge = this.root.dataset.dockEdge;
|
|
37
|
+
if (this.action === action && this.reducedMotion === reducedMotion && this.dockEdge === dockEdge) return;
|
|
38
|
+
this.action = action;
|
|
39
|
+
this.reducedMotion = reducedMotion;
|
|
40
|
+
this.dockEdge = dockEdge;
|
|
41
|
+
const revision = ++this.revision;
|
|
42
|
+
const clip = !reducedMotion && clips[action];
|
|
43
|
+
this.root.dataset.pose = action === 'failed' ? 'scared' : action === 'waiting' ? 'listening' :
|
|
44
|
+
action === 'sleeping' ? 'sleeping' : action === 'pending_decision' ? 'pending' : 'sit';
|
|
45
|
+
this.root.dataset.direction = action === 'running-left' || (action === 'peek' && this.root.dataset.dockEdge === 'left') ? 'left' : 'right';
|
|
46
|
+
this.video.onended = null;
|
|
47
|
+
this.video.pause();
|
|
48
|
+
if (!clip) {
|
|
49
|
+
setHidden(this.video, true);
|
|
50
|
+
setHidden(this.sit, false);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.video.src = `./skins/xianxian-living/clips/${clip.name}.webm`;
|
|
54
|
+
this.video.loop = clip.loop === true;
|
|
55
|
+
this.video.playbackRate = clip.rate ?? 1;
|
|
56
|
+
this.video.style.width = `${clip.size[0] * scale}px`;
|
|
57
|
+
this.video.style.height = `${clip.size[1] * scale}px`;
|
|
58
|
+
this.video.style.left = action === 'peek'
|
|
59
|
+
? `${this.root.dataset.dockEdge === 'left' ? -32 : 128 - clip.size[0] * scale}px`
|
|
60
|
+
: `${60 - clip.centre * scale}px`;
|
|
61
|
+
this.video.style.top = `${action === 'peek' ? 138 - clip.size[1] * scale : groundY - clip.ground * scale}px`;
|
|
62
|
+
try { this.video.currentTime = 0; } catch { /* New source is not yet seekable. */ }
|
|
63
|
+
this.video.onended = () => { if (revision === this.revision) onEnded(); };
|
|
64
|
+
setHidden(this.sit, true);
|
|
65
|
+
setHidden(this.video, false);
|
|
66
|
+
void this.video.play().catch(() => {
|
|
67
|
+
if (revision !== this.revision) return;
|
|
68
|
+
setHidden(this.video, true);
|
|
69
|
+
setHidden(this.sit, false);
|
|
70
|
+
onEnded();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
close() {
|
|
74
|
+
this.revision++;
|
|
75
|
+
this.video.onended = null;
|
|
76
|
+
this.video.pause();
|
|
77
|
+
}
|
|
78
|
+
hide() {
|
|
79
|
+
this.close();
|
|
80
|
+
setHidden(this.video, true);
|
|
81
|
+
setHidden(this.sit, true);
|
|
82
|
+
this.action = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Observe the Host window's current screen; never select or move a display. */
|
|
2
|
+
export function detectDockedEdge(win, petRect) {
|
|
3
|
+
const left = win.screen?.availLeft;
|
|
4
|
+
const width = win.screen?.availWidth;
|
|
5
|
+
if (![win.screenX, left, width, petRect.left, petRect.width].every(Number.isFinite) || width <= 0) return null;
|
|
6
|
+
const petLeft = win.screenX + petRect.left;
|
|
7
|
+
if (petLeft - left <= 40) return 'left';
|
|
8
|
+
if (left + width - petLeft - petRect.width <= 40) return 'right';
|
|
9
|
+
return null;
|
|
10
|
+
}
|
package/renderer/pet-motion.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isLivingClip } from './living-body.mjs';
|
|
2
|
+
|
|
1
3
|
// F229 pet.json frame durations, plus the three F258 rows pinned in source-lock.json.
|
|
2
4
|
const atlas = {
|
|
3
5
|
idle: { row: 0, frames: [1200, 400, 400, 500, 500, 800] },
|
|
@@ -15,7 +17,7 @@ const additional = {
|
|
|
15
17
|
working: { file: 'xianxian-working-row.png', frames: [520, 560, 520, 640, 520, 560, 520, 640] },
|
|
16
18
|
staged_thought: { file: 'xianxian-staged-thought-row.png', frames: [1200, 500, 700, 900, 1200, 500] },
|
|
17
19
|
};
|
|
18
|
-
const signals = { connected: 'waving', received: 'jumping', answered: 'review', failed: 'failed' };
|
|
20
|
+
const signals = { connected: 'waving', received: 'jumping', answered: 'review', failed: 'failed', play: 'play' };
|
|
19
21
|
const staticSkins = { 'ragdoll-v1': 'ragdoll-v1.png', 'yarn-ball': 'yarn-ball.png' };
|
|
20
22
|
|
|
21
23
|
/** All animation choices come from Host state or a completed local gesture. */
|
|
@@ -26,6 +28,7 @@ export class PetMotion {
|
|
|
26
28
|
const clearTimer = options.clearTimer ?? globalThis.clearTimeout;
|
|
27
29
|
this.setTimer = (callback, delay) => setTimer.call(globalThis, callback, delay);
|
|
28
30
|
this.clearTimer = id => clearTimer.call(globalThis, id);
|
|
31
|
+
this.livingBody = options.livingBody;
|
|
29
32
|
this.reducedMotion = options.reducedMotion ?? globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
30
33
|
this.skin = 'xianxian-codex';
|
|
31
34
|
this.phase = 'idle';
|
|
@@ -34,25 +37,51 @@ export class PetMotion {
|
|
|
34
37
|
this.frame = 0;
|
|
35
38
|
this.moving = false;
|
|
36
39
|
this.oneShot = false;
|
|
40
|
+
this.pendingDecision = false;
|
|
41
|
+
this.dockedEdge = null;
|
|
37
42
|
}
|
|
38
43
|
setContext({ skin, phase, nativeActivity, muted }) {
|
|
44
|
+
this.context = { skin, phase, nativeActivity, muted };
|
|
39
45
|
const selectedSkin = ['xianxian-codex', 'yanyan-codex', ...Object.keys(staticSkins)].includes(skin) ? skin : 'xianxian-codex';
|
|
40
46
|
const changedSkin = selectedSkin !== this.skin;
|
|
41
47
|
const changedPhase = phase !== this.phase;
|
|
42
48
|
this.skin = selectedSkin;
|
|
43
49
|
this.phase = phase;
|
|
44
|
-
const requested = nativeActivity === 'tool_running' ? 'working' : nativeActivity === 'reasoning' ? 'staged_thought'
|
|
45
|
-
: ['talking', 'connecting'].includes(phase) && !muted ? 'waiting' : 'idle';
|
|
50
|
+
const requested = this.pendingDecision ? 'pending_decision' : nativeActivity === 'tool_running' ? 'working' : nativeActivity === 'reasoning' ? 'staged_thought'
|
|
51
|
+
: ['talking', 'connecting'].includes(phase) && !muted ? 'waiting' : this.dockedEdge ? 'peek' : 'idle';
|
|
46
52
|
const nextBase = this.resolve(requested);
|
|
47
53
|
const changedBase = nextBase !== this.base;
|
|
48
54
|
this.base = nextBase;
|
|
49
55
|
if (phase !== 'idle') this.cancelRest();
|
|
50
56
|
if (phase === 'idle' && changedPhase) { this.moving = false; this.oneShot = false; }
|
|
51
|
-
|
|
57
|
+
const waking = this.living() && !this.reducedMotion && this.action === 'sleeping' && this.base === 'waiting';
|
|
58
|
+
if (waking) { this.oneShot = true; this.play('waking'); }
|
|
59
|
+
else if (this.oneShot && this.action === 'waking' && ['working', 'staged_thought', 'pending_decision'].includes(this.base)) {
|
|
60
|
+
this.oneShot = false;
|
|
61
|
+
this.play(this.base);
|
|
62
|
+
} else if (!this.moving && !this.oneShot && (changedSkin || changedBase || changedPhase || !this.action)) this.play(this.base, changedSkin);
|
|
52
63
|
if (phase === 'idle' && this.action === 'idle') this.armRest();
|
|
53
64
|
}
|
|
65
|
+
setPendingDecision(value) {
|
|
66
|
+
if (this.pendingDecision === value) return;
|
|
67
|
+
this.pendingDecision = value;
|
|
68
|
+
if (this.context) this.setContext(this.context);
|
|
69
|
+
if (value === true && this.oneShot) {
|
|
70
|
+
this.oneShot = false;
|
|
71
|
+
this.play(this.base);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
setDockedEdge(value) {
|
|
75
|
+
const edge = ['left', 'right'].includes(value) ? value : null;
|
|
76
|
+
if (this.dockedEdge === edge) return;
|
|
77
|
+
const previous = this.dockedEdge;
|
|
78
|
+
this.dockedEdge = edge;
|
|
79
|
+
if (this.context) this.setContext(this.context);
|
|
80
|
+
if (previous && edge && this.action === 'peek') this.play('peek', true);
|
|
81
|
+
}
|
|
54
82
|
signal(kind) {
|
|
55
|
-
const requested = signals[kind];
|
|
83
|
+
const requested = kind === 'received' && this.living() && this.action === 'sleeping' ? 'waking' : signals[kind];
|
|
84
|
+
if (kind === 'play' && (this.pendingDecision || this.phase !== 'idle' || this.context?.nativeActivity !== 'none')) return;
|
|
56
85
|
if (!requested || this.moving || this.reducedMotion || staticSkins[this.skin]) return;
|
|
57
86
|
this.cancelRest();
|
|
58
87
|
this.oneShot = true;
|
|
@@ -71,15 +100,18 @@ export class PetMotion {
|
|
|
71
100
|
this.play(this.base);
|
|
72
101
|
if (this.phase === 'idle') this.armRest();
|
|
73
102
|
}
|
|
74
|
-
close() { this.cancelFrame(); this.cancelRest(); }
|
|
103
|
+
close() { this.cancelFrame(); this.cancelRest(); this.livingBody?.close(); }
|
|
104
|
+
living() { return this.skin === 'xianxian-codex' && Boolean(this.livingBody); }
|
|
75
105
|
resolve(action) {
|
|
106
|
+
if (action === 'pending_decision' && !this.living()) return 'waiting';
|
|
107
|
+
if (action === 'peek' && !this.living()) return 'idle';
|
|
76
108
|
if (staticSkins[this.skin]) return 'idle';
|
|
77
109
|
if (this.skin !== 'xianxian-codex' && action in additional)
|
|
78
110
|
return action === 'sleeping' ? 'idle' : 'review';
|
|
79
111
|
return action;
|
|
80
112
|
}
|
|
81
|
-
play(action) {
|
|
82
|
-
if (this.action === action) return;
|
|
113
|
+
play(action, force = false) {
|
|
114
|
+
if (this.action === action && !force) return;
|
|
83
115
|
this.cancelFrame();
|
|
84
116
|
this.action = action;
|
|
85
117
|
this.frame = 0;
|
|
@@ -90,6 +122,13 @@ export class PetMotion {
|
|
|
90
122
|
const { style, dataset } = this.element;
|
|
91
123
|
dataset.skin = this.skin;
|
|
92
124
|
dataset.action = this.action;
|
|
125
|
+
dataset.dockEdge = this.dockedEdge ?? 'none';
|
|
126
|
+
if (this.living()) {
|
|
127
|
+
style.backgroundImage = 'none';
|
|
128
|
+
this.livingBody.show(this.action, { reducedMotion: this.reducedMotion, onEnded: () => this.completeOneShot() });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.livingBody?.hide();
|
|
93
132
|
const staticFile = staticSkins[this.skin];
|
|
94
133
|
if (staticFile) {
|
|
95
134
|
style.backgroundImage = `url("./skins/${staticFile}")`;
|
|
@@ -104,6 +143,11 @@ export class PetMotion {
|
|
|
104
143
|
}
|
|
105
144
|
scheduleFrame() {
|
|
106
145
|
if (this.reducedMotion || staticSkins[this.skin]) return;
|
|
146
|
+
if (this.living()) {
|
|
147
|
+
if (isLivingClip(this.action) || !this.oneShot) return;
|
|
148
|
+
this.frameTimer = this.setTimer(() => { this.frameTimer = undefined; this.completeOneShot(); }, 700);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
107
151
|
const frames = additional[this.action]?.frames ?? atlas[this.action]?.frames;
|
|
108
152
|
if (!frames) return;
|
|
109
153
|
this.frameTimer = this.setTimer(() => {
|
|
@@ -121,8 +165,15 @@ export class PetMotion {
|
|
|
121
165
|
}, frames[this.frame]);
|
|
122
166
|
}
|
|
123
167
|
cancelFrame() { if (this.frameTimer !== undefined) this.clearTimer(this.frameTimer); this.frameTimer = undefined; }
|
|
168
|
+
completeOneShot() {
|
|
169
|
+
if (!this.oneShot) return;
|
|
170
|
+
this.oneShot = false;
|
|
171
|
+
this.action = '';
|
|
172
|
+
this.play(this.base);
|
|
173
|
+
if (this.phase === 'idle') this.armRest();
|
|
174
|
+
}
|
|
124
175
|
armRest() {
|
|
125
|
-
if (this.restTimer !== undefined || this.action === 'sleeping') return;
|
|
176
|
+
if (this.restTimer !== undefined || this.action === 'sleeping' || this.pendingDecision || this.dockedEdge) return;
|
|
126
177
|
this.restTimer = this.setTimer(() => {
|
|
127
178
|
this.restTimer = undefined;
|
|
128
179
|
if (this.phase === 'idle' && !this.moving && !this.oneShot) this.play(this.resolve('sleeping'));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"canvas": 600,
|
|
3
|
+
"tailPivot": [
|
|
4
|
+
168.1,
|
|
5
|
+
456.1
|
|
6
|
+
],
|
|
7
|
+
"earPivotL": [
|
|
8
|
+
218.9,
|
|
9
|
+
101.4
|
|
10
|
+
],
|
|
11
|
+
"earPivotR": [
|
|
12
|
+
391.0,
|
|
13
|
+
100.2
|
|
14
|
+
],
|
|
15
|
+
"neckPivot": [
|
|
16
|
+
300.0,
|
|
17
|
+
280.4
|
|
18
|
+
],
|
|
19
|
+
"irisL": [
|
|
20
|
+
231.1,
|
|
21
|
+
156.5,
|
|
22
|
+
285.6,
|
|
23
|
+
213.4
|
|
24
|
+
],
|
|
25
|
+
"irisR": [
|
|
26
|
+
339.7,
|
|
27
|
+
154.5,
|
|
28
|
+
388.0,
|
|
29
|
+
211.0
|
|
30
|
+
],
|
|
31
|
+
"whiteL": [
|
|
32
|
+
230.6,
|
|
33
|
+
156.5,
|
|
34
|
+
294.3,
|
|
35
|
+
221.1
|
|
36
|
+
],
|
|
37
|
+
"whiteR": [
|
|
38
|
+
331.1,
|
|
39
|
+
154.5,
|
|
40
|
+
391.4,
|
|
41
|
+
219.6
|
|
42
|
+
],
|
|
43
|
+
"tail": [
|
|
44
|
+
47.8,
|
|
45
|
+
313.4,
|
|
46
|
+
178.9,
|
|
47
|
+
507.2
|
|
48
|
+
],
|
|
49
|
+
"earL": [
|
|
50
|
+
169.4,
|
|
51
|
+
26.8,
|
|
52
|
+
258.4,
|
|
53
|
+
140.7
|
|
54
|
+
],
|
|
55
|
+
"earR": [
|
|
56
|
+
345.9,
|
|
57
|
+
20.1,
|
|
58
|
+
437.8,
|
|
59
|
+
140.7
|
|
60
|
+
],
|
|
61
|
+
"mouth": [
|
|
62
|
+
286.6,
|
|
63
|
+
221.5,
|
|
64
|
+
343.1,
|
|
65
|
+
256.9
|
|
66
|
+
],
|
|
67
|
+
"areas_native": {
|
|
68
|
+
"tail": 78634,
|
|
69
|
+
"earL": 23228,
|
|
70
|
+
"earR": 23149,
|
|
71
|
+
"fearL": 9568,
|
|
72
|
+
"fearR": 9138,
|
|
73
|
+
"irisL": 7903,
|
|
74
|
+
"irisR": 7624,
|
|
75
|
+
"whiteL": 10967,
|
|
76
|
+
"whiteR": 10974,
|
|
77
|
+
"mouth": 4342
|
|
78
|
+
},
|
|
79
|
+
"collarTop_native": 580,
|
|
80
|
+
"sitFloorNative": 1135,
|
|
81
|
+
"runCropNative": [
|
|
82
|
+
27,
|
|
83
|
+
181,
|
|
84
|
+
1220,
|
|
85
|
+
1022
|
|
86
|
+
],
|
|
87
|
+
"runFloorNative": 1016
|
|
88
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/renderer/style.css
CHANGED
|
@@ -12,6 +12,34 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
12
12
|
#anchor { position: absolute; left: 8px; top: 8px; width: 120px; height: 130px; }
|
|
13
13
|
#pet { width: 120px; height: 130px; display: block; border: 0; padding: 0; background: url("./skins/xianxian-codex.webp") 0 0 / 960px 1170px; filter: drop-shadow(0 6px 4px #0002); touch-action: none; cursor: grab; }
|
|
14
14
|
#pet.dragging { cursor: grabbing; }
|
|
15
|
+
#pet[data-skin="xianxian-codex"] { position: relative; background-image: none; overflow: visible; }
|
|
16
|
+
#living-sit { position: absolute; left: 0; bottom: 10px; width: 120px; height: 120px; overflow: visible; pointer-events: none; }
|
|
17
|
+
#living-video { position: absolute; object-fit: fill; pointer-events: none; }
|
|
18
|
+
#pet[data-direction="left"] #living-video { transform: scaleX(-1); }
|
|
19
|
+
#living-sit [data-layer="tail"] { transform-box: view-box; transform-origin: 168px 456px; animation: living-tail 3.4s ease-in-out infinite; }
|
|
20
|
+
#living-sit [data-layer="head"] { transform-box: view-box; transform-origin: 300px 280px; animation: living-head 5.5s ease-in-out infinite; }
|
|
21
|
+
#living-sit [data-layer="ear-left"], #living-sit [data-layer="ear-right"] { transform-box: view-box; }
|
|
22
|
+
#living-sit [data-layer="ear-left"] { transform-origin: 219px 101px; }
|
|
23
|
+
#living-sit [data-layer="ear-right"] { transform-origin: 391px 100px; }
|
|
24
|
+
#pet[data-pose="listening"] #living-sit [data-layer="ear-left"] { transform: rotate(-8deg); }
|
|
25
|
+
#pet[data-pose="listening"] #living-sit [data-layer="ear-right"] { transform: rotate(8deg); }
|
|
26
|
+
#living-sit [data-layer="closed"] { opacity: 0; animation: living-blink 5.8s linear infinite; }
|
|
27
|
+
#pet[data-action="idle"] #living-sit [data-layer="tail"] { animation: living-idle-tail 4.8s ease-in-out infinite; }
|
|
28
|
+
#pet[data-action="idle"] #living-sit [data-layer="head"] { animation: living-idle-head 6.8s ease-in-out infinite; }
|
|
29
|
+
#pet[data-action="idle"] #living-sit [data-layer="ear-left"] { animation: living-idle-ear-left 8.1s ease-in-out infinite; }
|
|
30
|
+
#pet[data-action="idle"] #living-sit [data-layer="ear-right"] { animation: living-idle-ear-right 7.3s ease-in-out infinite; }
|
|
31
|
+
#pet[data-action="idle"] #living-sit [data-layer="closed"] { animation: living-idle-blink 5.8s steps(1, end) infinite; }
|
|
32
|
+
#living-sit [data-layer="mouth"], #living-sit [data-layer="scared"] { opacity: 0; }
|
|
33
|
+
#pet[data-pose="scared"] #living-sit [data-layer="scared"] { opacity: 1; }
|
|
34
|
+
#pet[data-pose="sleeping"] #living-sit [data-layer="closed"] { opacity: 1; animation: none; }
|
|
35
|
+
@keyframes living-tail { 50% { transform: rotate(5deg); } }
|
|
36
|
+
@keyframes living-head { 50% { transform: rotate(2deg); } }
|
|
37
|
+
@keyframes living-blink { 0%, 96%, 100% { opacity: 0; } 97%, 98% { opacity: 1; } }
|
|
38
|
+
@keyframes living-idle-tail { 0%, 12%, 100% { transform: rotate(0); } 34% { transform: rotate(10deg); } 62% { transform: rotate(-8deg); } 80% { transform: rotate(3deg); } }
|
|
39
|
+
@keyframes living-idle-head { 0%, 12%, 100% { transform: rotate(0); } 35% { transform: rotate(5deg); } 69% { transform: rotate(-3deg); } }
|
|
40
|
+
@keyframes living-idle-ear-left { 0%, 68%, 76%, 100% { transform: rotate(0); } 71% { transform: rotate(-9deg); } 73% { transform: rotate(3deg); } }
|
|
41
|
+
@keyframes living-idle-ear-right { 0%, 30%, 39%, 100% { transform: rotate(0); } 33% { transform: rotate(8deg); } 36% { transform: rotate(-3deg); } }
|
|
42
|
+
@keyframes living-idle-blink { 0%, 24%, 30%, 74%, 80%, 100% { opacity: 0; } 25%, 29%, 75%, 79% { opacity: 1; } }
|
|
15
43
|
#indicators { position: absolute; bottom: 0; right: 0; display: flex; gap: 4px; }
|
|
16
44
|
#indicators button { width: 32px; height: 32px; border: 1px solid #fff9; border-radius: 50%; display: grid; place-items: center; background: #236b59; color: white; box-shadow: 0 2px 5px #0003; }
|
|
17
45
|
#indicators button:hover { background: #ab4842; }
|
|
@@ -50,18 +78,22 @@ button:disabled { opacity: .45; cursor: default; }
|
|
|
50
78
|
#chat header button { font-size: 12px; color: var(--cafe-text-secondary); }
|
|
51
79
|
#transcript { height: 145px; overflow: auto; padding: 14px; line-height: 1.7; }
|
|
52
80
|
#transcript p { margin: 0 0 10px; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
53
|
-
#transcript p:not(.empty) {
|
|
81
|
+
#transcript p:not(.empty) { width: fit-content; max-width: 92%; padding: 7px 10px; border-radius: 9px; }
|
|
82
|
+
#transcript p.user { margin-left: auto; background: var(--accent-50); }
|
|
83
|
+
#transcript p.assistant { margin-right: auto; background: var(--cafe-surface); border: 1px solid var(--cafe-border-subtle); }
|
|
54
84
|
.empty { color: var(--cafe-text-muted); font-size: 13px; }
|
|
55
85
|
#compose { display: flex; align-items: flex-end; padding: 10px; gap: 8px; border-top: 1px solid var(--cafe-border-subtle); }
|
|
56
86
|
#message { resize: none; border: 0; min-width: 0; flex: 1; background: transparent; padding: 5px; color: inherit; line-height: 1.5; }
|
|
57
87
|
#chat small { color: var(--cafe-text-muted); font-size: 10px; padding: 0 14px 10px; }
|
|
58
88
|
.sr-only { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }
|
|
59
|
-
@media (prefers-reduced-motion: reduce) {
|
|
89
|
+
@media (prefers-reduced-motion: reduce) {
|
|
90
|
+
#pet, #indicators button, #living-sit [data-layer], #pet[data-action="idle"] #living-sit [data-layer] { animation: none; }
|
|
91
|
+
#living-sit [data-layer="closed"] { opacity: 0; }
|
|
92
|
+
}
|
|
60
93
|
#pet[data-skin="yanyan-codex"] { background-image: url("./skins/yanyan-codex.webp"); }
|
|
61
94
|
#pet[data-skin="ragdoll-v1"] { background-image: url("./skins/ragdoll-v1.png"); animation: none; background-size: contain; background-repeat: no-repeat; background-position: center; }
|
|
62
95
|
#pet[data-skin="yarn-ball"] { background-image: url("./skins/yarn-ball.png"); animation: none; background-size: contain; background-repeat: no-repeat; background-position: center; }
|
|
63
96
|
#transcript { height: 230px; font-size: 13px; }
|
|
64
|
-
#transcript .assistant { background: transparent; }
|
|
65
97
|
#transcript p[data-author]::before { content: attr(data-author); display: block; color: var(--cafe-text-muted); font-size: 10px; margin-bottom: 2px; }
|
|
66
98
|
#transcript p[data-live="true"] { border-left: 2px solid var(--cafe-accent); }
|
|
67
99
|
#history { padding: 0; font-size: 10px; color: var(--cafe-text-secondary); }
|
package/renderer/surface.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { ScreenShare } from './screen-share.mjs';
|
|
|
6
6
|
import { TranscriptView } from './transcript-view.mjs';
|
|
7
7
|
import { RecentBubble } from './recent-bubble.mjs';
|
|
8
8
|
import { PetMotion } from './pet-motion.mjs';
|
|
9
|
+
import { LivingBody } from './living-body.mjs';
|
|
10
|
+
import { detectDockedEdge } from './living-edge.mjs';
|
|
9
11
|
import { bindPetControls } from './pet-controls.mjs';
|
|
10
12
|
import { decisionBadge, decisionRows } from './decision-view.mjs';
|
|
11
13
|
|
|
@@ -14,10 +16,14 @@ const label = (id, text) => { $(id).querySelector('.label').textContent = text;
|
|
|
14
16
|
const status = text => { $('status').textContent = text; $('chat-status').textContent = text; $('announcement').textContent = text; };
|
|
15
17
|
const transcript = new TranscriptView($('transcript'));
|
|
16
18
|
const bubble = new RecentBubble([$('bubble-first'), $('bubble-second')]);
|
|
17
|
-
const motion = new PetMotion($('pet'));
|
|
19
|
+
const motion = new PetMotion($('pet'), { livingBody: new LivingBody({ root: $('pet'), sit: $('living-sit'), video: $('living-video') }) });
|
|
18
20
|
let sharing = false, pendingScreen = false, loading = false, latestHistory, previousPhase = 'idle';
|
|
21
|
+
let observedHistory = false, lastAssistantId;
|
|
22
|
+
let resultPreviewUntil = 0;
|
|
23
|
+
let didMove = false;
|
|
19
24
|
let decisionLoading = false, decisionOffset = 0;
|
|
20
25
|
let threadTitle;
|
|
26
|
+
const showAmbient = active => (active || Date.now() < resultPreviewUntil) && bubble.hasContent();
|
|
21
27
|
|
|
22
28
|
if (!window.clowderCompanion) {
|
|
23
29
|
$('actions').hidden = false; $('status').hidden = false;
|
|
@@ -27,7 +33,13 @@ if (!window.clowderCompanion) {
|
|
|
27
33
|
const client = createCompanionClient(window.clowderCompanion);
|
|
28
34
|
const controls = bindPetControls(client, {
|
|
29
35
|
error: error => status(explainError(error)),
|
|
30
|
-
moved: (dx, dy) =>
|
|
36
|
+
moved: (dx, dy) => {
|
|
37
|
+
if (dx !== 'stop') { didMove = true; motion.move(dx, dy); return; }
|
|
38
|
+
motion.stopMove();
|
|
39
|
+
if (!didMove) return;
|
|
40
|
+
didMove = false;
|
|
41
|
+
setTimeout(() => motion.setDockedEdge(detectDockedEdge(window, $('pet').getBoundingClientRect())), 100);
|
|
42
|
+
},
|
|
31
43
|
changed: panel => { if (panel === 'chat') void readHistory(); if (panel === 'decisions') void readDecisions(); },
|
|
32
44
|
action(kind) {
|
|
33
45
|
if (kind === 'begin') { transcript.reset(); bubble.reset(); void conversation.begin(); void controls.show('none'); }
|
|
@@ -37,6 +49,10 @@ if (!window.clowderCompanion) {
|
|
|
37
49
|
if (kind === 'share') toggleScreen();
|
|
38
50
|
if (kind === 'documents') void conversation.documents(conversation.identity?.documentsAllowed === false);
|
|
39
51
|
if (kind === 'hide') void client.hide().catch(error => status(explainError(error)));
|
|
52
|
+
if (kind === 'play' && conversation.identity?.skin === 'xianxian-codex') {
|
|
53
|
+
void controls.show('none');
|
|
54
|
+
motion.signal('play');
|
|
55
|
+
}
|
|
40
56
|
},
|
|
41
57
|
});
|
|
42
58
|
const screen = new ScreenShare({
|
|
@@ -73,7 +89,7 @@ if (!window.clowderCompanion) {
|
|
|
73
89
|
if (previousPhase !== 'idle' && value.phase === 'idle') { transcript.reset(); bubble.reset(); void readHistory(); }
|
|
74
90
|
previousPhase = value.phase;
|
|
75
91
|
const active = value.phase !== 'idle';
|
|
76
|
-
void controls.setAmbient(active
|
|
92
|
+
void controls.setAmbient(showAmbient(active));
|
|
77
93
|
$('begin').hidden = active; $('begin').disabled = !value.identity;
|
|
78
94
|
label('begin', value.failed ? '重试语音' : '语音聊');
|
|
79
95
|
$('mic').hidden = value.phase !== 'talking'; $('speaker').hidden = !active;
|
|
@@ -93,6 +109,7 @@ if (!window.clowderCompanion) {
|
|
|
93
109
|
if (justConnected) motion.signal('connected');
|
|
94
110
|
if (showFailure) motion.signal('failed');
|
|
95
111
|
if (identity) {
|
|
112
|
+
$('menu-play').hidden = identity.skin !== 'xianxian-codex';
|
|
96
113
|
$('chat-name').textContent = threadTitle ?? identity.displayName; $('menu-name').textContent = identity.displayName;
|
|
97
114
|
$('pet').setAttribute('aria-label', `${identity.displayName}:点击交流,右键更多,拖动移动`);
|
|
98
115
|
$('documents').querySelector('.menu-status').textContent = identity.documentsAllowed ? '开启' : '暂停';
|
|
@@ -103,15 +120,22 @@ if (!window.clowderCompanion) {
|
|
|
103
120
|
},
|
|
104
121
|
});
|
|
105
122
|
async function readHistory() {
|
|
106
|
-
if (loading
|
|
123
|
+
if (loading) return;
|
|
107
124
|
loading = true;
|
|
108
125
|
try {
|
|
109
126
|
const history = await client.readConversation();
|
|
127
|
+
const assistantId = history.messages.findLast(message => message.role === 'assistant')?.id;
|
|
128
|
+
if (observedHistory && assistantId && assistantId !== lastAssistantId) {
|
|
129
|
+
motion.signal('answered');
|
|
130
|
+
resultPreviewUntil = Date.now() + 12_000;
|
|
131
|
+
}
|
|
132
|
+
observedHistory = true;
|
|
133
|
+
lastAssistantId = assistantId;
|
|
110
134
|
$('history').textContent = history.hasMore ? '更早聊天 ↗' : '完整聊天 ↗';
|
|
111
135
|
threadTitle = history.threadTitle;
|
|
112
136
|
$('chat-name').textContent = threadTitle;
|
|
113
137
|
bubble.load(history.messages);
|
|
114
|
-
void controls.setAmbient(conversation.phase !== 'idle'
|
|
138
|
+
void controls.setAmbient(showAmbient(conversation.phase !== 'idle'));
|
|
115
139
|
const serialized = JSON.stringify(history.messages);
|
|
116
140
|
if (serialized !== latestHistory) { latestHistory = serialized; transcript.load(history.messages); }
|
|
117
141
|
} catch { $('chat-status').textContent = '聊天记录暂未更新 · 正在说的话仍会显示'; }
|
|
@@ -133,6 +157,7 @@ if (!window.clowderCompanion) {
|
|
|
133
157
|
const page = await client.readDecisions(more ? decisionOffset : 0, 10);
|
|
134
158
|
if (page.status !== 'available') throw new Error('Decision source unavailable');
|
|
135
159
|
showDecisionBadge(page);
|
|
160
|
+
motion.setPendingDecision(page.approvalCount + page.otherNeedsMeCount > 0);
|
|
136
161
|
if (controls.panel !== 'decisions') return;
|
|
137
162
|
const list = $('decision-list');
|
|
138
163
|
if (!more) { list.replaceChildren(); list.scrollTop = 0; }
|
|
@@ -165,6 +190,7 @@ if (!window.clowderCompanion) {
|
|
|
165
190
|
$('decision-more').hidden = !page.page.hasMoreApprovals && !page.page.hasMoreNeedsMe;
|
|
166
191
|
} catch {
|
|
167
192
|
showDecisionBadge(undefined);
|
|
193
|
+
motion.setPendingDecision(null);
|
|
168
194
|
if (controls.panel === 'decisions') $('decision-status').textContent = '待决事项暂不可读 · 请稍后刷新';
|
|
169
195
|
} finally { decisionLoading = false; }
|
|
170
196
|
}
|
|
@@ -191,7 +217,7 @@ if (!window.clowderCompanion) {
|
|
|
191
217
|
const statusMonitor = setInterval(() => void conversation.refresh(), 1000);
|
|
192
218
|
const historyMonitor = setInterval(() => void readHistory(), 3000);
|
|
193
219
|
const decisionMonitor = setInterval(() => { if (controls.panel !== 'decisions') void readDecisions(); }, 15000);
|
|
194
|
-
void conversation.refresh(); void readDecisions(); void controls.show('none');
|
|
220
|
+
void conversation.refresh(); void readHistory(); void readDecisions(); void controls.show('none');
|
|
195
221
|
window.addEventListener('beforeunload', () => {
|
|
196
222
|
clearInterval(statusMonitor); clearInterval(historyMonitor); clearInterval(decisionMonitor);
|
|
197
223
|
unsubscribe(); motion.close(); void conversation.end();
|
package/source-lock.json
CHANGED
|
@@ -66,5 +66,96 @@
|
|
|
66
66
|
"destination": "assets/skins/xianxian-staged-thought-row.png",
|
|
67
67
|
"sha256": "e9286ce2c234911d9b45835849e1a2a853598d0d0260ecf7d24430a95847725f"
|
|
68
68
|
}
|
|
69
|
-
]
|
|
69
|
+
],
|
|
70
|
+
"livingSkin": {
|
|
71
|
+
"package": "xianxian-living package-v2",
|
|
72
|
+
"manifestSha256": "08491ed557a82bc1860bbaeecd094f4a7e637479e78af8b411786f19a5164da0",
|
|
73
|
+
"creator": "opus55; keyframes by codex-astra; source videos by CVO",
|
|
74
|
+
"files": [
|
|
75
|
+
{
|
|
76
|
+
"destination": "assets/skins/xianxian-living/layers/body.webp",
|
|
77
|
+
"sha256": "39f8fae11b49d6fc8c7d0993a2ed867b6d863f1da580a711ff11d91aa8c6c233"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"destination": "assets/skins/xianxian-living/layers/head.webp",
|
|
81
|
+
"sha256": "18a7d34716448ceb70afd7b0e633ef6ab71c6717a96b73b8e39e7ee027d550cb"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"destination": "assets/skins/xianxian-living/layers/tail.webp",
|
|
85
|
+
"sha256": "d23c5c964e9084c6f44d4a6b8b42cbaff6678348bec87a8434a18967816cf4ff"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"destination": "assets/skins/xianxian-living/layers/earL.webp",
|
|
89
|
+
"sha256": "0d40004c7bbc592b76a4452c42740b6059697a1b313c9f9919bd16ef5a64b1d1"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"destination": "assets/skins/xianxian-living/layers/earR.webp",
|
|
93
|
+
"sha256": "1c2141ac3aa4d93ab4f4dfb14c5ec8c566db2e53444a730b008e00f65765b700"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"destination": "assets/skins/xianxian-living/layers/irisL.webp",
|
|
97
|
+
"sha256": "006b247e4fa8924b8f20198fc34d38a231dda0577ee45581a3d6d1fa74886533"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"destination": "assets/skins/xianxian-living/layers/irisR.webp",
|
|
101
|
+
"sha256": "929bd6c888860ac5ebe80f230a5793bc8c93b36eabdc9d18b3ef1fd7cb1ff095"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"destination": "assets/skins/xianxian-living/layers/whiteL.webp",
|
|
105
|
+
"sha256": "b4b2a539950376575ed9d61052b1a43f06812af6158244c86351d29ab935fde7"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"destination": "assets/skins/xianxian-living/layers/whiteR.webp",
|
|
109
|
+
"sha256": "9203fc92f5430d72deb56b1587a8cc79e9d7952fc6e5027adfc8507ed661a441"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"destination": "assets/skins/xianxian-living/layers/closed.webp",
|
|
113
|
+
"sha256": "adb354110fb952a3bb6d5f5fc11968b74c1cc2cf8d2e6dafc4b2b4ad7ca1d92b"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"destination": "assets/skins/xianxian-living/layers/mouth.webp",
|
|
117
|
+
"sha256": "f968f9afba93e6bf64a51e7c26f0ddfc2ed10fd18d65c38cf636b4e73eeb9b53"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"destination": "assets/skins/xianxian-living/layers/scared.webp",
|
|
121
|
+
"sha256": "1cb165fa0a116aea063b78aef64ac3d512d2ec71f0b3f169f57914aaa5a9f48a"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"destination": "assets/skins/xianxian-living/clips/trot.webm",
|
|
125
|
+
"sha256": "02660b4a57dfaca0e4b3d4a4d1e791715af8d01a831c56bd3ff9e30e4fc56900"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"destination": "assets/skins/xianxian-living/clips/pounce.webm",
|
|
129
|
+
"sha256": "65a380d993f53debb6ff761e586e9fdacce4cb2156c6bb461b4f3875926d5a00"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"destination": "assets/skins/xianxian-living/clips/sleep.webm",
|
|
133
|
+
"sha256": "e39554e7aca7b1c51b388b73f7dbc08e3fa7ddafef395cb28c1099d41a9b41d8"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"destination": "assets/skins/xianxian-living/clips/wake.webm",
|
|
137
|
+
"sha256": "73b455088e53edf2849e888e0ecb99953f7e5dba373592fb6232f9736b6da3a0"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"destination": "assets/skins/xianxian-living/clips/peek.webm",
|
|
141
|
+
"sha256": "adebecf89b724eec9852d097364e10bc328d006ce3bb3bd3f4b257c095d57a92"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"destination": "assets/skins/xianxian-living/clips/carry.webm",
|
|
145
|
+
"sha256": "4fa582ab6a18b538cfe815ab19e73da30c0da061ec661bb12e4060f8b3d2e600"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"destination": "assets/skins/xianxian-living/clips/wait.webm",
|
|
149
|
+
"sha256": "5e2a41e967eed178b8e540231e0b1733bbc388f955204a7b0b76b2aa7cfc4e45"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"destination": "assets/skins/xianxian-living/clips/think.webm",
|
|
153
|
+
"sha256": "4ba9fa0f38076a4abd0d664dc75287b76def7b340dc763b6aab4ea15bbe8abc1"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"destination": "assets/skins/xianxian-living/layers/meta.json",
|
|
157
|
+
"sha256": "92fe5571b2f4b78c1138e199aaa29bb523b6f6ad7ab346fbd6407c8b557e220f"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
70
161
|
}
|