@promptbook/wizard 0.112.0-117 → 0.112.0-119

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 (37) hide show
  1. package/esm/index.es.js +197 -44
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/_packages/node.index.d.ts +10 -0
  4. package/esm/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  5. package/esm/src/book-3.0/CliAgent.d.ts +7 -2
  6. package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
  7. package/esm/src/book-components/BookEditor/BookEditor.d.ts +1 -1
  8. package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +1 -1
  9. package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  10. package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  11. package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  12. package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
  13. package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
  14. package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
  15. package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  16. package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  17. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  18. package/esm/src/version.d.ts +1 -1
  19. package/package.json +2 -2
  20. package/umd/index.umd.js +197 -44
  21. package/umd/index.umd.js.map +1 -1
  22. package/umd/src/_packages/node.index.d.ts +10 -0
  23. package/umd/src/book-3.0/BookNodeAgentSource.d.ts +1 -1
  24. package/umd/src/book-3.0/CliAgent.d.ts +7 -2
  25. package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
  26. package/umd/src/book-components/BookEditor/BookEditor.d.ts +1 -1
  27. package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +1 -1
  28. package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
  29. package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
  30. package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
  31. package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
  32. package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
  33. package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
  34. package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
  35. package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
  36. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +2 -18
  37. package/umd/src/version.d.ts +1 -1
@@ -11,6 +11,11 @@ import type { CliAgentThinkingLevel } from '../book-3.0/CliAgent';
11
11
  import type { CliAgentRunOptions } from '../book-3.0/CliAgent';
12
12
  import type { CliAgentOptions } from '../book-3.0/CliAgent';
13
13
  import { CliAgent } from '../book-3.0/CliAgent';
14
+ import { CLI_AGENT_HARNESS_NAMES } from '../book-3.0/cliAgentEnv';
15
+ import { CLI_AGENT_THINKING_LEVEL_VALUES } from '../book-3.0/cliAgentEnv';
16
+ import { PTBK_HARNESS_ENV } from '../book-3.0/cliAgentEnv';
17
+ import { PTBK_MODEL_ENV } from '../book-3.0/cliAgentEnv';
18
+ import { PTBK_THINKING_LEVEL_ENV } from '../book-3.0/cliAgentEnv';
14
19
  import type { LiteAgentOptions } from '../book-3.0/LiteAgent';
15
20
  import type { LiteAgentRunOptions } from '../book-3.0/LiteAgent';
16
21
  import { LiteAgent } from '../book-3.0/LiteAgent';
@@ -39,6 +44,11 @@ export type { CliAgentThinkingLevel };
39
44
  export type { CliAgentRunOptions };
40
45
  export type { CliAgentOptions };
41
46
  export { CliAgent };
47
+ export { CLI_AGENT_HARNESS_NAMES };
48
+ export { CLI_AGENT_THINKING_LEVEL_VALUES };
49
+ export { PTBK_HARNESS_ENV };
50
+ export { PTBK_MODEL_ENV };
51
+ export { PTBK_THINKING_LEVEL_ENV };
42
52
  export type { LiteAgentOptions };
43
53
  export type { LiteAgentRunOptions };
44
54
  export { LiteAgent };
@@ -15,7 +15,7 @@ export type BookNodeAgentSource = Book | string_book;
15
15
  */
16
16
  export type BookNodeAgentSourceOptions = {
17
17
  readonly agentPath?: string;
18
- readonly book?: BookNodeAgentSource;
18
+ readonly book?: string | BookNodeAgentSource;
19
19
  readonly currentWorkingDirectory?: string;
20
20
  };
21
21
  /**
@@ -1,16 +1,17 @@
1
1
  import type { BookNodeAgentSourceOptions } from './BookNodeAgentSource';
2
+ import { CLI_AGENT_HARNESS_NAMES, CLI_AGENT_THINKING_LEVEL_VALUES } from './cliAgentEnv';
2
3
  /**
3
4
  * CLI harness names supported by `ptbk agent exec`.
4
5
  *
5
6
  * @public exported from `@promptbook/node`
6
7
  */
7
- export type CliAgentHarness = 'openai-codex' | 'github-copilot' | 'cline' | 'claude-code' | 'opencode' | 'gemini';
8
+ export type CliAgentHarness = (typeof CLI_AGENT_HARNESS_NAMES)[number];
8
9
  /**
9
10
  * Thinking levels supported by CLI coding harnesses.
10
11
  *
11
12
  * @public exported from `@promptbook/node`
12
13
  */
13
- export type CliAgentThinkingLevel = 'low' | 'medium' | 'high' | 'xhigh';
14
+ export type CliAgentThinkingLevel = (typeof CLI_AGENT_THINKING_LEVEL_VALUES)[number];
14
15
  /**
15
16
  * Per-run CLI options exposed by `CliAgent`.
16
17
  *
@@ -22,6 +23,7 @@ export type CliAgentRunOptions = {
22
23
  readonly allowCredits?: boolean;
23
24
  readonly context?: string;
24
25
  readonly harness?: CliAgentHarness;
26
+ readonly isVerbose?: boolean;
25
27
  readonly model?: string;
26
28
  readonly noUi?: boolean;
27
29
  readonly thinkingLevel?: CliAgentThinkingLevel;
@@ -38,6 +40,9 @@ export type CliAgentOptions = BookNodeAgentSourceOptions & CliAgentRunOptions;
38
40
  * It uses the same harnesses and execution path as `ptbk agent exec`, running the runner
39
41
  * in-process instead of spawning a separate CLI process.
40
42
  *
43
+ * When no `harness` is provided in the constructor or per-run options, `CliAgent` falls back
44
+ * to the `PTBK_HARNESS` environment variable, mirroring `ptbk agent exec` behavior.
45
+ *
41
46
  * @public exported from `@promptbook/node`
42
47
  */
43
48
  export declare class CliAgent {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * All CLI harness names supported by `CliAgent` and `ptbk agent exec`.
3
+ *
4
+ * @public exported from `@promptbook/node`
5
+ */
6
+ export declare const CLI_AGENT_HARNESS_NAMES: readonly ["openai-codex", "github-copilot", "cline", "claude-code", "opencode", "gemini"];
7
+ /**
8
+ * All supported thinking-level values for CLI coding-agent runners.
9
+ *
10
+ * @public exported from `@promptbook/node`
11
+ */
12
+ export declare const CLI_AGENT_THINKING_LEVEL_VALUES: readonly ["low", "medium", "high", "xhigh"];
13
+ /**
14
+ * Environment variable used as the default runner identifier when `--harness` is omitted or not set in `CliAgent`.
15
+ *
16
+ * Set this to one of the harness names (`openai-codex`, `github-copilot`, `cline`, `claude-code`, `opencode`, `gemini`)
17
+ * so that `CliAgent` and `ptbk agent exec` can run without an explicit `harness` option.
18
+ *
19
+ * @public exported from `@promptbook/node`
20
+ */
21
+ export declare const PTBK_HARNESS_ENV = "PTBK_HARNESS";
22
+ /**
23
+ * Environment variable used as the default runner model when `--model` is omitted or not set in `CliAgent`.
24
+ *
25
+ * @public exported from `@promptbook/node`
26
+ */
27
+ export declare const PTBK_MODEL_ENV = "PTBK_MODEL";
28
+ /**
29
+ * Environment variable used as the default thinking level when `--thinking-level` is omitted or not set in `CliAgent`.
30
+ *
31
+ * @public exported from `@promptbook/node`
32
+ */
33
+ export declare const PTBK_THINKING_LEVEL_ENV = "PTBK_THINKING_LEVEL";
@@ -1,4 +1,4 @@
1
- import { CSSProperties } from 'react';
1
+ import { type CSSProperties } from 'react';
2
2
  import type { Promisable } from 'type-fest';
3
3
  import type { string_book } from '../../book-2.0/agent-source/string_book';
4
4
  import type { number_percent } from '../../types/number_percent';
@@ -1,4 +1,4 @@
1
- import { BookEditorProps } from './BookEditor';
1
+ import type { BookEditorProps } from './BookEditor';
2
2
  /**
3
3
  * Renders a book editor
4
4
  *
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Props for the citation iframe preview component.
3
+ *
4
+ * @private component of `<ChatCitationModal/>`
5
+ */
6
+ export type CitationIframePreviewProps = {
7
+ src: string;
8
+ title: string;
9
+ };
10
+ /**
11
+ * Renders a citation URL preview as an iframe when the target page allows embedding,
12
+ * or falls back to a server-side screenshot with an "Open in new tab" link when it
13
+ * does not (e.g. X-Frame-Options: DENY / SAMEORIGIN).
14
+ *
15
+ * Embedding capability is determined by `GET /api/page-preview/check?url=<url>`.
16
+ * If that endpoint is unavailable the component falls back to the iframe directly.
17
+ *
18
+ * @private component of `<ChatCitationModal/>`
19
+ */
20
+ export declare function CitationIframePreview({ src, title }: CitationIframePreviewProps): import("react/jsx-runtime").JSX.Element;
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  /**
3
3
  * Props for dropdown.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  /**
3
3
  * Item that can be hoisted to the top menu
4
4
  *
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  /**
3
3
  * Handles modal.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { SVGProps } from 'react';
1
+ import type { SVGProps } from 'react';
2
2
  /**
3
3
  * Renders an information/about icon.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { SVGProps } from 'react';
1
+ import type { SVGProps } from 'react';
2
2
  /**
3
3
  * Renders a download icon.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { JSX } from 'react';
1
+ import type { JSX } from 'react';
2
2
  /**
3
3
  * Renders an icon that represents the exit fullscreen action
4
4
  *
@@ -1,4 +1,4 @@
1
- import { JSX } from 'react';
1
+ import type { JSX } from 'react';
2
2
  /**
3
3
  * Renders an icon that represents the fullscreen action
4
4
  *
@@ -1,29 +1,13 @@
1
1
  import { Command as Program } from 'commander';
2
2
  import type { ThinkingLevel } from '../coder/ThinkingLevel';
3
+ import { PTBK_HARNESS_ENV, PTBK_MODEL_ENV, PTBK_THINKING_LEVEL_ENV } from '../../../book-3.0/cliAgentEnv';
4
+ export { PTBK_HARNESS_ENV, PTBK_MODEL_ENV, PTBK_THINKING_LEVEL_ENV };
3
5
  /**
4
6
  * Runner identifiers supported by Promptbook CLI agent orchestration commands.
5
7
  *
6
8
  * @private internal utility of `promptbookCli`
7
9
  */
8
10
  export declare const PROMPT_RUNNER_HARNESS_NAMES: readonly ["openai-codex", "github-copilot", "cline", "claude-code", "opencode", "gemini"];
9
- /**
10
- * Environment variable used as the default runner identifier when `--harness` is omitted.
11
- *
12
- * @private internal utility of `promptbookCli`
13
- */
14
- export declare const PTBK_HARNESS_ENV = "PTBK_HARNESS";
15
- /**
16
- * Environment variable used as the default runner model when `--model` is omitted.
17
- *
18
- * @private internal utility of `promptbookCli`
19
- */
20
- export declare const PTBK_MODEL_ENV = "PTBK_MODEL";
21
- /**
22
- * Environment variable used as the default runner thinking level when `--thinking-level` is omitted.
23
- *
24
- * @private internal utility of `promptbookCli`
25
- */
26
- export declare const PTBK_THINKING_LEVEL_ENV = "PTBK_THINKING_LEVEL";
27
11
  /**
28
12
  * Runner identifier supported by Promptbook CLI agent orchestration commands.
29
13
  *
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.112.0-116`).
18
+ * It follows semantic versioning (e.g., `0.112.0-118`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/wizard",
3
- "version": "0.112.0-117",
3
+ "version": "0.112.0-119",
4
4
  "description": "Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -99,7 +99,7 @@
99
99
  "types": "./esm/src/_packages/wizard.index.d.ts",
100
100
  "typings": "./esm/src/_packages/wizard.index.d.ts",
101
101
  "peerDependencies": {
102
- "@promptbook/core": "0.112.0-117"
102
+ "@promptbook/core": "0.112.0-119"
103
103
  },
104
104
  "dependencies": {
105
105
  "@ai-sdk/deepseek": "0.1.17",
package/umd/index.umd.js CHANGED
@@ -52,7 +52,7 @@
52
52
  * @generated
53
53
  * @see https://github.com/webgptorg/promptbook
54
54
  */
55
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
55
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-119';
56
56
  /**
57
57
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
58
58
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -10034,7 +10034,7 @@
10034
10034
  return { skippedReason: 'invalid_data_url' };
10035
10035
  }
10036
10036
  return {
10037
- file: new File([parsed.buffer], parsed.filename, {
10037
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
10038
10038
  type: parsed.mimeType,
10039
10039
  }),
10040
10040
  sizeBytes: parsed.buffer.length,
@@ -22108,21 +22108,22 @@
22108
22108
  * @private helper of `fractalAvatarVisual`
22109
22109
  */
22110
22110
  function drawDragonCurveLayer(context, points, options) {
22111
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22111
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
22112
22112
  const firstPoint = points[0];
22113
22113
  const lastPoint = points[points.length - 1];
22114
22114
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
22115
22115
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
22116
22116
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
22117
22117
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
22118
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
22119
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
22118
22120
  context.save();
22119
22121
  context.beginPath();
22120
22122
  tracePolyline(context, points);
22121
- context.strokeStyle = `${shadowColor}82`;
22122
- context.lineWidth = strokeWidth * 1.8;
22123
+ context.strokeStyle = `${shadowColor}48`;
22124
+ context.lineWidth = strokeWidth * 4.5;
22123
22125
  context.lineJoin = 'round';
22124
22126
  context.lineCap = 'round';
22125
- context.filter = `blur(${size * 0.022}px)`;
22126
22127
  context.stroke();
22127
22128
  context.restore();
22128
22129
  context.beginPath();
@@ -22706,11 +22707,23 @@
22706
22707
  * @private helper of `minecraft2AvatarVisual`
22707
22708
  */
22708
22709
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
22710
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
22711
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
22712
+ const rx = size * (0.16 + interaction.intensity * 0.015);
22713
+ const ry = size * 0.055;
22714
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22709
22715
  context.save();
22710
- context.fillStyle = `${palette.shadow}66`;
22711
- context.filter = `blur(${size * 0.02}px)`;
22716
+ context.translate(cx, cy);
22717
+ context.scale(1, ry / rx);
22718
+ const blurRadius = rx * 1.4;
22719
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22720
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
22721
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
22722
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
22723
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
22724
+ context.fillStyle = shadowGradient;
22712
22725
  context.beginPath();
22713
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.03, size * 0.85 + Math.sin(timeMs / 880) * size * 0.01, size * (0.16 + interaction.intensity * 0.015), size * 0.055, 0, 0, Math.PI * 2);
22726
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22714
22727
  context.fill();
22715
22728
  context.restore();
22716
22729
  }
@@ -22934,13 +22947,27 @@
22934
22947
  spotlight.addColorStop(1, `${palette.highlight}00`);
22935
22948
  context.fillStyle = spotlight;
22936
22949
  context.fillRect(0, 0, size, size);
22937
- context.save();
22938
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
22939
- context.filter = `blur(${size * 0.018}px)`;
22940
- context.beginPath();
22941
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
22942
- context.fill();
22943
- context.restore();
22950
+ {
22951
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
22952
+ const cx = size * 0.5;
22953
+ const cy = size * 0.86;
22954
+ const rx = size * 0.2;
22955
+ const ry = size * 0.06;
22956
+ const blurRadius = rx * 1.4;
22957
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
22958
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
22959
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
22960
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
22961
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
22962
+ context.save();
22963
+ context.translate(cx, cy);
22964
+ context.scale(1, ry / rx);
22965
+ context.fillStyle = shadowGradient;
22966
+ context.beginPath();
22967
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
22968
+ context.fill();
22969
+ context.restore();
22970
+ }
22944
22971
  drawVoxelCuboid(context, {
22945
22972
  x: bodyX,
22946
22973
  y: bodyY,
@@ -23992,6 +24019,35 @@
23992
24019
  y: -0.62,
23993
24020
  z: 0.94,
23994
24021
  });
24022
+ /**
24023
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24024
+ *
24025
+ * @private helper of `octopus3dAvatarVisual`
24026
+ */
24027
+ const octopus3dStableStateCache = new WeakMap();
24028
+ /**
24029
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24030
+ *
24031
+ * @private helper of `octopus3dAvatarVisual`
24032
+ */
24033
+ function getOctopus3dStableState(createRandom) {
24034
+ const cached = octopus3dStableStateCache.get(createRandom);
24035
+ if (cached !== undefined) {
24036
+ return cached;
24037
+ }
24038
+ const animationRandom = createRandom('octopus3d-animation-profile');
24039
+ const eyeRandom = createRandom('octopus3d-eye-profile');
24040
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
24041
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
24042
+ const state = {
24043
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24044
+ animationPhase: animationRandom() * Math.PI * 2,
24045
+ leftEyePhaseOffset,
24046
+ rightEyePhaseOffset,
24047
+ };
24048
+ octopus3dStableStateCache.set(createRandom, state);
24049
+ return state;
24050
+ }
23995
24051
  /**
23996
24052
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
23997
24053
  *
@@ -24004,10 +24060,7 @@
24004
24060
  isAnimated: true,
24005
24061
  supportsPointerTracking: true,
24006
24062
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24007
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24008
- const animationRandom = createRandom('octopus3d-animation-profile');
24009
- const eyeRandom = createRandom('octopus3d-eye-profile');
24010
- const animationPhase = animationRandom() * Math.PI * 2;
24063
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
24011
24064
  const sceneCenterX = size * 0.5;
24012
24065
  const sceneCenterY = size * 0.56;
24013
24066
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -24104,12 +24157,12 @@
24104
24157
  x: -faceEyeSpacing,
24105
24158
  y: faceEyeYOffset,
24106
24159
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
24107
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24160
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24108
24161
  drawProjectedOrganicEye(context, {
24109
24162
  x: faceEyeSpacing,
24110
24163
  y: faceEyeYOffset,
24111
24164
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
24112
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
24165
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24113
24166
  drawProjectedOrganicMouth(context, [
24114
24167
  {
24115
24168
  x: -mouthHalfWidth,
@@ -24153,14 +24206,28 @@
24153
24206
  /**
24154
24207
  * Draws the soft ground shadow below the octopus.
24155
24208
  *
24209
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24210
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24211
+ *
24156
24212
  * @private helper of `octopus3dAvatarVisual`
24157
24213
  */
24158
24214
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
24215
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
24216
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
24217
+ const rx = size * (0.18 + interaction.intensity * 0.02);
24218
+ const ry = size * 0.06;
24159
24219
  context.save();
24160
- context.fillStyle = `${palette.shadow}66`;
24161
- context.filter = `blur(${size * 0.022}px)`;
24220
+ context.translate(cx, cy);
24221
+ context.scale(1, ry / rx);
24222
+ const blurRadius = rx * 1.4;
24223
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24224
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24225
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24226
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24227
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24228
+ context.fillStyle = shadowGradient;
24162
24229
  context.beginPath();
24163
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.04, size * 0.87 + Math.sin(timeMs / 920) * size * 0.008, size * (0.18 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
24230
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24164
24231
  context.fill();
24165
24232
  context.restore();
24166
24233
  }
@@ -24391,6 +24458,35 @@
24391
24458
  y: -0.6,
24392
24459
  z: 0.98,
24393
24460
  });
24461
+ /**
24462
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
24463
+ *
24464
+ * @private helper of `octopus3d2AvatarVisual`
24465
+ */
24466
+ const octopus3d2StableStateCache = new WeakMap();
24467
+ /**
24468
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
24469
+ *
24470
+ * @private helper of `octopus3d2AvatarVisual`
24471
+ */
24472
+ function getOctopus3d2StableState(createRandom) {
24473
+ const cached = octopus3d2StableStateCache.get(createRandom);
24474
+ if (cached !== undefined) {
24475
+ return cached;
24476
+ }
24477
+ const animationRandom = createRandom('octopus3d2-animation-profile');
24478
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
24479
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24480
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24481
+ const state = {
24482
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
24483
+ animationPhase: animationRandom() * Math.PI * 2,
24484
+ leftEyePhaseOffset,
24485
+ rightEyePhaseOffset,
24486
+ };
24487
+ octopus3d2StableStateCache.set(createRandom, state);
24488
+ return state;
24489
+ }
24394
24490
  /**
24395
24491
  * Octopus 3D 2 avatar visual.
24396
24492
  *
@@ -24403,10 +24499,7 @@
24403
24499
  isAnimated: true,
24404
24500
  supportsPointerTracking: true,
24405
24501
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24406
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24407
- const animationRandom = createRandom('octopus3d2-animation-profile');
24408
- const eyeRandom = createRandom('octopus3d2-eye-profile');
24409
- const animationPhase = animationRandom() * Math.PI * 2;
24502
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
24410
24503
  const sceneCenterX = size * 0.5;
24411
24504
  const sceneCenterY = size * 0.575;
24412
24505
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -24459,8 +24552,8 @@
24459
24552
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
24460
24553
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
24461
24554
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
24462
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24463
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24555
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24556
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24464
24557
  drawProjectedOrganicMouth(context, [
24465
24558
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24466
24559
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -24489,14 +24582,28 @@
24489
24582
  /**
24490
24583
  * Draws the soft floor shadow that anchors the single mesh in the frame.
24491
24584
  *
24585
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24586
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24587
+ *
24492
24588
  * @private helper of `octopus3d2AvatarVisual`
24493
24589
  */
24494
24590
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24591
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
24592
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
24593
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
24594
+ const ry = size * 0.062;
24495
24595
  context.save();
24496
- context.fillStyle = `${palette.shadow}66`;
24497
- context.filter = `blur(${size * 0.024}px)`;
24596
+ context.translate(cx, cy);
24597
+ context.scale(1, ry / rx);
24598
+ const blurRadius = rx * 1.4;
24599
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24600
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24601
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24602
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24603
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24604
+ context.fillStyle = shadowGradient;
24498
24605
  context.beginPath();
24499
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.88 + Math.sin(timeMs / 940) * size * 0.008, size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018), size * 0.062, 0, 0, Math.PI * 2);
24606
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24500
24607
  context.fill();
24501
24608
  context.restore();
24502
24609
  }
@@ -24652,6 +24759,40 @@
24652
24759
  * @private helper of `octopus3d3AvatarVisual`
24653
24760
  */
24654
24761
  const OCTOPUS_TENTACLE_COUNT = 8;
24762
+ /**
24763
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
24764
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
24765
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
24766
+ *
24767
+ * @private helper of `octopus3d3AvatarVisual`
24768
+ */
24769
+ const stableStateCache = new WeakMap();
24770
+ /**
24771
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
24772
+ * result on every subsequent call within the same `<Avatar/>` mount.
24773
+ *
24774
+ * @private helper of `octopus3d3AvatarVisual`
24775
+ */
24776
+ function getOctopus3d3StableState(createRandom) {
24777
+ const cached = stableStateCache.get(createRandom);
24778
+ if (cached !== undefined) {
24779
+ return cached;
24780
+ }
24781
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24782
+ const animationRandom = createRandom('octopus3d3-animation-profile');
24783
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
24784
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
24785
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
24786
+ const state = {
24787
+ morphologyProfile,
24788
+ animationPhase: animationRandom() * Math.PI * 2,
24789
+ leftEyePhaseOffset,
24790
+ rightEyePhaseOffset,
24791
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
24792
+ };
24793
+ stableStateCache.set(createRandom, state);
24794
+ return state;
24795
+ }
24655
24796
  /**
24656
24797
  * Octopus 3D 3 avatar visual.
24657
24798
  *
@@ -24664,11 +24805,7 @@
24664
24805
  isAnimated: true,
24665
24806
  supportsPointerTracking: true,
24666
24807
  render({ context, size, palette, createRandom, timeMs, interaction }) {
24667
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
24668
- const animationRandom = createRandom('octopus3d3-animation-profile');
24669
- const eyeRandom = createRandom('octopus3d3-eye-profile');
24670
- const animationPhase = animationRandom() * Math.PI * 2;
24671
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
24808
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
24672
24809
  const sceneCenterX = size * 0.5;
24673
24810
  const sceneCenterY = size * 0.535;
24674
24811
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -24745,8 +24882,8 @@
24745
24882
  size,
24746
24883
  palette,
24747
24884
  });
24748
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24749
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
24885
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24886
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
24750
24887
  drawProjectedOrganicMouth(context, [
24751
24888
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
24752
24889
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -24797,14 +24934,30 @@
24797
24934
  /**
24798
24935
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
24799
24936
  *
24937
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
24938
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
24939
+ *
24800
24940
  * @private helper of `octopus3d3AvatarVisual`
24801
24941
  */
24802
24942
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
24943
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
24944
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
24945
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
24946
+ const ry = size * 0.06;
24947
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
24948
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
24803
24949
  context.save();
24804
- context.fillStyle = `${palette.shadow}66`;
24805
- context.filter = `blur(${size * 0.025}px)`;
24950
+ context.translate(cx, cy);
24951
+ context.scale(1, ry / rx);
24952
+ const blurRadius = rx * 1.4;
24953
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
24954
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
24955
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
24956
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
24957
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
24958
+ context.fillStyle = shadowGradient;
24806
24959
  context.beginPath();
24807
- context.ellipse(size * 0.5 + interaction.gazeX * size * 0.045, size * 0.9 + Math.sin(timeMs / 980) * size * 0.007, size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02), size * 0.06, 0, 0, Math.PI * 2);
24960
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
24808
24961
  context.fill();
24809
24962
  context.restore();
24810
24963
  }