@chatlychat/world 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +120 -2
- package/dist/agent.d.ts +59 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +232 -0
- package/dist/agent.js.map +1 -0
- package/dist/bubble.d.ts +36 -0
- package/dist/bubble.d.ts.map +1 -0
- package/dist/bubble.js +108 -0
- package/dist/bubble.js.map +1 -0
- package/dist/capabilities.d.ts +77 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +118 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cards.d.ts +51 -0
- package/dist/cards.d.ts.map +1 -0
- package/dist/cards.js +136 -0
- package/dist/cards.js.map +1 -0
- package/dist/composer.d.ts +25 -0
- package/dist/composer.d.ts.map +1 -0
- package/dist/composer.js +53 -0
- package/dist/composer.js.map +1 -0
- package/dist/examples/tsconfig.tsbuildinfo +1 -0
- package/dist/handlers.d.ts +26 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +41 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +46 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +89 -0
- package/dist/input.js.map +1 -0
- package/dist/layout.d.ts +24 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +69 -0
- package/dist/layout.js.map +1 -0
- package/dist/model.d.ts +25 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +60 -0
- package/dist/model.js.map +1 -0
- package/dist/motion.d.ts +27 -0
- package/dist/motion.d.ts.map +1 -0
- package/dist/motion.js +65 -0
- package/dist/motion.js.map +1 -0
- package/dist/panel.d.ts +77 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +214 -0
- package/dist/panel.js.map +1 -0
- package/dist/selection.d.ts +14 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +21 -0
- package/dist/selection.js.map +1 -0
- package/dist/text.d.ts +26 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +100 -0
- package/dist/text.js.map +1 -0
- package/dist/theme-apply.d.ts +19 -0
- package/dist/theme-apply.d.ts.map +1 -0
- package/dist/theme-apply.js +88 -0
- package/dist/theme-apply.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/dist/visemes.d.ts +28 -0
- package/dist/visemes.d.ts.map +1 -0
- package/dist/visemes.js +42 -0
- package/dist/visemes.js.map +1 -0
- package/dist/world-api.d.ts +39 -0
- package/dist/world-api.d.ts.map +1 -0
- package/dist/world-api.js +2 -0
- package/dist/world-api.js.map +1 -0
- package/dist/world.d.ts +5 -0
- package/dist/world.d.ts.map +1 -0
- package/dist/world.js +573 -0
- package/dist/world.js.map +1 -0
- package/package.json +67 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @chatlychat/world
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-25
|
|
4
|
+
|
|
5
|
+
Unpublished. The first version: a Chatly conversation rendered into your three.js scene.
|
|
6
|
+
|
|
7
|
+
- `createWorld({ conversation, camera, … })` → `world.root` (add it to your scene) and `world.update(dt)` (call it from your loop).
|
|
8
|
+
- Picks: a `card_grid` laid out as `arc`, `shelf`, `wall` or `table`; `card.model` loaded with three's `GLTFLoader` (scale and anchor honoured); pedestal, beam, price label and selection ring from `worldTheme`. Tapping a pick selects it in the conversation (a postback) and the selection is shared with the host.
|
|
9
|
+
- Speech bubbles anchored to a speaker (never head-locked), scaled by distance, with a typing indicator.
|
|
10
|
+
- A world-space panel and composer; `fixed`, `lazy` and `wrist` follow; a `locked` placement is honoured as `lazy`.
|
|
11
|
+
- Hand, controller (`attachController`), gaze-dwell (`attachGaze`) and pointer (`pickAtPointer`) picking. The host keeps input.
|
|
12
|
+
- An embodied agent body: avatar model, `idle`/`listening`/`thinking`/`speaking` clips, expression and `lookAt` cues, viseme-driven mouth when a message carries `metadata.visemes`.
|
|
13
|
+
- Host capabilities: `world.walk_to`, `avatar.try_on`, `scene.highlight`, `scene.spawn` and custom names, dispatched to handlers you register; bot-fired invocations are answered into the thread; every action keeps its `equivalent`.
|
|
14
|
+
- Replaceable `renderCard`, `renderPick`, `renderBubble`, `renderPanel`, `renderComposer`, `renderAgent`, with a named, versioned object contract (`WORLD_CONTRACT`).
|
|
15
|
+
- `worldTheme` applied to panel, type, cards, speech, agent, sound cues and motion; reduced motion always wins.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chatly Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,121 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @chatlychat/world
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Chatly conversation, rendered into **your** three.js scene. You keep the scene,
|
|
4
|
+
the avatars, the input and the wayfinding; this draws what the conversation
|
|
5
|
+
contains and sends the visitor's taps back.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @chatlychat/world @chatlychat/core three
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`three` (>= 0.160) and `@chatlychat/core` are peer dependencies. There are no
|
|
12
|
+
others, and nothing here touches the web widget.
|
|
13
|
+
|
|
14
|
+
> `@chatlychat/world` is **not yet published to npm**; the install above will 404
|
|
15
|
+
> until it is.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createChatlyCore } from '@chatlychat/core';
|
|
19
|
+
import { createWorld } from '@chatlychat/world';
|
|
20
|
+
import { Clock } from 'three';
|
|
21
|
+
|
|
22
|
+
const core = createChatlyCore({ publicId: 'pub_xxx', socket: io });
|
|
23
|
+
const conversation = await core.startConversation('Do you have this in blue?');
|
|
24
|
+
|
|
25
|
+
const world = createWorld({
|
|
26
|
+
conversation,
|
|
27
|
+
camera, // the camera (or XR camera) the visitor sees through
|
|
28
|
+
theme: core.worldTheme, // the channel's worldTheme
|
|
29
|
+
scene: 'light', // what your scene's light is, for colorScheme "auto"
|
|
30
|
+
reducedMotion: () => false, // return your viewer's reduced-motion setting
|
|
31
|
+
presences: { '*': { name: 'Mia', avatar: { url: 'https://cdn.example/mia.glb' } } },
|
|
32
|
+
});
|
|
33
|
+
scene.add(world.root);
|
|
34
|
+
|
|
35
|
+
const clock = new Clock();
|
|
36
|
+
renderer.setAnimationLoop(() => {
|
|
37
|
+
world.update(clock.getDelta()); // seconds
|
|
38
|
+
renderer.render(scene, camera);
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What it draws
|
|
43
|
+
|
|
44
|
+
- **Picks.** A `card_grid` with `placement` `arc`, `shelf`, `wall` or `table`;
|
|
45
|
+
a card with a `model` becomes a glTF object (`scale` and `anchor` honoured)
|
|
46
|
+
with the theme's pedestal, beam, price label and selection ring. Tapping one
|
|
47
|
+
selects it: a postback to the conversation, a ring on the card, the object in
|
|
48
|
+
hand. `world.selection` is shared — selecting from your own UI is the same event
|
|
49
|
+
(`await world.select(key)`; keys from `world.cardKeys()`).
|
|
50
|
+
- **Speech bubbles** over a speaker, turned to the viewer and scaled by distance —
|
|
51
|
+
positioned from the speaker, never from the camera. Typing indicator per
|
|
52
|
+
`typingLook`. History is not replayed as speech.
|
|
53
|
+
- **A world-space panel and composer.** Never head-locked: `fixed`, `lazy` (an
|
|
54
|
+
eased follow with a dead zone) or `wrist` (carried on a controller you pass).
|
|
55
|
+
`follow: 'locked'` in a placement is honoured as `lazy`.
|
|
56
|
+
Typing is yours: fill `world.composer.setDraft(text)` from your keyboard, mic or
|
|
57
|
+
overlay (`onComposeRequest` fires when the field is pressed), then it sends.
|
|
58
|
+
- **An agent body**: avatar model (glTF; `usdz` is refused with a typed error),
|
|
59
|
+
`idle`/`listening`/`thinking`/`speaking` clips from `presence.clips`, one-shot
|
|
60
|
+
`expressions`, `lookAt` cues resolved through `anchors.resolve(ref)`, and a mouth
|
|
61
|
+
driven by `message.metadata.visemes` (`[{ t: ms, v: 'aa', w?: 0..1 }]`) when
|
|
62
|
+
present. `presence.idle` and `personalSpaceMetres` are yours to honour.
|
|
63
|
+
|
|
64
|
+
## Input
|
|
65
|
+
|
|
66
|
+
The host owns input; the world only knows which of its objects a ray hit.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
world.attachController(xrController); // your XR controller object: trigger or hand pinch
|
|
70
|
+
const gaze = world.attachGaze({ dwellMs: 1500 }); // call gaze.update(ms) per frame
|
|
71
|
+
const hit = world.pickAtPointer(ndcX, ndcY); // mouse / touch
|
|
72
|
+
if (hit) world.activate(hit);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Host capabilities
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
world.capabilities.register(
|
|
79
|
+
'world.walk_to', // also: avatar.try_on, scene.highlight, scene.spawn, or your own
|
|
80
|
+
async () => true, // do your own movement here; return false or throw to refuse
|
|
81
|
+
{ equivalent: { kind: 'text', text: 'The shop is on level 2.' }, unprompted: true },
|
|
82
|
+
);
|
|
83
|
+
await core.setSurface(world.capabilities.surface({ room: 'atrium' }));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A bot-fired invocation reaches your handler and is answered into the thread
|
|
87
|
+
(`done`, or `failed` + `refused: true`). The `equivalent` is required and is
|
|
88
|
+
announced (`announce`) whenever the handler is missing, refuses or throws.
|
|
89
|
+
|
|
90
|
+
## Replaceable renderers
|
|
91
|
+
|
|
92
|
+
`renderCard`, `renderPick`, `renderBubble`, `renderPanel`, `renderComposer`,
|
|
93
|
+
`renderAgent` — each receives the typed block (or message, or agent view), the
|
|
94
|
+
resolved `theme` and `placement`, `reducedMotion`, and for blocks the Chatly
|
|
95
|
+
handlers `select`, `press` and `react`. Return any `Object3D`.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
createWorld({ …, renderers: { contract: 1, renderCard: (p) => yourShelfMesh } });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The world names an override's node `cl3-card` etc. if you did not, and routes
|
|
102
|
+
taps on it to selection (set `userData.cl3Handled = true` to take taps yourself).
|
|
103
|
+
Object names and the renderer set are versioned in `WORLD_CONTRACT`; pinning
|
|
104
|
+
`contract: N` fails loudly on a world that speaks another N.
|
|
105
|
+
|
|
106
|
+
## Theme and reduced motion
|
|
107
|
+
|
|
108
|
+
Every `worldTheme` token is applied: panel material/colour/emissive, type
|
|
109
|
+
(`fontUrl`, weight, outline, `textScale`), cards, speech, agent, sound (as
|
|
110
|
+
`onSound` cues — you play them) and motion. `world.setTheme`, `setScene` and
|
|
111
|
+
`setReducedMotion` re-apply live. **Reduced motion always wins:** entrances and
|
|
112
|
+
trails off, lazy follow and gaze snap, expression clips skipped, clips held on
|
|
113
|
+
their first frame.
|
|
114
|
+
|
|
115
|
+
## Tests without WebGL
|
|
116
|
+
|
|
117
|
+
three builds a scene graph with no GL context, so you can test your integration in
|
|
118
|
+
Node: see `src/world.spec.ts` for a fake conversation and the assertions.
|
|
119
|
+
`pnpm --filter @chatlychat/world example` runs a scripted demo.
|
|
120
|
+
|
|
121
|
+
MIT.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embodied agent's body: a model, four state clips, one-shot expressions,
|
|
3
|
+
* a gaze and a mouth.
|
|
4
|
+
*
|
|
5
|
+
* What it does NOT do is walk. `presence.idle` (`stand`/`wander`/`follow`) and
|
|
6
|
+
* `personalSpaceMetres` are facts about where the agent may go, and wayfinding
|
|
7
|
+
* is the host's — they are readable on `presence` and honoured by nobody here.
|
|
8
|
+
*
|
|
9
|
+
* Reduced motion always wins: state clips are held on their first frame (a
|
|
10
|
+
* pose, not a loop), expression one-shots are not played, gaze snaps rather
|
|
11
|
+
* than turns, and there is no crossfade. The mouth still follows visemes —
|
|
12
|
+
* speech is not decorative motion, and a mouth that never moves on a speaking
|
|
13
|
+
* agent would read as a broken one.
|
|
14
|
+
*/
|
|
15
|
+
import { Group, Vector3, type Camera, type Object3D } from 'three';
|
|
16
|
+
import { type ModelLoader } from './model.js';
|
|
17
|
+
import { type VisemeTrack } from './visemes.js';
|
|
18
|
+
import type { AgentPresenceState, AgentWorldPresence } from '@chatlychat/core';
|
|
19
|
+
import type { ResolvedWorldTheme } from '@chatlychat/core';
|
|
20
|
+
export type GazeTarget = Object3D | Vector3 | 'visitor' | null;
|
|
21
|
+
export declare class AgentBody {
|
|
22
|
+
private theme;
|
|
23
|
+
private readonly loadModel;
|
|
24
|
+
private readonly onError;
|
|
25
|
+
readonly root: Group;
|
|
26
|
+
presence: AgentWorldPresence | null;
|
|
27
|
+
reduced: boolean;
|
|
28
|
+
private model;
|
|
29
|
+
private mixer;
|
|
30
|
+
private clips;
|
|
31
|
+
private current;
|
|
32
|
+
private avatarUrl;
|
|
33
|
+
private tag;
|
|
34
|
+
private gaze;
|
|
35
|
+
private track;
|
|
36
|
+
private trackStart;
|
|
37
|
+
private morphs;
|
|
38
|
+
private lastState;
|
|
39
|
+
private refused;
|
|
40
|
+
constructor(theme: ResolvedWorldTheme, loadModel: ModelLoader, onError: (e: unknown) => void, reduced: boolean);
|
|
41
|
+
get hasModel(): boolean;
|
|
42
|
+
get activeClip(): string | null;
|
|
43
|
+
setTheme(theme: ResolvedWorldTheme): void;
|
|
44
|
+
private placeholder;
|
|
45
|
+
setPresence(presence: AgentWorldPresence | null): void;
|
|
46
|
+
private setTag;
|
|
47
|
+
private swapModel;
|
|
48
|
+
private action;
|
|
49
|
+
/** Move to the state's clip — the host's own name for it, from `presence.clips`. */
|
|
50
|
+
setState(state: AgentPresenceState): void;
|
|
51
|
+
/** Play a one-shot expression clip. Skipped entirely under reduced motion. */
|
|
52
|
+
playExpression(clipName: string | null): boolean;
|
|
53
|
+
lookAt(target: GazeTarget): void;
|
|
54
|
+
setVisemes(track: VisemeTrack | null, nowMs: number): void;
|
|
55
|
+
private applyViseme;
|
|
56
|
+
update(dtMs: number, nowMs: number, camera: Camera): void;
|
|
57
|
+
dispose(): void;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAIL,KAAK,EAIL,OAAO,EAEP,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAEf,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAKxD,OAAO,EAA2B,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAK3D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;AAO/D,qBAAa,SAAS;IAkBlB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAnB1B,SAAgB,IAAI,QAAe;IAC5B,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAQ;IAC3C,OAAO,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,GAAG,CAAyB;IACpC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,OAAO,CAAuB;gBAG5B,KAAK,EAAE,kBAAkB,EAChB,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,EAC9C,OAAO,EAAE,OAAO;IAOlB,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED,IAAW,UAAU,IAAI,MAAM,GAAG,IAAI,CAErC;IAEM,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAIhD,OAAO,CAAC,WAAW;IAWZ,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,GAAG,IAAI;IA6B7D,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,SAAS;IA6BjB,OAAO,CAAC,MAAM;IAQd,oFAAoF;IAC7E,QAAQ,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAiBhD,8EAA8E;IACvE,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IAYhD,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQjE,OAAO,CAAC,WAAW;IAsBZ,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IA4BzD,OAAO,IAAI,IAAI;CAIvB"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embodied agent's body: a model, four state clips, one-shot expressions,
|
|
3
|
+
* a gaze and a mouth.
|
|
4
|
+
*
|
|
5
|
+
* What it does NOT do is walk. `presence.idle` (`stand`/`wander`/`follow`) and
|
|
6
|
+
* `personalSpaceMetres` are facts about where the agent may go, and wayfinding
|
|
7
|
+
* is the host's — they are readable on `presence` and honoured by nobody here.
|
|
8
|
+
*
|
|
9
|
+
* Reduced motion always wins: state clips are held on their first frame (a
|
|
10
|
+
* pose, not a loop), expression one-shots are not played, gaze snaps rather
|
|
11
|
+
* than turns, and there is no crossfade. The mouth still follows visemes —
|
|
12
|
+
* speech is not decorative motion, and a mouth that never moves on a speaking
|
|
13
|
+
* agent would read as a broken one.
|
|
14
|
+
*/
|
|
15
|
+
import { AnimationClip, AnimationMixer, CapsuleGeometry, Group, LoopOnce, Mesh, SphereGeometry, Vector3, } from 'three';
|
|
16
|
+
import { fitModel } from './model.js';
|
|
17
|
+
import { wrapAngle } from './panel.js';
|
|
18
|
+
import { createTextMesh } from './text.js';
|
|
19
|
+
import { accentMaterial } from './theme-apply.js';
|
|
20
|
+
import { WORLD_CONTRACT, WorldError } from './types.js';
|
|
21
|
+
import { morphNamesFor, visemeAt } from './visemes.js';
|
|
22
|
+
const TURN_RATE = 6;
|
|
23
|
+
const FADE_S = 0.25;
|
|
24
|
+
export class AgentBody {
|
|
25
|
+
theme;
|
|
26
|
+
loadModel;
|
|
27
|
+
onError;
|
|
28
|
+
root = new Group();
|
|
29
|
+
presence = null;
|
|
30
|
+
reduced;
|
|
31
|
+
model = null;
|
|
32
|
+
mixer = null;
|
|
33
|
+
clips = [];
|
|
34
|
+
current = null;
|
|
35
|
+
avatarUrl = null;
|
|
36
|
+
tag = null;
|
|
37
|
+
gaze = null;
|
|
38
|
+
track = null;
|
|
39
|
+
trackStart = 0;
|
|
40
|
+
morphs = [];
|
|
41
|
+
lastState = null;
|
|
42
|
+
refused = null;
|
|
43
|
+
constructor(theme, loadModel, onError, reduced) {
|
|
44
|
+
this.theme = theme;
|
|
45
|
+
this.loadModel = loadModel;
|
|
46
|
+
this.onError = onError;
|
|
47
|
+
this.reduced = reduced;
|
|
48
|
+
this.root.name = WORLD_CONTRACT.objects.agent;
|
|
49
|
+
this.root.add(this.placeholder());
|
|
50
|
+
}
|
|
51
|
+
get hasModel() {
|
|
52
|
+
return this.model !== null;
|
|
53
|
+
}
|
|
54
|
+
get activeClip() {
|
|
55
|
+
return this.current?.getClip().name ?? null;
|
|
56
|
+
}
|
|
57
|
+
setTheme(theme) {
|
|
58
|
+
this.theme = theme;
|
|
59
|
+
}
|
|
60
|
+
placeholder() {
|
|
61
|
+
const g = new Group();
|
|
62
|
+
g.name = 'cl3-agent-placeholder';
|
|
63
|
+
const body = new Mesh(new CapsuleGeometry(0.2, 0.7, 4, 12), accentMaterial(this.theme));
|
|
64
|
+
body.position.y = 0.55;
|
|
65
|
+
const head = new Mesh(new SphereGeometry(0.17, 16, 12), accentMaterial(this.theme));
|
|
66
|
+
head.position.y = 1.3;
|
|
67
|
+
g.add(body, head);
|
|
68
|
+
return g;
|
|
69
|
+
}
|
|
70
|
+
setPresence(presence) {
|
|
71
|
+
this.presence = presence;
|
|
72
|
+
this.setTag(presence === null ? null : (presence.nameTag ?? presence.name));
|
|
73
|
+
const a = presence?.avatar;
|
|
74
|
+
if (a?.format === 'usdz' && this.refused !== a.url) {
|
|
75
|
+
this.refused = a.url;
|
|
76
|
+
this.onError(new WorldError('unsupported_format', 'usdz avatars are not loadable by three.js; showing the placeholder'));
|
|
77
|
+
}
|
|
78
|
+
const url = a !== undefined && a.format !== 'usdz' ? a.url : null;
|
|
79
|
+
if (url === this.avatarUrl) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.avatarUrl = url;
|
|
83
|
+
if (url === null || a === undefined) {
|
|
84
|
+
this.swapModel(null, []);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.loadModel(url).then((m) => {
|
|
88
|
+
if (this.avatarUrl === url) {
|
|
89
|
+
this.swapModel(fitModel(m.scene, a.scale ?? 1, a.anchor ?? 'base'), m.animations);
|
|
90
|
+
}
|
|
91
|
+
}, this.onError);
|
|
92
|
+
}
|
|
93
|
+
setTag(text) {
|
|
94
|
+
if (this.tag !== null) {
|
|
95
|
+
this.root.remove(this.tag);
|
|
96
|
+
this.tag = null;
|
|
97
|
+
}
|
|
98
|
+
if (text !== null) {
|
|
99
|
+
this.tag = createTextMesh(text, this.theme, { maxWidthMetres: 0.8, size: 0.8 });
|
|
100
|
+
this.tag.name = 'cl3-agent-nametag';
|
|
101
|
+
this.tag.position.y = 1.75;
|
|
102
|
+
this.root.add(this.tag);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
swapModel(model, clips) {
|
|
106
|
+
this.mixer?.stopAllAction();
|
|
107
|
+
this.mixer = null;
|
|
108
|
+
this.current = null;
|
|
109
|
+
for (const c of [...this.root.children]) {
|
|
110
|
+
if (c !== this.tag) {
|
|
111
|
+
this.root.remove(c);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this.model = model;
|
|
115
|
+
this.clips = clips;
|
|
116
|
+
this.morphs = [];
|
|
117
|
+
if (model === null) {
|
|
118
|
+
this.root.add(this.placeholder());
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
this.root.add(model);
|
|
122
|
+
model.traverse((o) => {
|
|
123
|
+
if (o.morphTargetDictionary !== undefined) {
|
|
124
|
+
this.morphs.push(o);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
this.mixer = new AnimationMixer(model);
|
|
128
|
+
// The state was set before the model arrived; its clip could not play then.
|
|
129
|
+
if (this.lastState !== null) {
|
|
130
|
+
this.setState(this.lastState);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
action(name) {
|
|
134
|
+
if (this.mixer === null || name === null || name === undefined) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const clip = AnimationClip.findByName([...this.clips], name);
|
|
138
|
+
return clip === null ? null : this.mixer.clipAction(clip);
|
|
139
|
+
}
|
|
140
|
+
/** Move to the state's clip — the host's own name for it, from `presence.clips`. */
|
|
141
|
+
setState(state) {
|
|
142
|
+
this.lastState = state;
|
|
143
|
+
const next = this.action(this.presence?.clips?.[state]);
|
|
144
|
+
if (next === null || next === this.current) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
next.reset();
|
|
148
|
+
next.play();
|
|
149
|
+
if (this.reduced) {
|
|
150
|
+
next.paused = true; // a held pose, not a loop
|
|
151
|
+
this.current?.stop();
|
|
152
|
+
}
|
|
153
|
+
else if (this.current !== null) {
|
|
154
|
+
next.crossFadeFrom(this.current, FADE_S, false);
|
|
155
|
+
}
|
|
156
|
+
this.current = next;
|
|
157
|
+
}
|
|
158
|
+
/** Play a one-shot expression clip. Skipped entirely under reduced motion. */
|
|
159
|
+
playExpression(clipName) {
|
|
160
|
+
const a = this.reduced ? null : this.action(clipName);
|
|
161
|
+
if (a === null) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
a.reset();
|
|
165
|
+
a.setLoop(LoopOnce, 1);
|
|
166
|
+
a.clampWhenFinished = true;
|
|
167
|
+
a.play();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
lookAt(target) {
|
|
171
|
+
this.gaze = target;
|
|
172
|
+
}
|
|
173
|
+
setVisemes(track, nowMs) {
|
|
174
|
+
this.track = track;
|
|
175
|
+
this.trackStart = nowMs;
|
|
176
|
+
if (track === null) {
|
|
177
|
+
this.applyViseme(null, 0);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
applyViseme(v, w) {
|
|
181
|
+
for (const m of this.morphs) {
|
|
182
|
+
const dict = m.morphTargetDictionary;
|
|
183
|
+
const inf = m.morphTargetInfluences;
|
|
184
|
+
if (dict === undefined || inf === undefined) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Zero every viseme-ish target first so a released mouth closes.
|
|
188
|
+
for (const [name, idx] of Object.entries(dict)) {
|
|
189
|
+
if (/^(viseme_|Viseme_)/.test(name) || ['aa', 'ih', 'ou', 'ee', 'oh'].includes(name)) {
|
|
190
|
+
inf[idx] = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (v !== null) {
|
|
194
|
+
const hit = morphNamesFor(v).find((n) => dict[n] !== undefined);
|
|
195
|
+
if (hit !== undefined) {
|
|
196
|
+
inf[dict[hit]] = w;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
update(dtMs, nowMs, camera) {
|
|
202
|
+
this.mixer?.update(dtMs / 1000);
|
|
203
|
+
if (this.track !== null) {
|
|
204
|
+
const e = visemeAt(this.track, nowMs - this.trackStart);
|
|
205
|
+
this.applyViseme(e?.v ?? null, e?.w ?? 0);
|
|
206
|
+
}
|
|
207
|
+
if (this.gaze !== null) {
|
|
208
|
+
const here = this.root.getWorldPosition(new Vector3());
|
|
209
|
+
const at = this.gaze === 'visitor'
|
|
210
|
+
? camera.getWorldPosition(new Vector3())
|
|
211
|
+
: this.gaze instanceof Vector3
|
|
212
|
+
? this.gaze
|
|
213
|
+
: this.gaze.getWorldPosition(new Vector3());
|
|
214
|
+
const want = Math.atan2(at.x - here.x, at.z - here.z);
|
|
215
|
+
const err = wrapAngle(want - this.root.rotation.y);
|
|
216
|
+
const step = TURN_RATE * (dtMs / 1000);
|
|
217
|
+
this.root.rotation.y =
|
|
218
|
+
this.reduced || Math.abs(err) <= step ? want : this.root.rotation.y + Math.sign(err) * step;
|
|
219
|
+
}
|
|
220
|
+
if (this.tag !== null) {
|
|
221
|
+
// The tag turns to the viewer regardless of where the body faces.
|
|
222
|
+
const cam = camera.getWorldPosition(new Vector3());
|
|
223
|
+
const t = this.tag.getWorldPosition(new Vector3());
|
|
224
|
+
this.tag.rotation.y = Math.atan2(cam.x - t.x, cam.z - t.z) - this.root.rotation.y;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
dispose() {
|
|
228
|
+
this.mixer?.stopAllAction();
|
|
229
|
+
this.root.removeFromParent();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/bubble.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech bubbles, anchored to a speaker in space.
|
|
3
|
+
*
|
|
4
|
+
* A bubble is positioned from its SPEAKER'S world position — never from the
|
|
5
|
+
* camera, so it cannot be head-locked — and only turned to face the viewer.
|
|
6
|
+
* Its size grows with distance (clamped) so type stays legible across a room,
|
|
7
|
+
* which is why the text itself is built at one size and the whole bubble is
|
|
8
|
+
* scaled.
|
|
9
|
+
*/
|
|
10
|
+
import { Group, Vector3, type Camera, type Object3D } from 'three';
|
|
11
|
+
import type { BubbleRenderProps, ConversationMessage, ResolvedWorldTheme } from '@chatlychat/core';
|
|
12
|
+
export declare const MAX_BUBBLES = 3;
|
|
13
|
+
/** Uniform scale for a bubble `distance` metres from the viewer. */
|
|
14
|
+
export declare const bubbleScale: (distance: number) => number;
|
|
15
|
+
export declare function defaultBubble(props: BubbleRenderProps): Object3D;
|
|
16
|
+
/** The typing indicator: three dots, one pulsing dot, or nothing (`avatar`/`none` live on the body). */
|
|
17
|
+
export declare function createTyping(theme: ResolvedWorldTheme): Group;
|
|
18
|
+
/** Bob or pulse the dots. With reduced motion they sit still: a static ellipsis still says "typing". */
|
|
19
|
+
export declare function animateTyping(typing: Object3D, nowMs: number, reduced: boolean): void;
|
|
20
|
+
/** World-space point above a speaker's head. */
|
|
21
|
+
export declare function headPoint(speaker: Object3D): Vector3;
|
|
22
|
+
export interface LiveBubble {
|
|
23
|
+
readonly messageId: string;
|
|
24
|
+
readonly speaker: Object3D;
|
|
25
|
+
node: Object3D;
|
|
26
|
+
body: string;
|
|
27
|
+
readonly bornAt: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Re-anchor `node` over `speaker`: stack slot `slot` above the head, turned to
|
|
31
|
+
* the camera, scaled by distance. Called every frame, so a walking speaker
|
|
32
|
+
* carries its bubble.
|
|
33
|
+
*/
|
|
34
|
+
export declare function anchorBubble(node: Object3D, speaker: Object3D, camera: Camera, slot: number, parent: Object3D): void;
|
|
35
|
+
export declare const speakerKind: (m: ConversationMessage) => "visitor" | "agent" | null;
|
|
36
|
+
//# sourceMappingURL=bubble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bubble.d.ts","sourceRoot":"","sources":["../src/bubble.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,EAGL,OAAO,EACP,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAMf,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAOnG,eAAO,MAAM,WAAW,IAAI,CAAC;AAE7B,oEAAoE;AACpE,eAAO,MAAM,WAAW,aAAc,MAAM,KAAG,MACwB,CAAC;AAExE,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,QAAQ,CAwBhE;AAED,wGAAwG;AACxG,wBAAgB,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,KAAK,CAW7D;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAiBrF;AAED,gDAAgD;AAChD,wBAAgB,SAAS,CAAC,OAAO,EAAE,QAAQ,GAAG,OAAO,CAKpD;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,QAAQ,GACf,IAAI,CAUN;AAED,eAAO,MAAM,WAAW,MAAO,mBAAmB,KAAG,SAAS,GAAG,OAAO,GAAG,IAK/D,CAAC"}
|
package/dist/bubble.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech bubbles, anchored to a speaker in space.
|
|
3
|
+
*
|
|
4
|
+
* A bubble is positioned from its SPEAKER'S world position — never from the
|
|
5
|
+
* camera, so it cannot be head-locked — and only turned to face the viewer.
|
|
6
|
+
* Its size grows with distance (clamped) so type stays legible across a room,
|
|
7
|
+
* which is why the text itself is built at one size and the whole bubble is
|
|
8
|
+
* scaled.
|
|
9
|
+
*/
|
|
10
|
+
import { Box3, ConeGeometry, Group, Mesh, SphereGeometry, Vector3, } from 'three';
|
|
11
|
+
import { createTextMesh } from './text.js';
|
|
12
|
+
import { accentMaterial, bubbleBacking } from './theme-apply.js';
|
|
13
|
+
import { WORLD_CONTRACT } from './types.js';
|
|
14
|
+
const REFERENCE_METRES = 2;
|
|
15
|
+
const SCALE_MIN = 0.6;
|
|
16
|
+
const SCALE_MAX = 2.5;
|
|
17
|
+
const HEAD_FALLBACK = 1.9;
|
|
18
|
+
const STACK_STEP = 0.22;
|
|
19
|
+
export const MAX_BUBBLES = 3;
|
|
20
|
+
/** Uniform scale for a bubble `distance` metres from the viewer. */
|
|
21
|
+
export const bubbleScale = (distance) => Math.min(SCALE_MAX, Math.max(SCALE_MIN, distance / REFERENCE_METRES));
|
|
22
|
+
export function defaultBubble(props) {
|
|
23
|
+
const { theme } = props;
|
|
24
|
+
const g = new Group();
|
|
25
|
+
g.name = WORLD_CONTRACT.objects.bubble;
|
|
26
|
+
const text = createTextMesh(props.message.body, theme, {
|
|
27
|
+
maxWidthMetres: theme.bubbleMaxWidthMetres,
|
|
28
|
+
});
|
|
29
|
+
const [w, h] = text.userData['size'];
|
|
30
|
+
const pad = 0.03;
|
|
31
|
+
const plate = bubbleBacking(theme, w + pad * 2, h + pad * 2);
|
|
32
|
+
if (plate !== null) {
|
|
33
|
+
g.add(plate);
|
|
34
|
+
if (theme.bubbleTail && theme.bubbleShape !== 'cloud') {
|
|
35
|
+
const tail = new Mesh(new ConeGeometry(0.025, 0.05, 3), plate.material);
|
|
36
|
+
tail.name = 'cl3-bubble-tail';
|
|
37
|
+
tail.position.set(0, -(h / 2 + pad + 0.02), 0);
|
|
38
|
+
tail.rotation.set(0, 0, Math.PI);
|
|
39
|
+
g.add(tail);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
text.position.z = 0.002;
|
|
43
|
+
g.add(text);
|
|
44
|
+
g.userData['size'] = [w + pad * 2, h + pad * 2];
|
|
45
|
+
return g;
|
|
46
|
+
}
|
|
47
|
+
/** The typing indicator: three dots, one pulsing dot, or nothing (`avatar`/`none` live on the body). */
|
|
48
|
+
export function createTyping(theme) {
|
|
49
|
+
const g = new Group();
|
|
50
|
+
g.name = WORLD_CONTRACT.objects.typing;
|
|
51
|
+
g.userData['look'] = theme.typingLook;
|
|
52
|
+
const dots = theme.typingLook === 'dots' ? 3 : theme.typingLook === 'pulse' ? 1 : 0;
|
|
53
|
+
for (let i = 0; i < dots; i += 1) {
|
|
54
|
+
const d = new Mesh(new SphereGeometry(0.02, 12, 8), accentMaterial(theme));
|
|
55
|
+
d.position.x = (i - (dots - 1) / 2) * 0.06;
|
|
56
|
+
g.add(d);
|
|
57
|
+
}
|
|
58
|
+
return g;
|
|
59
|
+
}
|
|
60
|
+
/** Bob or pulse the dots. With reduced motion they sit still: a static ellipsis still says "typing". */
|
|
61
|
+
export function animateTyping(typing, nowMs, reduced) {
|
|
62
|
+
if (reduced) {
|
|
63
|
+
typing.children.forEach((c) => {
|
|
64
|
+
c.position.y = 0;
|
|
65
|
+
c.scale.setScalar(1);
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const pulse = typing.userData['look'] === 'pulse';
|
|
70
|
+
typing.children.forEach((c, i) => {
|
|
71
|
+
const phase = nowMs / 300 - i * 0.6;
|
|
72
|
+
if (pulse) {
|
|
73
|
+
c.scale.setScalar(1 + 0.4 * Math.sin(phase));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
c.position.y = 0.015 * Math.sin(phase);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** World-space point above a speaker's head. */
|
|
81
|
+
export function headPoint(speaker) {
|
|
82
|
+
speaker.updateWorldMatrix(true, true);
|
|
83
|
+
const box = new Box3().setFromObject(speaker);
|
|
84
|
+
const p = speaker.getWorldPosition(new Vector3());
|
|
85
|
+
return box.isEmpty() ? p.setY(p.y + HEAD_FALLBACK) : new Vector3(p.x, box.max.y + 0.2, p.z);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Re-anchor `node` over `speaker`: stack slot `slot` above the head, turned to
|
|
89
|
+
* the camera, scaled by distance. Called every frame, so a walking speaker
|
|
90
|
+
* carries its bubble.
|
|
91
|
+
*/
|
|
92
|
+
export function anchorBubble(node, speaker, camera, slot, parent) {
|
|
93
|
+
const head = headPoint(speaker);
|
|
94
|
+
const cam = camera.getWorldPosition(new Vector3());
|
|
95
|
+
const scale = bubbleScale(head.distanceTo(cam));
|
|
96
|
+
head.y += (slot * STACK_STEP + 0.1) * scale;
|
|
97
|
+
parent.worldToLocal(head);
|
|
98
|
+
node.position.copy(head);
|
|
99
|
+
const camLocal = parent.worldToLocal(cam.clone());
|
|
100
|
+
node.rotation.set(0, Math.atan2(camLocal.x - head.x, camLocal.z - head.z), 0);
|
|
101
|
+
node.scale.setScalar(scale);
|
|
102
|
+
}
|
|
103
|
+
export const speakerKind = (m) => m.authorType === 'contact'
|
|
104
|
+
? 'visitor'
|
|
105
|
+
: m.authorType === 'agent' || m.authorType === 'bot'
|
|
106
|
+
? 'agent'
|
|
107
|
+
: null;
|
|
108
|
+
//# sourceMappingURL=bubble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bubble.js","sourceRoot":"","sources":["../src/bubble.ts"],"names":[],"mappings":"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"}
|