@chatlychat/world 0.0.0-stage → 0.2.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +184 -2
  4. package/dist/agent.d.ts +63 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +251 -0
  7. package/dist/agent.js.map +1 -0
  8. package/dist/bubble.d.ts +36 -0
  9. package/dist/bubble.d.ts.map +1 -0
  10. package/dist/bubble.js +108 -0
  11. package/dist/bubble.js.map +1 -0
  12. package/dist/capabilities.d.ts +77 -0
  13. package/dist/capabilities.d.ts.map +1 -0
  14. package/dist/capabilities.js +129 -0
  15. package/dist/capabilities.js.map +1 -0
  16. package/dist/cards.d.ts +51 -0
  17. package/dist/cards.d.ts.map +1 -0
  18. package/dist/cards.js +136 -0
  19. package/dist/cards.js.map +1 -0
  20. package/dist/composer.d.ts +25 -0
  21. package/dist/composer.d.ts.map +1 -0
  22. package/dist/composer.js +53 -0
  23. package/dist/composer.js.map +1 -0
  24. package/dist/handlers.d.ts +26 -0
  25. package/dist/handlers.d.ts.map +1 -0
  26. package/dist/handlers.js +41 -0
  27. package/dist/handlers.js.map +1 -0
  28. package/dist/index.d.ts +27 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +25 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/input.d.ts +46 -0
  33. package/dist/input.d.ts.map +1 -0
  34. package/dist/input.js +89 -0
  35. package/dist/input.js.map +1 -0
  36. package/dist/layout.d.ts +24 -0
  37. package/dist/layout.d.ts.map +1 -0
  38. package/dist/layout.js +69 -0
  39. package/dist/layout.js.map +1 -0
  40. package/dist/model.d.ts +57 -0
  41. package/dist/model.d.ts.map +1 -0
  42. package/dist/model.js +86 -0
  43. package/dist/model.js.map +1 -0
  44. package/dist/motion.d.ts +27 -0
  45. package/dist/motion.d.ts.map +1 -0
  46. package/dist/motion.js +65 -0
  47. package/dist/motion.js.map +1 -0
  48. package/dist/panel.d.ts +77 -0
  49. package/dist/panel.d.ts.map +1 -0
  50. package/dist/panel.js +214 -0
  51. package/dist/panel.js.map +1 -0
  52. package/dist/picks.d.ts +45 -0
  53. package/dist/picks.d.ts.map +1 -0
  54. package/dist/picks.js +186 -0
  55. package/dist/picks.js.map +1 -0
  56. package/dist/selection.d.ts +14 -0
  57. package/dist/selection.d.ts.map +1 -0
  58. package/dist/selection.js +21 -0
  59. package/dist/selection.js.map +1 -0
  60. package/dist/text.d.ts +26 -0
  61. package/dist/text.d.ts.map +1 -0
  62. package/dist/text.js +100 -0
  63. package/dist/text.js.map +1 -0
  64. package/dist/theme-apply.d.ts +19 -0
  65. package/dist/theme-apply.d.ts.map +1 -0
  66. package/dist/theme-apply.js +88 -0
  67. package/dist/theme-apply.js.map +1 -0
  68. package/dist/types.d.ts +108 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +41 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/visemes.d.ts +28 -0
  73. package/dist/visemes.d.ts.map +1 -0
  74. package/dist/visemes.js +42 -0
  75. package/dist/visemes.js.map +1 -0
  76. package/dist/vrm.d.ts +97 -0
  77. package/dist/vrm.d.ts.map +1 -0
  78. package/dist/vrm.js +212 -0
  79. package/dist/vrm.js.map +1 -0
  80. package/dist/world-api.d.ts +39 -0
  81. package/dist/world-api.d.ts.map +1 -0
  82. package/dist/world-api.js +2 -0
  83. package/dist/world-api.js.map +1 -0
  84. package/dist/world.d.ts +5 -0
  85. package/dist/world.d.ts.map +1 -0
  86. package/dist/world.js +414 -0
  87. package/dist/world.js.map +1 -0
  88. package/package.json +75 -3
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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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Host capabilities: what the visitor's world can DO, declared to the bot and
3
+ * dispatched to handlers the host registers.
4
+ *
5
+ * The world never walks, dresses or spawns anything itself — those are the
6
+ * host's. This surfaces the four standard intents as typed handlers and keeps
7
+ * the contract every capability carries:
8
+ *
9
+ * - an `equivalent` is REQUIRED at registration, because a bot's offer must
10
+ * mean something to a visitor whose world cannot do it. It is announced
11
+ * whenever a handler is missing, declines or throws;
12
+ * - the host is the gate: a handler may return `false` or throw to refuse, and
13
+ * a refusal is reported as `failed` with `refused: true` (two refusals in ten
14
+ * minutes stop the bot firing it — server-side);
15
+ * - args are checked here against the declared shape before a handler sees
16
+ * them, because a bot-fired invocation is data from outside.
17
+ */
18
+ import type { ConversationLike } from './types.js';
19
+ import type { CapabilityEquivalent, HostCapabilityDeclaration, HostSurface, HostSurfaceState } from '@chatlychat/core';
20
+ /** The standard intents and their arguments. Names are verbs, never positions. */
21
+ export interface WorldActionMap {
22
+ 'world.walk_to': {
23
+ place: string;
24
+ };
25
+ 'avatar.try_on': {
26
+ item: string;
27
+ };
28
+ 'scene.highlight': {
29
+ ref: string;
30
+ };
31
+ 'scene.spawn': {
32
+ ref: string;
33
+ };
34
+ }
35
+ export type StandardAction = keyof WorldActionMap;
36
+ /** What a handler may answer. `false` or a throw is a refusal; `void`/`true` is done. */
37
+ export type ActionOutcome = void | boolean | {
38
+ readonly ok: boolean;
39
+ readonly text?: string;
40
+ };
41
+ export interface ActionContext {
42
+ readonly equivalent: CapabilityEquivalent;
43
+ /** True when the bot fired it without a press. */
44
+ readonly unprompted: boolean;
45
+ }
46
+ export type ActionHandler<A extends Readonly<Record<string, unknown>>> = (args: A, ctx: ActionContext) => ActionOutcome | Promise<ActionOutcome>;
47
+ export interface RegisterOptions {
48
+ /** What a person or screen reader gets instead of the world doing it. Required. */
49
+ readonly equivalent: CapabilityEquivalent;
50
+ /** Let the bot fire it without a press. Off by default. */
51
+ readonly unprompted?: boolean;
52
+ readonly label?: string;
53
+ readonly description?: string;
54
+ /** Required for a custom (non-standard) name; a small JSON-Schema subset. */
55
+ readonly args?: Readonly<Record<string, unknown>>;
56
+ }
57
+ export interface ActionReport {
58
+ readonly status: 'done' | 'failed';
59
+ readonly refused: boolean;
60
+ readonly text: string | null;
61
+ }
62
+ export declare class CapabilityRegistry {
63
+ private readonly announce;
64
+ private readonly entries;
65
+ constructor(announce?: (text: string) => void);
66
+ register<N extends StandardAction>(name: N, handler: ActionHandler<WorldActionMap[N]>, opts: RegisterOptions): () => void;
67
+ register(name: string, handler: ActionHandler<Readonly<Record<string, unknown>>>, opts: RegisterOptions): () => void;
68
+ declarations(): readonly HostCapabilityDeclaration[];
69
+ /** The surface to hand `core.setSurface`. */
70
+ surface(state?: HostSurfaceState, kind?: string): HostSurface;
71
+ has(name: string): boolean;
72
+ /** Run a handler, with every failure mode folded into a report. Never throws. */
73
+ invoke(name: string, args: Readonly<Record<string, unknown>>, unprompted: boolean): Promise<ActionReport>;
74
+ /** Route a bot-fired invocation to its handler and answer it into the thread. */
75
+ bind(conversation: ConversationLike): () => void;
76
+ }
77
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ const STANDARD = {
2
+ 'world.walk_to': {
3
+ label: 'Take me there',
4
+ description: 'Walk the visitor to a named place.',
5
+ arg: 'place',
6
+ },
7
+ 'avatar.try_on': {
8
+ label: 'Try it on',
9
+ description: "Put an item on the visitor's avatar.",
10
+ arg: 'item',
11
+ },
12
+ 'scene.highlight': {
13
+ label: 'Show me',
14
+ description: 'Highlight a named object in the scene.',
15
+ arg: 'ref',
16
+ },
17
+ 'scene.spawn': {
18
+ label: 'Bring it here',
19
+ description: 'Place a named object in front of the visitor.',
20
+ arg: 'ref',
21
+ },
22
+ };
23
+ const ARG_MAX = 40;
24
+ const equivalentText = (e) => e.kind === 'text' ? e.text : (e.text ?? e.href);
25
+ export class CapabilityRegistry {
26
+ announce;
27
+ entries = new Map();
28
+ constructor(announce = () => undefined) {
29
+ this.announce = announce;
30
+ }
31
+ register(name, handler, opts) {
32
+ const std = STANDARD[name];
33
+ const decl = {
34
+ name,
35
+ label: opts.label ?? std?.label ?? name,
36
+ ...((opts.description ?? std?.description)
37
+ ? { description: (opts.description ?? std?.description) }
38
+ : {}),
39
+ ...(opts.args !== undefined || std !== undefined
40
+ ? {
41
+ args: opts.args ?? {
42
+ type: 'object',
43
+ properties: { [std?.arg]: { type: 'string', maxLength: ARG_MAX } },
44
+ required: [std?.arg],
45
+ },
46
+ }
47
+ : {}),
48
+ ...(opts.unprompted === true ? { unprompted: true } : {}),
49
+ equivalent: opts.equivalent,
50
+ };
51
+ this.entries.set(name, {
52
+ decl,
53
+ handler: handler,
54
+ argName: std?.arg ?? null,
55
+ });
56
+ return () => {
57
+ this.entries.delete(name);
58
+ };
59
+ }
60
+ declarations() {
61
+ return [...this.entries.values()].map((e) => e.decl);
62
+ }
63
+ /** The surface to hand `core.setSurface`. */
64
+ surface(state, kind = 'world') {
65
+ return { kind, capabilities: this.declarations(), ...(state === undefined ? {} : { state }) };
66
+ }
67
+ has(name) {
68
+ return this.entries.has(name);
69
+ }
70
+ /** Run a handler, with every failure mode folded into a report. Never throws. */
71
+ async invoke(name, args, unprompted) {
72
+ const e = this.entries.get(name);
73
+ if (e === undefined) {
74
+ return { status: 'failed', refused: true, text: null };
75
+ }
76
+ const fail = (refused) => {
77
+ this.announce(equivalentText(e.decl.equivalent));
78
+ return { status: 'failed', refused, text: equivalentText(e.decl.equivalent) };
79
+ };
80
+ if (e.argName !== null) {
81
+ const v = args[e.argName];
82
+ if (typeof v !== 'string' || v === '' || v.length > ARG_MAX) {
83
+ return fail(false);
84
+ }
85
+ }
86
+ try {
87
+ const out = await e.handler(args, { equivalent: e.decl.equivalent, unprompted });
88
+ const ok = out === undefined || out === true || (typeof out === 'object' && out.ok);
89
+ if (!ok) {
90
+ return fail(true);
91
+ }
92
+ return {
93
+ status: 'done',
94
+ refused: false,
95
+ text: typeof out === 'object' ? (out.text ?? null) : null,
96
+ };
97
+ }
98
+ catch {
99
+ return fail(true);
100
+ }
101
+ }
102
+ /** Route a bot-fired invocation to its handler and answer it into the thread. */
103
+ bind(conversation) {
104
+ return conversation.onCapability((inv) => {
105
+ /*
106
+ * The operator's switch, as a second wall. The core already drops a
107
+ * bot-fired capability when the space turned them off
108
+ * (`Conversation.setUnpromptedAllowed`); this keeps a stand-in
109
+ * conversation, or a core older than the switch, from running a
110
+ * handler for one. Read structurally because `ConversationLike` is a
111
+ * fixed pick of the core class.
112
+ */
113
+ if (conversation.acceptsUnprompted === false) {
114
+ return;
115
+ }
116
+ void this.invoke(inv.capability, inv.args, true)
117
+ .then((r) => conversation.reportAction({
118
+ messageId: inv.messageId,
119
+ actionId: inv.invocationId,
120
+ status: r.status,
121
+ ...(r.text === null ? {} : { text: r.text }),
122
+ ...(r.refused ? { data: { refused: true } } : {}),
123
+ }))
124
+ // Reporting is best-effort: the visitor already saw the announcement.
125
+ .catch(() => undefined);
126
+ });
127
+ }
128
+ }
129
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Picks: a `card_grid` rendered as objects in the scene.
3
+ *
4
+ * `buildGrid` lays the cards out (`layout.ts`), asks the card renderer — the
5
+ * default below or a host's override — for each one and parents them. The
6
+ * default card is a plate with title, subtitle and price label, a glTF model
7
+ * if the card has one, and the theme's pedestal, beam and selection ring.
8
+ */
9
+ import { Group, type Object3D } from 'three';
10
+ import { type GridPlacement, type Mount } from './layout.js';
11
+ import { type ModelAnchor, type ModelLoader } from './model.js';
12
+ import type { BlockOf, CardRenderProps, ConversationMessage, ResolvedWorldTheme } from '@chatlychat/core';
13
+ export interface CardData {
14
+ readonly title: string;
15
+ readonly subtitle: string | null;
16
+ readonly price: {
17
+ readonly amount: number;
18
+ readonly currency: string;
19
+ } | null;
20
+ readonly model: {
21
+ readonly url: string;
22
+ readonly format?: string;
23
+ readonly scale?: number;
24
+ readonly anchor?: ModelAnchor;
25
+ } | null;
26
+ }
27
+ /** Read the fields a world draws off a card block, tolerantly: the block is wire data. */
28
+ export declare function readCard(block: Readonly<Record<string, unknown>>): CardData;
29
+ /** Minor units to a locale string. The fraction digits come from the currency, not from "100". */
30
+ export declare function formatPrice(amount: number, currency: string, locale?: string): string;
31
+ export interface CardDeps {
32
+ readonly loadModel: ModelLoader;
33
+ readonly onError: (e: unknown) => void;
34
+ }
35
+ export declare const cardKey: (message: ConversationMessage, block: Readonly<Record<string, unknown>>, index: number) => string;
36
+ /** The default `renderCard`. */
37
+ export declare function defaultCard(props: CardRenderProps, deps: CardDeps): Object3D;
38
+ /** Whether a card node is drawn selected — the default's ring, readable by tests and hosts. */
39
+ export declare function isHighlighted(node: Object3D): boolean;
40
+ export interface GridBuild {
41
+ readonly group: Group;
42
+ readonly mount: Mount;
43
+ /** card key -> node, so one card can be redrawn when its selection changes. */
44
+ readonly nodes: Map<string, {
45
+ node: Object3D;
46
+ index: number;
47
+ }>;
48
+ }
49
+ export declare function buildGrid(placement: GridPlacement, count: number, theme: ResolvedWorldTheme, draw: (index: number) => Object3D, keyOf: (index: number) => string): GridBuild;
50
+ export type { BlockOf };
51
+ //# sourceMappingURL=cards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,KAAK,EAKL,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAEf,OAAO,EAAe,KAAK,aAAa,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAY,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAK1E,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAK1B,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9E,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;KAC/B,GAAG,IAAI,CAAC;CACV;AAED,0FAA0F;AAC1F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,QAAQ,CAsB3E;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAOrF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAED,eAAO,MAAM,OAAO,YACT,mBAAmB,SACrB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,SACjC,MAAM,KACZ,MAA0F,CAAC;AAE9F,gCAAgC;AAChC,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CA2E5E;AAED,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAErD;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,wBAAgB,SAAS,CACvB,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,QAAQ,EACjC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAC/B,SAAS,CAcX;AAED,YAAY,EAAE,OAAO,EAAE,CAAC"}
package/dist/cards.js ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Picks: a `card_grid` rendered as objects in the scene.
3
+ *
4
+ * `buildGrid` lays the cards out (`layout.ts`), asks the card renderer — the
5
+ * default below or a host's override — for each one and parents them. The
6
+ * default card is a plate with title, subtitle and price label, a glTF model
7
+ * if the card has one, and the theme's pedestal, beam and selection ring.
8
+ */
9
+ import { CylinderGeometry, Group, Mesh, MeshBasicMaterial, RingGeometry, DoubleSide, } from 'three';
10
+ import { layoutCards } from './layout.js';
11
+ import { fitModel } from './model.js';
12
+ import { createTextMesh } from './text.js';
13
+ import { accentMaterial, panelBacking } from './theme-apply.js';
14
+ import { WORLD_CONTRACT } from './types.js';
15
+ const PLATE_W = 0.28;
16
+ const PLATE_H = 0.2;
17
+ /** Read the fields a world draws off a card block, tolerantly: the block is wire data. */
18
+ export function readCard(block) {
19
+ const s = (k) => typeof block[k] === 'string' && block[k] !== '' ? block[k] : null;
20
+ const p = block['price'];
21
+ const m = block['model'];
22
+ return {
23
+ title: s('title') ?? '',
24
+ subtitle: s('subtitle'),
25
+ price: typeof p?.amount === 'number' && typeof p.currency === 'string'
26
+ ? { amount: p.amount, currency: p.currency }
27
+ : null,
28
+ model: typeof m?.['url'] === 'string'
29
+ ? {
30
+ url: m['url'],
31
+ ...(typeof m['format'] === 'string' ? { format: m['format'] } : {}),
32
+ ...(typeof m['scale'] === 'number' ? { scale: m['scale'] } : {}),
33
+ ...(typeof m['anchor'] === 'string' ? { anchor: m['anchor'] } : {}),
34
+ }
35
+ : null,
36
+ };
37
+ }
38
+ /** Minor units to a locale string. The fraction digits come from the currency, not from "100". */
39
+ export function formatPrice(amount, currency, locale) {
40
+ try {
41
+ const f = new Intl.NumberFormat(locale, { style: 'currency', currency });
42
+ return f.format(amount / 10 ** (f.resolvedOptions().maximumFractionDigits ?? 2));
43
+ }
44
+ catch {
45
+ return `${String(amount / 100)} ${currency}`;
46
+ }
47
+ }
48
+ export const cardKey = (message, block, index) => `${message.id}:${typeof block['id'] === 'string' ? block['id'] : String(index)}`;
49
+ /** The default `renderCard`. */
50
+ export function defaultCard(props, deps) {
51
+ const { theme } = props;
52
+ const card = readCard(props.block);
53
+ const g = new Group();
54
+ g.name = WORLD_CONTRACT.objects.card;
55
+ g.userData['float'] = theme.cardPedestal === 'float';
56
+ if (theme.cardPedestal === 'plinth') {
57
+ const plinth = new Mesh(new CylinderGeometry(0.16, 0.18, 0.1, 24), accentMaterial(theme));
58
+ plinth.name = 'cl3-card-pedestal';
59
+ plinth.position.y = -0.1;
60
+ g.add(plinth);
61
+ }
62
+ else if (theme.cardPedestal === 'hanger') {
63
+ const rod = new Mesh(new CylinderGeometry(0.004, 0.004, 0.5, 6), accentMaterial(theme));
64
+ rod.name = 'cl3-card-pedestal';
65
+ rod.position.y = 0.35;
66
+ g.add(rod);
67
+ }
68
+ if (theme.cardBeam) {
69
+ const beam = new Mesh(new CylinderGeometry(0.12, 0.16, 1.2, 24, 1, true), accentMaterial(theme, 0.2));
70
+ beam.name = 'cl3-card-beam';
71
+ beam.position.y = 0.55;
72
+ g.add(beam);
73
+ }
74
+ const plate = panelBacking(theme, PLATE_W, PLATE_H);
75
+ const body = new Group();
76
+ if (plate !== null) {
77
+ body.add(plate);
78
+ }
79
+ const title = createTextMesh(card.title, theme, { maxWidthMetres: PLATE_W - 0.02, size: 0.8 });
80
+ title.position.set(0, card.subtitle === null ? 0.02 : 0.04, 0.002);
81
+ body.add(title);
82
+ if (card.subtitle !== null) {
83
+ const sub = createTextMesh(card.subtitle, theme, { maxWidthMetres: PLATE_W - 0.02, size: 0.6 });
84
+ sub.position.set(0, -0.03, 0.002);
85
+ body.add(sub);
86
+ }
87
+ if (theme.cardPriceLabel && card.price !== null) {
88
+ const price = createTextMesh(formatPrice(card.price.amount, card.price.currency), theme, {
89
+ maxWidthMetres: PLATE_W,
90
+ size: 0.8,
91
+ color: theme.accentColor,
92
+ });
93
+ price.name = 'cl3-card-price';
94
+ price.position.set(0, -PLATE_H / 2 - 0.03, 0.002);
95
+ body.add(price);
96
+ }
97
+ g.add(body);
98
+ if (card.model !== null && card.model.format !== 'usdz') {
99
+ const { url, scale, anchor } = card.model;
100
+ deps.loadModel(url).then((m) => {
101
+ const fitted = fitModel(m.scene, scale ?? 1, anchor ?? 'center');
102
+ fitted.name = 'cl3-card-model';
103
+ // The model stands above its label plate.
104
+ body.position.y = -0.2;
105
+ fitted.position.y = 0.1;
106
+ g.add(fitted);
107
+ }, deps.onError);
108
+ }
109
+ const ring = new Mesh(new RingGeometry(0.2, 0.23, 32), new MeshBasicMaterial({ color: theme.cardHighlightColor, side: DoubleSide }));
110
+ ring.name = 'cl3-card-highlight';
111
+ ring.rotation.x = -Math.PI / 2;
112
+ ring.position.y = -0.14;
113
+ ring.visible = props.selected;
114
+ g.add(ring);
115
+ return g;
116
+ }
117
+ /** Whether a card node is drawn selected — the default's ring, readable by tests and hosts. */
118
+ export function isHighlighted(node) {
119
+ return node.getObjectByName('cl3-card-highlight')?.visible === true;
120
+ }
121
+ export function buildGrid(placement, count, theme, draw, keyOf) {
122
+ const group = new Group();
123
+ group.name = WORLD_CONTRACT.objects.grid;
124
+ group.userData['placement'] = placement;
125
+ const { poses, mount } = layoutCards(placement, count, theme.cardSpacingMetres);
126
+ const nodes = new Map();
127
+ poses.forEach((p, i) => {
128
+ const node = draw(i);
129
+ node.position.set(...p.position);
130
+ node.rotation.set(p.pitch, p.yaw, 0, 'YXZ');
131
+ group.add(node);
132
+ nodes.set(keyOf(i), { node, index: i });
133
+ });
134
+ return { group, mount, nodes };
135
+ }
136
+ //# sourceMappingURL=cards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.js","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The composer's state: a draft the HOST fills (a virtual keyboard, dictation,
3
+ * a DOM overlay, voice) and a submit the world sends. Typing in a headset is
4
+ * the host's problem; what is ours is that composing turns the agent's body to
5
+ * listen, so the draft going empty/non-empty is reported to the conversation.
6
+ */
7
+ import type { ConversationLike } from './types.js';
8
+ export interface Composer {
9
+ readonly draft: string;
10
+ readonly composing: boolean;
11
+ readonly busy: boolean;
12
+ setDraft(text: string): void;
13
+ /** Report composing without a draft (a custom composer with its own field). */
14
+ setComposing(composing: boolean): void;
15
+ submit(): Promise<void>;
16
+ }
17
+ export interface ComposerDeps {
18
+ readonly conversation: ConversationLike;
19
+ readonly onError: (e: unknown) => void;
20
+ readonly onSent: () => void;
21
+ /** The draft changed; `composingChanged` when it crossed empty/non-empty. */
22
+ readonly onChange: (composingChanged: boolean) => void;
23
+ }
24
+ export declare function createComposer(d: ComposerDeps): Composer;
25
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../src/composer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,CAAC,gBAAgB,EAAE,OAAO,KAAK,IAAI,CAAC;CACxD;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,GAAG,QAAQ,CAiDxD"}
@@ -0,0 +1,53 @@
1
+ export function createComposer(d) {
2
+ let draft = '';
3
+ let composing = false;
4
+ let busy = false;
5
+ const flip = (next) => {
6
+ if (next === composing) {
7
+ return false;
8
+ }
9
+ composing = next;
10
+ d.conversation.setTyping(next);
11
+ return true;
12
+ };
13
+ const composer = {
14
+ get draft() {
15
+ return draft;
16
+ },
17
+ get composing() {
18
+ return composing;
19
+ },
20
+ get busy() {
21
+ return busy;
22
+ },
23
+ setDraft(text) {
24
+ draft = text;
25
+ d.onChange(flip(text !== ''));
26
+ },
27
+ setComposing(c) {
28
+ if (flip(c)) {
29
+ d.onChange(true);
30
+ }
31
+ },
32
+ async submit() {
33
+ const body = draft.trim();
34
+ if (body === '' || busy) {
35
+ return;
36
+ }
37
+ busy = true;
38
+ try {
39
+ await d.conversation.send(body);
40
+ composer.setDraft('');
41
+ d.onSent();
42
+ }
43
+ catch (e) {
44
+ d.onError(e);
45
+ }
46
+ finally {
47
+ busy = false;
48
+ }
49
+ },
50
+ };
51
+ return composer;
52
+ }
53
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.js","sourceRoot":"","sources":["../src/composer.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,cAAc,CAAC,CAAe;IAC5C,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,MAAM,IAAI,GAAG,CAAC,IAAa,EAAW,EAAE;QACtC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,GAAG,IAAI,CAAC;QACjB,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IACF,MAAM,QAAQ,GAAa;QACzB,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI;YACN,OAAO,IAAI,CAAC;QACd,CAAC;QACD,QAAQ,CAAC,IAAI;YACX,KAAK,GAAG,IAAI,CAAC;YACb,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;QAChC,CAAC;QACD,YAAY,CAAC,CAAC;YACZ,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBACZ,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM;YACV,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;gBACxB,OAAO;YACT,CAAC;YACD,IAAI,GAAG,IAAI,CAAC;YACZ,IAAI,CAAC;gBACH,MAAM,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBACtB,CAAC,CAAC,MAAM,EAAE,CAAC;YACb,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACf,CAAC;oBAAS,CAAC;gBACT,IAAI,GAAG,KAAK,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC"}