@datalayer/agent-runtimes 1.3.27 → 1.3.29

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 (58) hide show
  1. package/lib/chat/ChatFloating.d.ts +12 -2
  2. package/lib/chat/ChatFloating.js +196 -25
  3. package/lib/chat/assistant/AssistantStage.d.ts +70 -0
  4. package/lib/chat/assistant/AssistantStage.js +192 -0
  5. package/lib/chat/assistant/SpeechBalloon.d.ts +26 -0
  6. package/lib/chat/assistant/SpeechBalloon.js +46 -0
  7. package/lib/chat/assistant/SpriteCharacter.d.ts +26 -0
  8. package/lib/chat/assistant/SpriteCharacter.js +73 -0
  9. package/lib/chat/assistant/characters.d.ts +30 -0
  10. package/lib/chat/assistant/characters.js +41 -0
  11. package/lib/chat/assistant/formats/acs.d.ts +88 -0
  12. package/lib/chat/assistant/formats/acs.js +493 -0
  13. package/lib/chat/assistant/formats/agentCompression.d.ts +5 -0
  14. package/lib/chat/assistant/formats/agentCompression.js +92 -0
  15. package/lib/chat/assistant/formats/blobs.d.ts +6 -0
  16. package/lib/chat/assistant/formats/blobs.js +52 -0
  17. package/lib/chat/assistant/formats/clippy.d.ts +21 -0
  18. package/lib/chat/assistant/formats/clippy.js +167 -0
  19. package/lib/chat/assistant/formats/index.d.ts +13 -0
  20. package/lib/chat/assistant/formats/index.js +15 -0
  21. package/lib/chat/assistant/formats/objectLiteral.d.ts +23 -0
  22. package/lib/chat/assistant/formats/objectLiteral.js +233 -0
  23. package/lib/chat/assistant/formats/stateAnimations.d.ts +16 -0
  24. package/lib/chat/assistant/formats/stateAnimations.js +115 -0
  25. package/lib/chat/assistant/formats/types.d.ts +56 -0
  26. package/lib/chat/assistant/formats/types.js +11 -0
  27. package/lib/chat/assistant/state.d.ts +62 -0
  28. package/lib/chat/assistant/state.js +116 -0
  29. package/lib/chat/header/ChatViewModeToggle.js +6 -3
  30. package/lib/chat/presence/Presence.d.ts +9 -0
  31. package/lib/chat/presence/Presence.js +57 -0
  32. package/lib/chat/presence/presenceStatus.d.ts +32 -0
  33. package/lib/chat/presence/presenceStatus.js +46 -0
  34. package/lib/chat/viewModes.d.ts +6 -0
  35. package/lib/chat/viewModes.js +10 -1
  36. package/lib/examples/ChatAssistantExample.d.ts +8 -0
  37. package/lib/examples/ChatAssistantExample.js +59 -0
  38. package/lib/examples/example-selector.js +1 -0
  39. package/lib/examples/main.js +3 -0
  40. package/lib/loop/apps/AppRenderer.d.ts +8 -1
  41. package/lib/loop/apps/AppRenderer.js +2 -1
  42. package/lib/loop/apps/appspec.d.ts +5 -1
  43. package/lib/loop/apps/appspec.js +21 -1
  44. package/lib/loop/apps/checks.js +3 -1
  45. package/lib/loop/core/index.d.ts +15 -0
  46. package/lib/loop/core/index.js +1 -0
  47. package/lib/loop/plugins/assistant-characters/index.d.ts +30 -0
  48. package/lib/loop/plugins/assistant-characters/index.js +41 -0
  49. package/lib/loop/plugins/chat/ChatView.js +21 -1
  50. package/lib/loop/plugins/chat/index.d.ts +8 -0
  51. package/lib/loop/plugins/page-layout/index.js +5 -1
  52. package/lib/specs/actions.js +6 -0
  53. package/lib/specs/apps.d.ts +3 -0
  54. package/lib/specs/apps.js +793 -65
  55. package/lib/specs/appspecSchema.js +20 -2
  56. package/lib/types/agentspecs.d.ts +10 -1
  57. package/lib/types/chat.d.ts +1 -1
  58. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from '@primer/react';
3
+ import { Box } from '@datalayer/primer-addons';
4
+ export function SpeechBalloon({ text, more = false, onOpen, above, side = 'above', align, tailAt, }) {
5
+ return (_jsxs(Box, { role: "status", "aria-live": "polite", "data-speech-balloon": "", sx: {
6
+ position: 'absolute',
7
+ ...(side === 'above' ? { bottom: above } : { top: above }),
8
+ [align]: 0,
9
+ maxWidth: 280,
10
+ width: 'max-content',
11
+ px: 3,
12
+ py: 2,
13
+ bg: 'canvas.default',
14
+ color: 'fg.default',
15
+ border: '1px solid',
16
+ borderColor: 'border.default',
17
+ borderRadius: 'var(--theme-radius-bubble, 16px)',
18
+ boxShadow: 'shadow.medium',
19
+ fontSize: 1,
20
+ textAlign: 'left',
21
+ // The tail, toward what speaks.
22
+ '&::after': {
23
+ content: '""',
24
+ position: 'absolute',
25
+ ...(side === 'above' ? { bottom: -7 } : { top: -7 }),
26
+ [align]: tailAt - 6,
27
+ width: 12,
28
+ height: 12,
29
+ bg: 'canvas.default',
30
+ borderRight: '1px solid',
31
+ borderBottom: '1px solid',
32
+ borderColor: 'border.default',
33
+ transform: side === 'above' ? 'rotate(45deg)' : 'rotate(225deg)',
34
+ },
35
+ }, children: [_jsx(Text, { as: "p", sx: { m: 0 }, children: text }), more && (_jsx(Box, { as: "button", type: "button", onClick: onOpen, sx: {
36
+ mt: 1,
37
+ p: 0,
38
+ border: 0,
39
+ bg: 'transparent',
40
+ color: 'accent.fg',
41
+ textDecoration: 'underline',
42
+ cursor: 'pointer',
43
+ fontSize: 1,
44
+ }, children: "Open the conversation" }))] }));
45
+ }
46
+ export default SpeechBalloon;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A character one brings, played (LOOP T-26): the frames of a character file
3
+ * read by `formats` — clippy.js or Microsoft Agent — drawn from its sprite
4
+ * sheet and stepped as the character authored them, its branches included,
5
+ * the animation chosen by the state the stage acts (T-22). Nothing plays for
6
+ * a reader who asks for reduced motion: the first frame of the state stands.
7
+ *
8
+ * @module chat/assistant/SpriteCharacter
9
+ */
10
+ import type { JSX } from 'react';
11
+ import type { AssistantCharacterAnimation, AssistantCharacterData } from './formats/types';
12
+ import type { AssistantState } from './state';
13
+ /**
14
+ * The frame after `index`: a branch, when the frame has some and the draw
15
+ * falls in one (weights are percents; what is left over goes on), else the
16
+ * next frame; `undefined` at the end of the animation.
17
+ */
18
+ export declare function nextFrameIndex(animation: AssistantCharacterAnimation, index: number, draw?: number): number | undefined;
19
+ /** One of the animations offered for a state, drawn at random. */
20
+ export declare function animationFor(character: AssistantCharacterData, state: AssistantState, draw?: number): string;
21
+ export declare function SpriteCharacter({ character, state, size, }: {
22
+ character: AssistantCharacterData;
23
+ state: AssistantState;
24
+ size: number;
25
+ }): JSX.Element;
26
+ export default SpriteCharacter;
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { stateAnimations } from './formats/stateAnimations';
4
+ /**
5
+ * The frame after `index`: a branch, when the frame has some and the draw
6
+ * falls in one (weights are percents; what is left over goes on), else the
7
+ * next frame; `undefined` at the end of the animation.
8
+ */
9
+ export function nextFrameIndex(animation, index, draw = Math.random()) {
10
+ const frame = animation.frames[index];
11
+ let at = draw * 100;
12
+ for (const branch of frame?.branching ?? []) {
13
+ if (at < branch.weight) {
14
+ return branch.frameIndex;
15
+ }
16
+ at -= branch.weight;
17
+ }
18
+ return index + 1 < animation.frames.length ? index + 1 : undefined;
19
+ }
20
+ /** One of the animations offered for a state, drawn at random. */
21
+ export function animationFor(character, state, draw = Math.random()) {
22
+ const names = stateAnimations(character)[state];
23
+ return names[Math.min(names.length - 1, Math.floor(draw * names.length))];
24
+ }
25
+ const reducedMotion = () => typeof window !== 'undefined' &&
26
+ !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
27
+ export function SpriteCharacter({ character, state, size, }) {
28
+ const [play, setPlay] = useState(() => ({
29
+ name: animationFor(character, state),
30
+ index: 0,
31
+ }));
32
+ // A new state starts one of its own animations from the first frame.
33
+ useEffect(() => {
34
+ setPlay({ name: animationFor(character, state), index: 0 });
35
+ }, [character, state]);
36
+ const animation = character.animations[play.name];
37
+ const frame = animation?.frames[play.index];
38
+ const still = useMemo(reducedMotion, []);
39
+ useEffect(() => {
40
+ if (!animation || !frame || still) {
41
+ return;
42
+ }
43
+ const timer = setTimeout(() => {
44
+ const next = nextFrameIndex(animation, play.index);
45
+ // At its end an animation gives way to another of the same state.
46
+ setPlay(next === undefined
47
+ ? { name: animationFor(character, state), index: 0 }
48
+ : { name: play.name, index: next });
49
+ }, Math.max(frame.duration, 16));
50
+ return () => clearTimeout(timer);
51
+ }, [animation, frame, play, character, state, still]);
52
+ const { width, height } = character.frameSize;
53
+ const scale = size / Math.max(width, height);
54
+ return (_jsx("span", { role: "img", "aria-label": character.name, "data-sprite-animation": play.name, style: {
55
+ position: 'relative',
56
+ display: 'block',
57
+ width: size,
58
+ height: size,
59
+ }, children: (frame?.images ?? []).map((image, layer) => (_jsx("span", { "aria-hidden": true, style: {
60
+ position: 'absolute',
61
+ left: (size - width * scale) / 2,
62
+ top: (size - height * scale) / 2,
63
+ width,
64
+ height,
65
+ transform: `scale(${scale})`,
66
+ transformOrigin: 'top left',
67
+ backgroundImage: `url(${character.sprite})`,
68
+ backgroundPosition: `-${image.x}px -${image.y}px`,
69
+ backgroundRepeat: 'no-repeat',
70
+ imageRendering: 'pixelated',
71
+ } }, layer))) }));
72
+ }
73
+ export default SpriteCharacter;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Datalayer's own characters for the floating assistant (LOOP T-25): drawn
3
+ * for LOOP and owned by Datalayer, in the spirit of the Office Assistant —
4
+ * a paper clip of our own, a wizard, a cat and the LšŸ‘€P eyes. The characters
5
+ * of Office are Microsoft's and are never bundled (§6.9); a person loads
6
+ * one they hold the rights to (T-26).
7
+ *
8
+ * Each drawing names its moving parts with classes the stage animates by
9
+ * state — `assistant-body`, `assistant-pupils`, `assistant-lids`,
10
+ * `assistant-mouth` — so one set of motions serves every character.
11
+ *
12
+ * @module chat/assistant/characters
13
+ */
14
+ import type { JSX } from 'react';
15
+ /** A character Datalayer ships. */
16
+ export interface AssistantCharacter {
17
+ id: string;
18
+ /** Its name, as the picker shows it. */
19
+ name: string;
20
+ /** The drawing, at the size asked; its parts animate by class. */
21
+ Drawing: (props: {
22
+ size: number;
23
+ }) => JSX.Element;
24
+ }
25
+ /** The characters Datalayer ships, in the order a picker shows them. */
26
+ export declare const ASSISTANT_CHARACTERS: readonly AssistantCharacter[];
27
+ /** The character an assistant shows when none is chosen. */
28
+ export declare const DEFAULT_ASSISTANT_CHARACTER = "paperclip";
29
+ /** A shipped character by id; an unknown id is an error, said plainly. */
30
+ export declare function assistantCharacter(id: string): AssistantCharacter;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const INK = '#1D1D1F';
3
+ /** Two eyes with lids that blink and pupils that look about. */
4
+ function Eyes({ cx, cy, gap, r, }) {
5
+ const left = cx - gap / 2;
6
+ const right = cx + gap / 2;
7
+ return (_jsxs("g", { children: [_jsx("circle", { cx: left, cy: cy, r: r, fill: "#FFFFFF", stroke: INK, strokeWidth: 1.5 }), _jsx("circle", { cx: right, cy: cy, r: r, fill: "#FFFFFF", stroke: INK, strokeWidth: 1.5 }), _jsxs("g", { className: "assistant-pupils", children: [_jsx("circle", { cx: left + r * 0.15, cy: cy + r * 0.1, r: r * 0.45, fill: INK }), _jsx("circle", { cx: right + r * 0.15, cy: cy + r * 0.1, r: r * 0.45, fill: INK }), _jsx("circle", { cx: left + r * 0.3, cy: cy - r * 0.1, r: r * 0.14, fill: "#FFFFFF" }), _jsx("circle", { cx: right + r * 0.3, cy: cy - r * 0.1, r: r * 0.14, fill: "#FFFFFF" })] }), _jsxs("g", { className: "assistant-lids", children: [_jsx("ellipse", { cx: left, cy: cy, rx: r + 0.5, ry: r + 0.5, fill: "currentColor" }), _jsx("ellipse", { cx: right, cy: cy, rx: r + 0.5, ry: r + 0.5, fill: "currentColor" })] })] }));
8
+ }
9
+ /** A paper clip of our own: a bent wire with eyes on its upper loop. */
10
+ function PaperClip({ size }) {
11
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Paper clip", style: { color: '#B8BDC6' }, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M40 86 L40 30 Q40 14 52 14 Q64 14 64 30 L64 74 Q64 82 56 82 Q48 82 48 74 L48 36", fill: "none", stroke: "#9AA0AA", strokeWidth: 6, strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M40 86 L40 30 Q40 14 52 14 Q64 14 64 30 L64 74 Q64 82 56 82 Q48 82 48 74 L48 36", fill: "none", stroke: "#E3E6EB", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M36 22 Q42 18 47 21", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" }), _jsx("path", { d: "M57 21 Q62 18 68 22", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" }), _jsx(Eyes, { cx: 52, cy: 30, gap: 16, r: 6.5 }), _jsx("path", { className: "assistant-mouth", d: "M48 44 Q52 47 56 44", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" })] }) }));
12
+ }
13
+ /** A wizard: a starred hat, a white beard, kind eyes. */
14
+ function Wizard({ size }) {
15
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Wizard", style: { color: '#F2D3B3' }, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M24 92 Q48 58 72 92 Z", fill: "#5B4BB7" }), _jsx("circle", { cx: 48, cy: 48, r: 17, fill: "#F2D3B3" }), _jsx("path", { d: "M31 50 Q48 92 65 50 Q57 60 48 60 Q39 60 31 50 Z", fill: "#F4F4F6", stroke: "#D9DAE0", strokeWidth: 1 }), _jsx("path", { d: "M22 36 L48 2 L74 36 Q48 30 22 36 Z", fill: "#6E5CD6" }), _jsx("path", { d: "M20 37 Q48 29 76 37 Q48 43 20 37 Z", fill: "#4A3C9E" }), _jsx("path", { d: "M52 14 l2 4 4 1 -3 3 1 4 -4 -2 -4 2 1 -4 -3 -3 4 -1 Z", fill: "#F8D469" }), _jsx("circle", { cx: 40, cy: 26, r: 1.8, fill: "#F8D469" }), _jsx(Eyes, { cx: 48, cy: 46, gap: 13, r: 5 }), _jsx("path", { className: "assistant-mouth", d: "M44 58 Q48 61 52 58", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" })] }) }));
16
+ }
17
+ /** A cat: round, two ears, whiskers, a tail. */
18
+ function Cat({ size }) {
19
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Cat", style: { color: '#F4A261' }, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M70 84 Q90 78 84 58", stroke: "#E08A45", strokeWidth: 6, fill: "none", strokeLinecap: "round" }), _jsx("ellipse", { cx: 48, cy: 78, rx: 24, ry: 14, fill: "#F4A261" }), _jsx("path", { d: "M24 30 L28 8 L42 22 Z", fill: "#F4A261" }), _jsx("path", { d: "M72 30 L68 8 L54 22 Z", fill: "#F4A261" }), _jsx("path", { d: "M28 26 L30 14 L38 22 Z", fill: "#F6A5C1" }), _jsx("path", { d: "M68 26 L66 14 L58 22 Z", fill: "#F6A5C1" }), _jsx("circle", { cx: 48, cy: 42, r: 24, fill: "#F4A261" }), _jsx(Eyes, { cx: 48, cy: 38, gap: 18, r: 6.5 }), _jsx("path", { d: "M45 48 L51 48 L48 52 Z", fill: "#B83A6B" }), _jsx("path", { className: "assistant-mouth", d: "M43 55 Q48 58 53 55", stroke: INK, strokeWidth: 2, fill: "none", strokeLinecap: "round" }), _jsx("path", { d: "M30 48 L18 46 M30 52 L18 54 M66 48 L78 46 M66 52 L78 54", stroke: INK, strokeWidth: 1.2, strokeLinecap: "round" })] }) }));
20
+ }
21
+ /** The LšŸ‘€P eyes: the product's own face, two eyes and nothing else. */
22
+ function LoopEyes({ size }) {
23
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "L\uD83D\uDC40P eyes", style: { color: '#FFFFFF' }, children: _jsxs("g", { className: "assistant-body", children: [_jsx(Eyes, { cx: 48, cy: 50, gap: 40, r: 18 }), _jsx("path", { className: "assistant-mouth", d: "M42 80 Q48 84 54 80", stroke: INK, strokeWidth: 2.5, fill: "none", strokeLinecap: "round", opacity: 0 })] }) }));
24
+ }
25
+ /** The characters Datalayer ships, in the order a picker shows them. */
26
+ export const ASSISTANT_CHARACTERS = [
27
+ { id: 'paperclip', name: 'Paper clip', Drawing: PaperClip },
28
+ { id: 'wizard', name: 'Wizard', Drawing: Wizard },
29
+ { id: 'cat', name: 'Cat', Drawing: Cat },
30
+ { id: 'eyes', name: 'LšŸ‘€P eyes', Drawing: LoopEyes },
31
+ ];
32
+ /** The character an assistant shows when none is chosen. */
33
+ export const DEFAULT_ASSISTANT_CHARACTER = 'paperclip';
34
+ /** A shipped character by id; an unknown id is an error, said plainly. */
35
+ export function assistantCharacter(id) {
36
+ const found = ASSISTANT_CHARACTERS.find(character => character.id === id);
37
+ if (!found) {
38
+ throw new Error(`There is no assistant character "${id}"; the characters are ${ASSISTANT_CHARACTERS.map(c => c.id).join(', ')}.`);
39
+ }
40
+ return found;
41
+ }
@@ -0,0 +1,88 @@
1
+ import { type AssistantCharacterData } from './types';
2
+ export declare const ACS_SIGNATURE = 2882382787;
3
+ export declare const ACF_SIGNATURE = 2882382788;
4
+ export interface AcsLocator {
5
+ offset: number;
6
+ size: number;
7
+ }
8
+ export interface AcsFrameImage {
9
+ image: number;
10
+ x: number;
11
+ y: number;
12
+ }
13
+ export interface AcsFrame {
14
+ /** Images, as stored: the first is drawn on top. */
15
+ images: AcsFrameImage[];
16
+ /** Index into the audio list, or -1. */
17
+ sound: number;
18
+ /** In 1/100 s. */
19
+ duration: number;
20
+ /** Frame index, or a negative value for none. */
21
+ exitBranch: number;
22
+ branches: {
23
+ frameIndex: number;
24
+ probability: number;
25
+ }[];
26
+ }
27
+ export interface AcsAnimation {
28
+ name: string;
29
+ transition: number;
30
+ returnAnimation: string;
31
+ frames: AcsFrame[];
32
+ }
33
+ export interface AcsImageEntry {
34
+ locator: AcsLocator;
35
+ }
36
+ /** An `.acs` read into its tables, the images not yet decoded. */
37
+ export interface AcsFile {
38
+ version: {
39
+ major: number;
40
+ minor: number;
41
+ };
42
+ name: string;
43
+ width: number;
44
+ height: number;
45
+ transparentIndex: number;
46
+ /** RGBA, one entry per palette index. */
47
+ palette: Uint32Array;
48
+ states: Record<string, string[]>;
49
+ animations: AcsAnimation[];
50
+ images: AcsImageEntry[];
51
+ sounds: AcsLocator[];
52
+ bytes: Uint8Array;
53
+ }
54
+ /** A decoded image: palette indices, top row first, no padding. */
55
+ export interface AcsImage {
56
+ width: number;
57
+ height: number;
58
+ pixels: Uint8Array;
59
+ }
60
+ /**
61
+ * Read the tables of an `.acs`: the character, its animations and frames,
62
+ * where its images and sounds are. Images are decoded by `decodeAcsImage`.
63
+ */
64
+ export declare function parseAcs(buffer: ArrayBuffer): AcsFile;
65
+ /** Decode one image of an `.acs` into palette indices, top row first. */
66
+ export declare function decodeAcsImage(file: AcsFile, index: number): AcsImage;
67
+ /** One cell of the sprite sheet: the frames that look the same share it. */
68
+ export interface AcsSpriteLayout {
69
+ columns: number;
70
+ rows: number;
71
+ /** The images of each cell, as stored in the frame (first on top). */
72
+ cells: AcsFrameImage[][];
73
+ /** For each animation, for each frame, its cell, or -1 for an empty frame. */
74
+ frameCells: number[][];
75
+ }
76
+ /** Assign a cell to each distinct, non-empty frame. */
77
+ export declare function layoutAcsSprite(file: AcsFile): AcsSpriteLayout;
78
+ /**
79
+ * Composite a cell as Agent draws a frame: its images from last to first,
80
+ * each at its offset, the transparent colour left clear. RGBA, frame-sized.
81
+ */
82
+ export declare function composeAcsCell(file: AcsFile, images: AcsFrameImage[], decode?: (index: number) => AcsImage): Uint8ClampedArray;
83
+ /**
84
+ * Read a Microsoft Agent character (`.acs`) a person picked: its animations,
85
+ * a sprite sheet drawn in the page (an object URL), its sounds as object
86
+ * URLs. Nothing leaves the page.
87
+ */
88
+ export declare function readAcsCharacter(buffer: ArrayBuffer): Promise<AssistantCharacterData>;