@promptbook/core 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 +200 -45
  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 +1 -1
  20. package/umd/index.umd.js +200 -45
  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/core",
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,
package/umd/index.umd.js CHANGED
@@ -29,7 +29,7 @@
29
29
  * @generated
30
30
  * @see https://github.com/webgptorg/promptbook
31
31
  */
32
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-117';
32
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-119';
33
33
  /**
34
34
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
35
35
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -13064,21 +13064,22 @@
13064
13064
  * @private helper of `fractalAvatarVisual`
13065
13065
  */
13066
13066
  function drawDragonCurveLayer(context, points, options) {
13067
- const { size, primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
13067
+ const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
13068
13068
  const firstPoint = points[0];
13069
13069
  const lastPoint = points[points.length - 1];
13070
13070
  const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
13071
13071
  ribbonGradient.addColorStop(0, `${primaryColor}f2`);
13072
13072
  ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
13073
13073
  ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
13074
+ // Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
13075
+ // context.filter blur, which triggers a costly software rasterization pass every frame.
13074
13076
  context.save();
13075
13077
  context.beginPath();
13076
13078
  tracePolyline(context, points);
13077
- context.strokeStyle = `${shadowColor}82`;
13078
- context.lineWidth = strokeWidth * 1.8;
13079
+ context.strokeStyle = `${shadowColor}48`;
13080
+ context.lineWidth = strokeWidth * 4.5;
13079
13081
  context.lineJoin = 'round';
13080
13082
  context.lineCap = 'round';
13081
- context.filter = `blur(${size * 0.022}px)`;
13082
13083
  context.stroke();
13083
13084
  context.restore();
13084
13085
  context.beginPath();
@@ -13662,11 +13663,23 @@
13662
13663
  * @private helper of `minecraft2AvatarVisual`
13663
13664
  */
13664
13665
  function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
13666
+ const cx = size * 0.5 + interaction.gazeX * size * 0.03;
13667
+ const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
13668
+ const rx = size * (0.16 + interaction.intensity * 0.015);
13669
+ const ry = size * 0.055;
13670
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
13665
13671
  context.save();
13666
- context.fillStyle = `${palette.shadow}66`;
13667
- context.filter = `blur(${size * 0.02}px)`;
13672
+ context.translate(cx, cy);
13673
+ context.scale(1, ry / rx);
13674
+ const blurRadius = rx * 1.4;
13675
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13676
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
13677
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
13678
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
13679
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
13680
+ context.fillStyle = shadowGradient;
13668
13681
  context.beginPath();
13669
- 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);
13682
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13670
13683
  context.fill();
13671
13684
  context.restore();
13672
13685
  }
@@ -13890,13 +13903,27 @@
13890
13903
  spotlight.addColorStop(1, `${palette.highlight}00`);
13891
13904
  context.fillStyle = spotlight;
13892
13905
  context.fillRect(0, 0, size, size);
13893
- context.save();
13894
- context.fillStyle = 'rgba(0, 0, 0, 0.22)';
13895
- context.filter = `blur(${size * 0.018}px)`;
13896
- context.beginPath();
13897
- context.ellipse(size * 0.5, size * 0.86, size * 0.2, size * 0.06, 0, 0, Math.PI * 2);
13898
- context.fill();
13899
- context.restore();
13906
+ {
13907
+ // Radial gradient approximates the blurry ellipse shadow without context.filter blur.
13908
+ const cx = size * 0.5;
13909
+ const cy = size * 0.86;
13910
+ const rx = size * 0.2;
13911
+ const ry = size * 0.06;
13912
+ const blurRadius = rx * 1.4;
13913
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
13914
+ shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
13915
+ shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
13916
+ shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
13917
+ shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
13918
+ context.save();
13919
+ context.translate(cx, cy);
13920
+ context.scale(1, ry / rx);
13921
+ context.fillStyle = shadowGradient;
13922
+ context.beginPath();
13923
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
13924
+ context.fill();
13925
+ context.restore();
13926
+ }
13900
13927
  drawVoxelCuboid(context, {
13901
13928
  x: bodyX,
13902
13929
  y: bodyY,
@@ -14948,6 +14975,35 @@
14948
14975
  y: -0.62,
14949
14976
  z: 0.94,
14950
14977
  });
14978
+ /**
14979
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
14980
+ *
14981
+ * @private helper of `octopus3dAvatarVisual`
14982
+ */
14983
+ const octopus3dStableStateCache = new WeakMap();
14984
+ /**
14985
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
14986
+ *
14987
+ * @private helper of `octopus3dAvatarVisual`
14988
+ */
14989
+ function getOctopus3dStableState(createRandom) {
14990
+ const cached = octopus3dStableStateCache.get(createRandom);
14991
+ if (cached !== undefined) {
14992
+ return cached;
14993
+ }
14994
+ const animationRandom = createRandom('octopus3d-animation-profile');
14995
+ const eyeRandom = createRandom('octopus3d-eye-profile');
14996
+ const leftEyePhaseOffset = eyeRandom() * 0.6;
14997
+ const rightEyePhaseOffset = eyeRandom() * 0.6;
14998
+ const state = {
14999
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
15000
+ animationPhase: animationRandom() * Math.PI * 2,
15001
+ leftEyePhaseOffset,
15002
+ rightEyePhaseOffset,
15003
+ };
15004
+ octopus3dStableStateCache.set(createRandom, state);
15005
+ return state;
15006
+ }
14951
15007
  /**
14952
15008
  * Proper 3D Octopus visual built from projected organic meshes and tentacles.
14953
15009
  *
@@ -14960,10 +15016,7 @@
14960
15016
  isAnimated: true,
14961
15017
  supportsPointerTracking: true,
14962
15018
  render({ context, size, palette, createRandom, timeMs, interaction }) {
14963
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
14964
- const animationRandom = createRandom('octopus3d-animation-profile');
14965
- const eyeRandom = createRandom('octopus3d-eye-profile');
14966
- const animationPhase = animationRandom() * Math.PI * 2;
15019
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
14967
15020
  const sceneCenterX = size * 0.5;
14968
15021
  const sceneCenterY = size * 0.56;
14969
15022
  const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
@@ -15060,12 +15113,12 @@
15060
15113
  x: -faceEyeSpacing,
15061
15114
  y: faceEyeYOffset,
15062
15115
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
15063
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
15116
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15064
15117
  drawProjectedOrganicEye(context, {
15065
15118
  x: faceEyeSpacing,
15066
15119
  y: faceEyeYOffset,
15067
15120
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
15068
- }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
15121
+ }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15069
15122
  drawProjectedOrganicMouth(context, [
15070
15123
  {
15071
15124
  x: -mouthHalfWidth,
@@ -15109,14 +15162,28 @@
15109
15162
  /**
15110
15163
  * Draws the soft ground shadow below the octopus.
15111
15164
  *
15165
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
15166
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
15167
+ *
15112
15168
  * @private helper of `octopus3dAvatarVisual`
15113
15169
  */
15114
15170
  function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
15171
+ const cx = size * 0.5 + interaction.gazeX * size * 0.04;
15172
+ const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
15173
+ const rx = size * (0.18 + interaction.intensity * 0.02);
15174
+ const ry = size * 0.06;
15115
15175
  context.save();
15116
- context.fillStyle = `${palette.shadow}66`;
15117
- context.filter = `blur(${size * 0.022}px)`;
15176
+ context.translate(cx, cy);
15177
+ context.scale(1, ry / rx);
15178
+ const blurRadius = rx * 1.4;
15179
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15180
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
15181
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
15182
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
15183
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
15184
+ context.fillStyle = shadowGradient;
15118
15185
  context.beginPath();
15119
- 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);
15186
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15120
15187
  context.fill();
15121
15188
  context.restore();
15122
15189
  }
@@ -15347,6 +15414,35 @@
15347
15414
  y: -0.6,
15348
15415
  z: 0.98,
15349
15416
  });
15417
+ /**
15418
+ * Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
15419
+ *
15420
+ * @private helper of `octopus3d2AvatarVisual`
15421
+ */
15422
+ const octopus3d2StableStateCache = new WeakMap();
15423
+ /**
15424
+ * Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
15425
+ *
15426
+ * @private helper of `octopus3d2AvatarVisual`
15427
+ */
15428
+ function getOctopus3d2StableState(createRandom) {
15429
+ const cached = octopus3d2StableStateCache.get(createRandom);
15430
+ if (cached !== undefined) {
15431
+ return cached;
15432
+ }
15433
+ const animationRandom = createRandom('octopus3d2-animation-profile');
15434
+ const eyeRandom = createRandom('octopus3d2-eye-profile');
15435
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
15436
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
15437
+ const state = {
15438
+ morphologyProfile: createOctopus3MorphologyProfile(createRandom),
15439
+ animationPhase: animationRandom() * Math.PI * 2,
15440
+ leftEyePhaseOffset,
15441
+ rightEyePhaseOffset,
15442
+ };
15443
+ octopus3d2StableStateCache.set(createRandom, state);
15444
+ return state;
15445
+ }
15350
15446
  /**
15351
15447
  * Octopus 3D 2 avatar visual.
15352
15448
  *
@@ -15359,10 +15455,7 @@
15359
15455
  isAnimated: true,
15360
15456
  supportsPointerTracking: true,
15361
15457
  render({ context, size, palette, createRandom, timeMs, interaction }) {
15362
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15363
- const animationRandom = createRandom('octopus3d2-animation-profile');
15364
- const eyeRandom = createRandom('octopus3d2-eye-profile');
15365
- const animationPhase = animationRandom() * Math.PI * 2;
15458
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
15366
15459
  const sceneCenterX = size * 0.5;
15367
15460
  const sceneCenterY = size * 0.575;
15368
15461
  const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
@@ -15415,8 +15508,8 @@
15415
15508
  const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
15416
15509
  const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
15417
15510
  const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
15418
- drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15419
- drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15511
+ drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15512
+ drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15420
15513
  drawProjectedOrganicMouth(context, [
15421
15514
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
15422
15515
  sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -15445,14 +15538,28 @@
15445
15538
  /**
15446
15539
  * Draws the soft floor shadow that anchors the single mesh in the frame.
15447
15540
  *
15541
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
15542
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
15543
+ *
15448
15544
  * @private helper of `octopus3d2AvatarVisual`
15449
15545
  */
15450
15546
  function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
15547
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
15548
+ const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
15549
+ const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
15550
+ const ry = size * 0.062;
15451
15551
  context.save();
15452
- context.fillStyle = `${palette.shadow}66`;
15453
- context.filter = `blur(${size * 0.024}px)`;
15552
+ context.translate(cx, cy);
15553
+ context.scale(1, ry / rx);
15554
+ const blurRadius = rx * 1.4;
15555
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15556
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
15557
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
15558
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
15559
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
15560
+ context.fillStyle = shadowGradient;
15454
15561
  context.beginPath();
15455
- 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);
15562
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15456
15563
  context.fill();
15457
15564
  context.restore();
15458
15565
  }
@@ -15608,6 +15715,40 @@
15608
15715
  * @private helper of `octopus3d3AvatarVisual`
15609
15716
  */
15610
15717
  const OCTOPUS_TENTACLE_COUNT = 8;
15718
+ /**
15719
+ * Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
15720
+ * mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
15721
+ * React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
15722
+ *
15723
+ * @private helper of `octopus3d3AvatarVisual`
15724
+ */
15725
+ const stableStateCache = new WeakMap();
15726
+ /**
15727
+ * Returns the stable per-avatar state, computing it on first access and returning the cached
15728
+ * result on every subsequent call within the same `<Avatar/>` mount.
15729
+ *
15730
+ * @private helper of `octopus3d3AvatarVisual`
15731
+ */
15732
+ function getOctopus3d3StableState(createRandom) {
15733
+ const cached = stableStateCache.get(createRandom);
15734
+ if (cached !== undefined) {
15735
+ return cached;
15736
+ }
15737
+ const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15738
+ const animationRandom = createRandom('octopus3d3-animation-profile');
15739
+ const eyeRandom = createRandom('octopus3d3-eye-profile');
15740
+ const leftEyePhaseOffset = eyeRandom() * 0.7;
15741
+ const rightEyePhaseOffset = eyeRandom() * 0.7;
15742
+ const state = {
15743
+ morphologyProfile,
15744
+ animationPhase: animationRandom() * Math.PI * 2,
15745
+ leftEyePhaseOffset,
15746
+ rightEyePhaseOffset,
15747
+ tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
15748
+ };
15749
+ stableStateCache.set(createRandom, state);
15750
+ return state;
15751
+ }
15611
15752
  /**
15612
15753
  * Octopus 3D 3 avatar visual.
15613
15754
  *
@@ -15620,11 +15761,7 @@
15620
15761
  isAnimated: true,
15621
15762
  supportsPointerTracking: true,
15622
15763
  render({ context, size, palette, createRandom, timeMs, interaction }) {
15623
- const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
15624
- const animationRandom = createRandom('octopus3d3-animation-profile');
15625
- const eyeRandom = createRandom('octopus3d3-eye-profile');
15626
- const animationPhase = animationRandom() * Math.PI * 2;
15627
- const tentacleProfiles = createContinuousTentacleProfiles(createRandom, morphologyProfile);
15764
+ const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
15628
15765
  const sceneCenterX = size * 0.5;
15629
15766
  const sceneCenterY = size * 0.535;
15630
15767
  const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
@@ -15701,8 +15838,8 @@
15701
15838
  size,
15702
15839
  palette,
15703
15840
  });
15704
- drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + eyeRandom() * 0.7, interaction, morphologyProfile.face.eyeStyle);
15705
- 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);
15841
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15842
+ drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
15706
15843
  drawProjectedOrganicMouth(context, [
15707
15844
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
15708
15845
  sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
@@ -15753,14 +15890,30 @@
15753
15890
  /**
15754
15891
  * Draws the soft lower shadow that anchors the octopus in the avatar frame.
15755
15892
  *
15893
+ * Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
15894
+ * blurry ellipse without triggering a costly software rasterization pass on every frame.
15895
+ *
15756
15896
  * @private helper of `octopus3d3AvatarVisual`
15757
15897
  */
15758
15898
  function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
15899
+ const cx = size * 0.5 + interaction.gazeX * size * 0.045;
15900
+ const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
15901
+ const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
15902
+ const ry = size * 0.06;
15903
+ // Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
15904
+ // then fill with a radial gradient that approximates the blurry edge without context.filter.
15759
15905
  context.save();
15760
- context.fillStyle = `${palette.shadow}66`;
15761
- context.filter = `blur(${size * 0.025}px)`;
15906
+ context.translate(cx, cy);
15907
+ context.scale(1, ry / rx);
15908
+ const blurRadius = rx * 1.4;
15909
+ const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
15910
+ shadowGradient.addColorStop(0, `${palette.shadow}7a`);
15911
+ shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
15912
+ shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
15913
+ shadowGradient.addColorStop(1, `${palette.shadow}00`);
15914
+ context.fillStyle = shadowGradient;
15762
15915
  context.beginPath();
15763
- 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);
15916
+ context.arc(0, 0, blurRadius, 0, Math.PI * 2);
15764
15917
  context.fill();
15765
15918
  context.restore();
15766
15919
  }
@@ -29165,7 +29318,9 @@
29165
29318
  * @private internal utility of `Book`
29166
29319
  */
29167
29320
  function parseCommitmentHeader(line) {
29168
- const match = /^([A-Z][A-Z0-9]*(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
29321
+ // Require at least 2 characters in the first keyword word to avoid treating common
29322
+ // single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
29323
+ const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
29169
29324
  if (!match) {
29170
29325
  return null;
29171
29326
  }
@@ -39356,7 +39511,7 @@
39356
39511
  return { skippedReason: 'invalid_data_url' };
39357
39512
  }
39358
39513
  return {
39359
- file: new File([parsed.buffer], parsed.filename, {
39514
+ file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
39360
39515
  type: parsed.mimeType,
39361
39516
  }),
39362
39517
  sizeBytes: parsed.buffer.length,