@promptbook/components 0.112.0-126 → 0.112.0-128

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 (46) hide show
  1. package/README.md +5 -5
  2. package/esm/index.es.js +952 -243
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/_packages/components.index.d.ts +2 -0
  5. package/esm/src/_packages/types.index.d.ts +2 -0
  6. package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  7. package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  8. package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  9. package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  10. package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  11. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  12. package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  13. package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  14. package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  15. package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  16. package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  17. package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  18. package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  19. package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  20. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  21. package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  22. package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  23. package/esm/src/version.d.ts +1 -1
  24. package/package.json +1 -1
  25. package/umd/index.umd.js +952 -243
  26. package/umd/index.umd.js.map +1 -1
  27. package/umd/src/_packages/components.index.d.ts +2 -0
  28. package/umd/src/_packages/types.index.d.ts +2 -0
  29. package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
  30. package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
  31. package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
  32. package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
  33. package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  34. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
  35. package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
  36. package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
  37. package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
  38. package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
  39. package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
  40. package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
  41. package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
  42. package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
  43. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
  44. package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
  45. package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
  46. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
40
40
  * @generated
41
41
  * @see https://github.com/webgptorg/promptbook
42
42
  */
43
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-126';
43
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-128';
44
44
  /**
45
45
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
46
46
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -5275,41 +5275,91 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
5275
5275
  context.fill();
5276
5276
  context.restore();
5277
5277
  }
5278
+ /**
5279
+ * Number of latitude segments used by the single blobby octopus mesh.
5280
+ *
5281
+ * @private helper of `octopus3d2AvatarVisual`
5282
+ */
5283
+ const LATITUDE_PATCH_COUNT$1 = 12;
5284
+ /**
5285
+ * Number of longitude segments used by the single blobby octopus mesh.
5286
+ *
5287
+ * @private helper of `octopus3d2AvatarVisual`
5288
+ */
5289
+ const LONGITUDE_PATCH_COUNT$1 = 24;
5278
5290
  /**
5279
5291
  * Resolves all visible projected patches for the single blobby octopus mesh.
5280
5292
  *
5293
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
5294
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
5295
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
5296
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
5297
+ * corners are shared between neighboring patches.
5298
+ *
5281
5299
  * @private helper of `octopus3d2AvatarVisual`
5282
5300
  */
5283
5301
  function resolveVisibleBlobbyOctopusPatches(options) {
5284
5302
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
5285
- const latitudePatchCount = 12;
5286
- const longitudePatchCount = 24;
5303
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
5304
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
5287
5305
  const surfacePatches = [];
5306
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
5307
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
5308
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
5309
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
5310
+ }
5311
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5312
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
5313
+ }
5314
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
5315
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5316
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
5317
+ }
5318
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
5319
+ const transformedCornerSamples = new Array(cornerCount);
5320
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
5321
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
5322
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
5323
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
5324
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
5325
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
5326
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
5327
+ }
5328
+ }
5288
5329
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
5289
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
5290
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
5330
+ const startLatitude = latitudeBoundaries[latitudeIndex];
5331
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
5291
5332
  const centerLatitude = (startLatitude + endLatitude) / 2;
5292
5333
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
5334
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
5335
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
5293
5336
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5294
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
5295
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
5337
+ const startLongitude = longitudeBoundaries[longitudeIndex];
5338
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
5296
5339
  const centerLongitude = (startLongitude + endLongitude) / 2;
5297
- const localCorners = [
5298
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
5299
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
5300
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
5301
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
5340
+ const transformedCorners = [
5341
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
5342
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
5343
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
5344
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
5302
5345
  ];
5303
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
5304
5346
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
5305
5347
  if (surfaceNormal.z <= 0.01) {
5306
5348
  continue;
5307
5349
  }
5308
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
5350
+ const projectedCorners = [
5351
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
5352
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
5353
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
5354
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
5355
+ ];
5309
5356
  surfacePatches.push({
5310
5357
  corners: projectedCorners,
5311
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
5312
- transformedCorners.length,
5358
+ averageDepth: (transformedCorners[0].z +
5359
+ transformedCorners[1].z +
5360
+ transformedCorners[2].z +
5361
+ transformedCorners[3].z) /
5362
+ 4,
5313
5363
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
5314
5364
  fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
5315
5365
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
@@ -5327,12 +5377,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
5327
5377
  * @private helper of `octopus3d2AvatarVisual`
5328
5378
  */
5329
5379
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
5380
+ const { morphologyProfile, animationPhase, timeMs } = options;
5381
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
5382
+ }
5383
+ /**
5384
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
5385
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
5386
+ *
5387
+ * @private helper of `octopus3d2AvatarVisual`
5388
+ */
5389
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
5330
5390
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
5331
5391
  const cosineLatitude = Math.max(0, Math.cos(latitude));
5332
5392
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
5333
5393
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
5334
5394
  const lowerBlend = Math.pow(verticalProgress, 1.42);
5335
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
5336
5395
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
5337
5396
  const horizontalScale = 1.02 +
5338
5397
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -5629,45 +5688,102 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
5629
5688
  context.fill();
5630
5689
  context.restore();
5631
5690
  }
5691
+ /**
5692
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
5693
+ *
5694
+ * @private helper of `octopus3d3AvatarVisual`
5695
+ */
5696
+ const LATITUDE_PATCH_COUNT = 16;
5697
+ /**
5698
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
5699
+ *
5700
+ * @private helper of `octopus3d3AvatarVisual`
5701
+ */
5702
+ const LONGITUDE_PATCH_COUNT = 40;
5632
5703
  /**
5633
5704
  * Resolves visible projected patches for the continuous octopus mesh.
5634
5705
  *
5706
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
5707
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
5708
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
5709
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
5710
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
5711
+ *
5635
5712
  * @private helper of `octopus3d3AvatarVisual`
5636
5713
  */
5637
5714
  function resolveVisibleContinuousOctopusPatches(options) {
5638
5715
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
5639
- const latitudePatchCount = 16;
5640
- const longitudePatchCount = 40;
5716
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
5717
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
5641
5718
  const surfacePatches = [];
5719
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
5720
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
5721
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
5722
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
5723
+ }
5724
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5725
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
5726
+ }
5727
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
5728
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
5729
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
5730
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
5731
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
5732
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5733
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
5734
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
5735
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
5736
+ }
5737
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5738
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
5739
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
5740
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
5741
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
5742
+ }
5743
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
5744
+ const transformedCornerSamples = new Array(cornerCount);
5745
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
5746
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
5747
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
5748
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
5749
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
5750
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
5751
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
5752
+ }
5753
+ }
5642
5754
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
5643
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
5644
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
5755
+ const startLatitude = latitudeBoundaries[latitudeIndex];
5756
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
5645
5757
  const centerLatitude = (startLatitude + endLatitude) / 2;
5646
5758
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
5759
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
5760
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
5647
5761
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5648
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
5649
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
5650
- const centerLongitude = (startLongitude + endLongitude) / 2;
5651
- const localCorners = [
5652
- sampleContinuousOctopusSurfacePoint(options, startLatitude, startLongitude),
5653
- sampleContinuousOctopusSurfacePoint(options, startLatitude, endLongitude),
5654
- sampleContinuousOctopusSurfacePoint(options, endLatitude, endLongitude),
5655
- sampleContinuousOctopusSurfacePoint(options, endLatitude, startLongitude),
5762
+ const transformedCorners = [
5763
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
5764
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
5765
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
5766
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
5656
5767
  ];
5657
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
5658
5768
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
5659
5769
  if (surfaceNormal.z <= 0.008) {
5660
5770
  continue;
5661
5771
  }
5662
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
5663
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, centerLongitude);
5664
- const lowerLobeWave = resolveContinuousLobeWave(options, centerLongitude);
5772
+ const projectedCorners = [
5773
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
5774
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
5775
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
5776
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
5777
+ ];
5665
5778
  surfacePatches.push({
5666
5779
  corners: projectedCorners,
5667
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
5668
- transformedCorners.length,
5780
+ averageDepth: (transformedCorners[0].z +
5781
+ transformedCorners[1].z +
5782
+ transformedCorners[2].z +
5783
+ transformedCorners[3].z) /
5784
+ 4,
5669
5785
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
5670
- fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), tentacleInfluence.core, lowerLobeWave),
5786
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
5671
5787
  outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
5672
5788
  });
5673
5789
  }
@@ -5683,16 +5799,27 @@ function resolveVisibleContinuousOctopusPatches(options) {
5683
5799
  * @private helper of `octopus3d3AvatarVisual`
5684
5800
  */
5685
5801
  function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
5802
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
5803
+ }
5804
+ /**
5805
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
5806
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
5807
+ *
5808
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
5809
+ * corner grid, so the same longitude is reused across every latitude row and each
5810
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
5811
+ *
5812
+ * @private helper of `octopus3d3AvatarVisual`
5813
+ */
5814
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
5686
5815
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
5687
5816
  const cosineLatitude = Math.max(0, Math.cos(latitude));
5688
5817
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
5689
5818
  const upperBlend = Math.pow(1 - verticalProgress, 1.28);
5690
5819
  const lowerBlend = smoothStep(0.38, 1, verticalProgress);
5691
5820
  const tipBlend = smoothStep(0.68, 1, verticalProgress);
5692
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, longitude);
5693
5821
  const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
5694
5822
  const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
5695
- const lowerLobeWave = resolveContinuousLobeWave(options, longitude);
5696
5823
  const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
5697
5824
  animationPhase * 0.6 +
5698
5825
  timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
@@ -9381,8 +9508,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
9381
9508
  if (!trimmedContent) {
9382
9509
  return requirements;
9383
9510
  }
9384
- // Add goal as a proper h2 section to the system message
9385
- const goalSection = `## Goal\n\n${trimmedContent}`;
9511
+ const goalSection = spaceTrim$1((block) => `
9512
+ ## Goal
9513
+
9514
+ ${block(trimmedContent)}
9515
+ `);
9386
9516
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
9387
9517
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
9388
9518
  }
@@ -9904,8 +10034,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
9904
10034
  if (!trimmedContent) {
9905
10035
  return requirements;
9906
10036
  }
9907
- // Add language as a bullet under a ## Language section
9908
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
10037
+ const languageSection = spaceTrim$1((block) => `
10038
+ ## Language
10039
+
10040
+ - Your language is ${block(trimmedContent)}
10041
+ `);
9909
10042
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
9910
10043
  }
9911
10044
  }
@@ -13485,7 +13618,15 @@ function buildTeammateMetadata(entry) {
13485
13618
  * Builds the teammate request text, optionally including context.
13486
13619
  */
13487
13620
  function buildTeammateRequest(message, context) {
13488
- return context ? `${message}\n\nContext:\n${context}` : message;
13621
+ if (!context) {
13622
+ return message;
13623
+ }
13624
+ return spaceTrim$1((block) => `
13625
+ ${block(message)}
13626
+
13627
+ Context:
13628
+ ${block(context)}
13629
+ `);
13489
13630
  }
13490
13631
  /**
13491
13632
  * Builds a minimal chat prompt for teammate calls.
@@ -17508,7 +17649,11 @@ function createUseProjectToolFunctions() {
17508
17649
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
17509
17650
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
17510
17651
  const contentToReturn = wasCharacterTruncated
17511
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
17652
+ ? spaceTrim$1((block) => `
17653
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
17654
+
17655
+ [...truncated...]
17656
+ `)
17512
17657
  : lineRangedContent;
17513
17658
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
17514
17659
  if (wasCharacterTruncated) {
@@ -25810,11 +25955,19 @@ const FILE_EXTENSION_REGEX = /\.([a-z0-9]{1,10})$/i;
25810
25955
  * Pattern matching punctuation that should be trimmed from citation tails.
25811
25956
  */
25812
25957
  const TRAILING_PUNCTUATION_REGEX = /[.,;:!?)+\]]+$/;
25958
+ /**
25959
+ * Pattern matching filename separators that should become spaces in source labels.
25960
+ */
25961
+ const FILENAME_SEPARATOR_REGEX = /[-_]+/g;
25962
+ /**
25963
+ * Pattern matching consecutive whitespace in display labels.
25964
+ */
25965
+ const WHITESPACE_REGEX = /\s+/g;
25813
25966
  /**
25814
25967
  * Collapses consecutive whitespace into single spaces.
25815
25968
  */
25816
25969
  function collapseWhitespace(value) {
25817
- return value.replace(/\s+/g, ' ');
25970
+ return value.replace(WHITESPACE_REGEX, ' ');
25818
25971
  }
25819
25972
  /**
25820
25973
  * Trims a value before further citation normalization.
@@ -25887,6 +26040,10 @@ function isPlainTextCitation(citation) {
25887
26040
  * @private utility of `<Chat/>` citation rendering
25888
26041
  */
25889
26042
  function getCitationLabel(citation) {
26043
+ const title = normalizeCitationDisplayLabel(citation.title);
26044
+ if (title) {
26045
+ return title;
26046
+ }
25890
26047
  const trimmed = trimToNormalized(citation.source);
25891
26048
  if (!trimmed) {
25892
26049
  return citation.source;
@@ -25898,7 +26055,7 @@ function getCitationLabel(citation) {
25898
26055
  }
25899
26056
  return collapsed.slice(0, TEXT_LABEL_LENGTH) + LABEL_ELLIPSIS;
25900
26057
  }
25901
- return simplifyKnowledgeLabel(trimmed);
26058
+ return createReadableCitationSourceLabel(trimmed);
25902
26059
  }
25903
26060
  /**
25904
26061
  * Resolves the preview URL used inside the citation modal iframe.
@@ -25917,8 +26074,134 @@ function resolveCitationPreviewUrl(citation, participants) {
25917
26074
  const knowledgeUrl = resolveCitationUrl(trimmed, participants);
25918
26075
  return explicitUrl || literalUrl || knowledgeUrl || null;
25919
26076
  }
26077
+ /**
26078
+ * Creates a readable fallback label from a citation source when no title metadata is available.
26079
+ *
26080
+ * @param source - Raw citation source value.
26081
+ * @returns Human-friendly source label.
26082
+ *
26083
+ * @private utility of `<Chat/>` citation rendering
26084
+ */
26085
+ function createReadableCitationSourceLabel(source) {
26086
+ const trimmed = trimToNormalized(source);
26087
+ if (!trimmed) {
26088
+ return source;
26089
+ }
26090
+ const parsedUrl = parseCitationUrl(trimmed);
26091
+ const filenameCandidate = parsedUrl
26092
+ ? getUrlLabelCandidate(parsedUrl) || parsedUrl.hostname.replace(/^www\./i, '')
26093
+ : simplifyKnowledgeLabel(trimmed);
26094
+ return normalizeCitationDisplayLabel(filenameCandidate) || simplifyKnowledgeLabel(trimmed);
26095
+ }
26096
+ /**
26097
+ * Normalizes a citation label candidate for display.
26098
+ *
26099
+ * @param label - Raw candidate label.
26100
+ * @returns Cleaned label or `null` when empty.
26101
+ *
26102
+ * @private utility of `<Chat/>` citation rendering
26103
+ */
26104
+ function normalizeCitationDisplayLabel(label) {
26105
+ const normalized = collapseWhitespace((label || '').replace(FILENAME_SEPARATOR_REGEX, ' ')).trim();
26106
+ return normalized || null;
26107
+ }
26108
+ /**
26109
+ * Parses one HTTP(S) citation URL, returning null for non-URL values.
26110
+ *
26111
+ * @param value - Candidate URL value.
26112
+ * @returns Parsed URL or null.
26113
+ *
26114
+ * @private utility of `<Chat/>` citation rendering
26115
+ */
26116
+ function parseCitationUrl(value) {
26117
+ try {
26118
+ const url = new URL(value);
26119
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url : null;
26120
+ }
26121
+ catch (_a) {
26122
+ return null;
26123
+ }
26124
+ }
26125
+ /**
26126
+ * Extracts a readable candidate from a URL path.
26127
+ *
26128
+ * @param url - Parsed citation URL.
26129
+ * @returns URL path label candidate or null.
26130
+ *
26131
+ * @private utility of `<Chat/>` citation rendering
26132
+ */
26133
+ function getUrlLabelCandidate(url) {
26134
+ const pathSegments = url.pathname.split('/').filter(Boolean);
26135
+ const lastPathSegment = pathSegments[pathSegments.length - 1];
26136
+ if (!lastPathSegment) {
26137
+ return null;
26138
+ }
26139
+ try {
26140
+ return simplifyKnowledgeLabel(decodeURIComponent(lastPathSegment));
26141
+ }
26142
+ catch (_a) {
26143
+ return simplifyKnowledgeLabel(lastPathSegment);
26144
+ }
26145
+ }
25920
26146
  // TODO: [💞] Spread into multiple files
25921
26147
 
26148
+ /**
26149
+ * Extracts one quick button definition from a markdown link query string.
26150
+ *
26151
+ * @param text The visible button label.
26152
+ * @param query Raw query-string payload captured from markdown.
26153
+ * @returns Parsed quick button definition or `null` when the link is not a supported quick button.
26154
+ *
26155
+ * @private internal helper of `parseMessageButtons`
26156
+ */
26157
+ function parseQuickButtonDefinition(text, query) {
26158
+ const searchParams = new URLSearchParams(query);
26159
+ const message = searchParams.get('message');
26160
+ if (message !== null) {
26161
+ return {
26162
+ type: 'message',
26163
+ text,
26164
+ message,
26165
+ };
26166
+ }
26167
+ const code = searchParams.get('action');
26168
+ if (code !== null) {
26169
+ return {
26170
+ type: 'action',
26171
+ text,
26172
+ code,
26173
+ };
26174
+ }
26175
+ return null;
26176
+ }
26177
+ /**
26178
+ * Parses markdown quick buttons in the format `[Button Text](?message=...)` or `[Button Text](?action=...)`.
26179
+ * Returns both the content without supported quick buttons and the extracted button definitions.
26180
+ *
26181
+ * @param content The markdown content that may contain buttons
26182
+ * @returns Object with contentWithoutButtons and buttons array
26183
+ *
26184
+ * @public exported from `@promptbook/components`
26185
+ */
26186
+ function parseMessageButtons(content) {
26187
+ const buttonRegex = /\[([^\]]+)\]\(\?([^)]+)\)/g;
26188
+ const buttons = [];
26189
+ const contentWithoutButtons = content
26190
+ .replace(buttonRegex, (match, text, query) => {
26191
+ const button = parseQuickButtonDefinition(text, query);
26192
+ if (!button) {
26193
+ return match;
26194
+ }
26195
+ buttons.push(button);
26196
+ return '';
26197
+ })
26198
+ .trim();
26199
+ return {
26200
+ contentWithoutButtons,
26201
+ buttons,
26202
+ };
26203
+ }
26204
+
25922
26205
  /**
25923
26206
  * Default citation identifier used for simplified citation markers without an explicit position.
25924
26207
  *
@@ -26809,12 +27092,13 @@ function resolveCitationForToken(rawToken, bracketCitationId, citationsById) {
26809
27092
  * @private utility of `<Chat/>`
26810
27093
  */
26811
27094
  function normalizeParsedCitation(citation) {
26812
- var _a, _b;
27095
+ var _a, _b, _c;
26813
27096
  return {
26814
27097
  id: citation.id.trim(),
26815
27098
  source: citation.source.trim(),
26816
27099
  url: ((_a = citation.url) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
26817
- excerpt: ((_b = citation.excerpt) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
27100
+ title: ((_b = citation.title) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
27101
+ excerpt: ((_c = citation.excerpt) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
26818
27102
  };
26819
27103
  }
26820
27104
  /**
@@ -26831,11 +27115,13 @@ function mergeParsedCitations(currentCitation, incomingCitation) {
26831
27115
  id: currentCitation.id || incomingCitation.id,
26832
27116
  source: resolvePreferredCitationSource(currentCitation, incomingCitation),
26833
27117
  url: currentCitation.url || incomingCitation.url,
27118
+ title: currentCitation.title || incomingCitation.title,
26834
27119
  excerpt: currentCitation.excerpt || incomingCitation.excerpt,
26835
27120
  };
26836
27121
  if (mergedCitation.id === currentCitation.id &&
26837
27122
  mergedCitation.source === currentCitation.source &&
26838
27123
  mergedCitation.url === currentCitation.url &&
27124
+ mergedCitation.title === currentCitation.title &&
26839
27125
  mergedCitation.excerpt === currentCitation.excerpt) {
26840
27126
  return currentCitation;
26841
27127
  }
@@ -27185,6 +27471,12 @@ const CITATION_FOOTNOTE_REFERENCE_HTML_REGEX = /<sup data-citation-footnote="(\d
27185
27471
  * @private Internal helper shared by HTML and PDF chat exports.
27186
27472
  */
27187
27473
  const CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME = 'chat-html-export-render-root';
27474
+ /**
27475
+ * Roles whose messages are aligned to the right side of the exported transcript.
27476
+ *
27477
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27478
+ */
27479
+ const RIGHT_ALIGNED_SENDER_ROLES = new Set(['USER']);
27188
27480
  /**
27189
27481
  * Escapes HTML-sensitive text before embedding it into the export document.
27190
27482
  *
@@ -27198,6 +27490,38 @@ function escapeHtml$1(value) {
27198
27490
  .replace(/"/g, '&quot;')
27199
27491
  .replace(/'/g, '&#39;');
27200
27492
  }
27493
+ /**
27494
+ * Returns the first uppercase character of a display name, used for the fallback avatar glyph.
27495
+ *
27496
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27497
+ */
27498
+ function getAvatarInitial(displayName) {
27499
+ const trimmedName = displayName.trim();
27500
+ if (!trimmedName) {
27501
+ return '?';
27502
+ }
27503
+ return Array.from(trimmedName)[0].toUpperCase();
27504
+ }
27505
+ /**
27506
+ * Renders the avatar bubble shown next to one exported message.
27507
+ *
27508
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27509
+ */
27510
+ function buildAvatarMarkup(participant, displayName, accentColor) {
27511
+ const avatarStyle = `background-color:${escapeHtml$1(accentColor)};`;
27512
+ if (participant === null || participant === void 0 ? void 0 : participant.avatarSrc) {
27513
+ return spaceTrim$1(`
27514
+ <div class="message-avatar" aria-hidden="true" style="${avatarStyle}">
27515
+ <img src="${escapeHtml$1(participant.avatarSrc)}" alt="" loading="lazy" />
27516
+ </div>
27517
+ `);
27518
+ }
27519
+ return spaceTrim$1(`
27520
+ <div class="message-avatar message-avatar--initial" aria-hidden="true" style="${avatarStyle}">
27521
+ <span>${escapeHtml$1(getAvatarInitial(displayName))}</span>
27522
+ </div>
27523
+ `);
27524
+ }
27201
27525
  /**
27202
27526
  * Renders attachments for one exported message as a compact supporting section.
27203
27527
  *
@@ -27261,13 +27585,26 @@ function buildCitationsMarkup(message) {
27261
27585
  </section>
27262
27586
  `);
27263
27587
  }
27588
+ /**
27589
+ * Removes quick action and quick message buttons from the markdown body before rendering.
27590
+ *
27591
+ * Quick buttons are interactive UI affordances and have no meaning inside a static export.
27592
+ *
27593
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27594
+ */
27595
+ function stripQuickButtonsFromContent(content) {
27596
+ return parseMessageButtons(content).contentWithoutButtons;
27597
+ }
27264
27598
  /**
27265
27599
  * Converts one markdown message body to HTML with document-wide source references.
27266
27600
  *
27267
27601
  * @private Internal helper of `htmlSaveFormatDefinition`.
27268
27602
  */
27269
27603
  function renderFootnotedMarkdown(message, citationFootnotes) {
27270
- const renderModel = createChatExportCitationRenderModel(citationFootnotes, message);
27604
+ const renderModel = createChatExportCitationRenderModel(citationFootnotes, {
27605
+ ...message,
27606
+ content: stripQuickButtonsFromContent(message.content),
27607
+ });
27271
27608
  const html = linkCitationFootnoteReferences(renderMarkdown(renderModel.content));
27272
27609
  return {
27273
27610
  html,
@@ -27305,34 +27642,74 @@ function buildReplyingToMarkup(message, citationFootnotes) {
27305
27642
  `);
27306
27643
  }
27307
27644
  /**
27308
- * Renders one message card for the standalone HTML export.
27645
+ * Determines whether a message bubble should sit on the right side of the transcript.
27646
+ *
27647
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27648
+ */
27649
+ function isRightAlignedMessage(participant, sender) {
27650
+ if ((participant === null || participant === void 0 ? void 0 : participant.isMe) === true) {
27651
+ return true;
27652
+ }
27653
+ return RIGHT_ALIGNED_SENDER_ROLES.has(sender.toUpperCase());
27654
+ }
27655
+ /**
27656
+ * Detects whether a message would render with no body, attachments, or supporting metadata after sanitization.
27657
+ *
27658
+ * Such messages exist only to carry quick buttons in the live chat UI and add no value to a printed transcript.
27659
+ *
27660
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27661
+ */
27662
+ function isMessageEmptyForExport(message) {
27663
+ const trimmedContent = stripQuickButtonsFromContent(message.content).trim();
27664
+ if (trimmedContent.length > 0) {
27665
+ return false;
27666
+ }
27667
+ if (message.attachments && message.attachments.length > 0) {
27668
+ return false;
27669
+ }
27670
+ if (message.citations && message.citations.length > 0) {
27671
+ return false;
27672
+ }
27673
+ if (message.replyingTo) {
27674
+ return false;
27675
+ }
27676
+ return true;
27677
+ }
27678
+ /**
27679
+ * Renders one message bubble for the standalone HTML export.
27309
27680
  *
27310
27681
  * @private Internal helper of `htmlSaveFormatDefinition`.
27311
27682
  */
27312
27683
  function renderMessageBlock(message, participants, citationFootnotes) {
27684
+ var _a;
27313
27685
  const sender = String(message.sender || 'SYSTEM');
27314
- const upperSender = sender.toUpperCase();
27686
+ const participant = (_a = participants.get(sender)) !== null && _a !== void 0 ? _a : participants.get(sender.toUpperCase());
27315
27687
  const visuals = resolveChatExportParticipantVisuals(participants, sender);
27316
27688
  const timestamp = formatChatExportTimestamp(message.createdAt);
27317
27689
  const durationLabel = typeof message.generationDurationMs === 'number' ? `${(message.generationDurationMs / 1000).toFixed(1)}s` : '';
27318
27690
  const replyingToMarkup = buildReplyingToMarkup(message, citationFootnotes);
27319
27691
  const { html: messageBody, renderModel } = renderFootnotedMarkdown(message, citationFootnotes);
27692
+ const isRightAligned = isRightAlignedMessage(participant, sender);
27693
+ const avatarMarkup = buildAvatarMarkup(participant, visuals.displayName, visuals.accentColor);
27694
+ const alignmentClass = isRightAligned ? 'message--mine' : 'message--theirs';
27320
27695
  return spaceTrim$1(`
27321
- <article class="message-card" style="--message-accent:${escapeHtml$1(visuals.accentColor)};">
27322
- <header class="message-header">
27323
- <div class="message-header-main">
27696
+ <article class="message ${alignmentClass}" style="--message-accent:${escapeHtml$1(visuals.accentColor)};">
27697
+ ${avatarMarkup}
27698
+ <div class="message-body">
27699
+ <header class="message-meta">
27324
27700
  <span class="message-sender">${escapeHtml$1(visuals.displayName)}</span>
27325
- <span class="message-role">${escapeHtml$1(upperSender)}</span>
27701
+ ${timestamp ? `<time class="message-time">${escapeHtml$1(timestamp)}</time>` : ''}
27702
+ </header>
27703
+ <div class="message-bubble">
27704
+ ${replyingToMarkup}
27705
+ <div class="message-content markdown-content">
27706
+ ${messageBody || '<p class="message-empty">No text provided.</p>'}
27707
+ </div>
27326
27708
  </div>
27327
- ${timestamp ? `<time class="message-time">${escapeHtml$1(timestamp)}</time>` : ''}
27328
- </header>
27329
- ${replyingToMarkup}
27330
- <section class="message-content markdown-content">
27331
- ${messageBody || '<p class="message-empty">No text provided.</p>'}
27332
- </section>
27333
- ${durationLabel ? `<p class="message-duration">Responded in ${escapeHtml$1(durationLabel)}</p>` : ''}
27334
- ${buildAttachmentsMarkup(message)}
27335
- ${renderModel.footnotes.length === 0 ? buildCitationsMarkup(message) : ''}
27709
+ ${durationLabel ? `<p class="message-footnote">Responded in ${escapeHtml$1(durationLabel)}</p>` : ''}
27710
+ ${buildAttachmentsMarkup(message)}
27711
+ ${renderModel.footnotes.length === 0 ? buildCitationsMarkup(message) : ''}
27712
+ </div>
27336
27713
  </article>
27337
27714
  `);
27338
27715
  }
@@ -27396,8 +27773,11 @@ function buildChatHtml(title, messages, participants) {
27396
27773
  const participantLookup = buildChatExportParticipantMap(participants);
27397
27774
  const citationFootnotes = createChatExportCitationFootnoteRegistry();
27398
27775
  const exportedLabel = formatChatExportTimestamp(new Date());
27399
- const messageMarkup = messages.length > 0
27400
- ? messages.map((message) => renderMessageBlock(message, participantLookup, citationFootnotes)).join('')
27776
+ const messagesForExport = messages.filter((message) => !isMessageEmptyForExport(message));
27777
+ const messageMarkup = messagesForExport.length > 0
27778
+ ? messagesForExport
27779
+ .map((message) => renderMessageBlock(message, participantLookup, citationFootnotes))
27780
+ .join('')
27401
27781
  : '<div class="empty-state">No messages were available in this chat export.</div>';
27402
27782
  const documentSourcesMarkup = buildDocumentSourcesMarkup(citationFootnotes, participants);
27403
27783
  return spaceTrim$1(`
@@ -27411,6 +27791,17 @@ function buildChatHtml(title, messages, participants) {
27411
27791
  <style>
27412
27792
  :root {
27413
27793
  color-scheme: light;
27794
+ --chat-export-background: #f4f6fb;
27795
+ --chat-export-surface: #ffffff;
27796
+ --chat-export-text: #0f172a;
27797
+ --chat-export-muted: #64748b;
27798
+ --chat-export-soft-border: #e2e8f0;
27799
+ --chat-export-bubble-theirs-bg: #ffffff;
27800
+ --chat-export-bubble-theirs-text: #0f172a;
27801
+ --chat-export-bubble-theirs-border: #e2e8f0;
27802
+ --chat-export-bubble-mine-bg: #115EB6;
27803
+ --chat-export-bubble-mine-text: #ffffff;
27804
+ --chat-export-link: #0f6cbd;
27414
27805
  }
27415
27806
 
27416
27807
  * {
@@ -27420,25 +27811,27 @@ function buildChatHtml(title, messages, participants) {
27420
27811
  body,
27421
27812
  .${CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME} {
27422
27813
  margin: 0;
27423
- background: #f8fafc;
27424
- color: #0f172a;
27425
- font-family: Inter, "Segoe UI", Arial, sans-serif;
27814
+ background: var(--chat-export-background);
27815
+ color: var(--chat-export-text);
27816
+ font-family: Inter, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
27817
+ font-size: 15px;
27818
+ line-height: 1.55;
27426
27819
  }
27427
27820
 
27428
27821
  a {
27429
- color: #0f6cbd;
27822
+ color: var(--chat-export-link);
27430
27823
  }
27431
27824
 
27432
27825
  .document {
27433
- max-width: 860px;
27826
+ max-width: 820px;
27434
27827
  margin: 0 auto;
27435
- padding: 32px 20px 48px;
27828
+ padding: 40px 24px 56px;
27436
27829
  }
27437
27830
 
27438
27831
  .document-header {
27439
- margin-bottom: 24px;
27440
- padding-bottom: 16px;
27441
- border-bottom: 1px solid #e2e8f0;
27832
+ margin-bottom: 28px;
27833
+ padding-bottom: 18px;
27834
+ border-bottom: 1px solid var(--chat-export-soft-border);
27442
27835
  }
27443
27836
 
27444
27837
  .document-eyebrow {
@@ -27446,7 +27839,7 @@ function buildChatHtml(title, messages, participants) {
27446
27839
  font-size: 11px;
27447
27840
  letter-spacing: 0.14em;
27448
27841
  text-transform: uppercase;
27449
- color: #64748b;
27842
+ color: var(--chat-export-muted);
27450
27843
  }
27451
27844
 
27452
27845
  .document-title {
@@ -27457,89 +27850,174 @@ function buildChatHtml(title, messages, participants) {
27457
27850
 
27458
27851
  .document-meta {
27459
27852
  margin: 10px 0 0;
27460
- color: #475569;
27461
- font-size: 14px;
27853
+ color: var(--chat-export-muted);
27854
+ font-size: 13px;
27462
27855
  }
27463
27856
 
27464
27857
  .message-list {
27465
27858
  display: flex;
27466
27859
  flex-direction: column;
27467
- gap: 16px;
27860
+ gap: 22px;
27468
27861
  }
27469
27862
 
27470
- .message-card {
27471
- background: #ffffff;
27472
- border: 1px solid #dbe4f0;
27473
- border-left-width: 4px;
27474
- border-left-color: var(--message-accent, #64748b);
27475
- border-radius: 14px;
27476
- padding: 16px 18px;
27477
- }
27478
-
27479
- .message-header {
27863
+ .message {
27480
27864
  display: flex;
27481
- justify-content: space-between;
27482
27865
  align-items: flex-start;
27483
27866
  gap: 12px;
27484
- margin-bottom: 12px;
27485
27867
  }
27486
27868
 
27487
- .message-header-main {
27869
+ .message--mine {
27870
+ flex-direction: row-reverse;
27871
+ }
27872
+
27873
+ .message-avatar {
27874
+ flex-shrink: 0;
27875
+ width: 40px;
27876
+ height: 40px;
27877
+ border-radius: 50%;
27878
+ overflow: hidden;
27879
+ background-color: var(--message-accent, #64748b);
27880
+ color: #ffffff;
27488
27881
  display: flex;
27489
- flex-wrap: wrap;
27490
27882
  align-items: center;
27883
+ justify-content: center;
27884
+ }
27885
+
27886
+ .message-avatar img {
27887
+ width: 100%;
27888
+ height: 100%;
27889
+ object-fit: cover;
27890
+ display: block;
27891
+ }
27892
+
27893
+ .message-avatar--initial span {
27894
+ font-size: 16px;
27895
+ font-weight: 600;
27896
+ line-height: 1;
27897
+ }
27898
+
27899
+ .message-body {
27900
+ flex: 1 1 auto;
27901
+ min-width: 0;
27902
+ max-width: 78%;
27903
+ display: flex;
27904
+ flex-direction: column;
27905
+ gap: 6px;
27906
+ }
27907
+
27908
+ .message--mine .message-body {
27909
+ align-items: flex-end;
27910
+ }
27911
+
27912
+ .message-meta {
27913
+ display: flex;
27914
+ flex-wrap: wrap;
27915
+ align-items: baseline;
27491
27916
  gap: 8px;
27917
+ padding: 0 4px;
27918
+ }
27919
+
27920
+ .message--mine .message-meta {
27921
+ justify-content: flex-end;
27492
27922
  }
27493
27923
 
27494
27924
  .message-sender {
27495
- font-size: 16px;
27925
+ font-size: 13px;
27496
27926
  font-weight: 600;
27497
- color: var(--message-accent, #0f172a);
27927
+ color: var(--message-accent, var(--chat-export-text));
27928
+ }
27929
+
27930
+ .message--mine .message-sender {
27931
+ color: var(--chat-export-text);
27498
27932
  }
27499
27933
 
27500
- .message-role {
27934
+ .message-time {
27501
27935
  font-size: 11px;
27502
- letter-spacing: 0.12em;
27503
- text-transform: uppercase;
27504
- color: #64748b;
27936
+ color: var(--chat-export-muted);
27505
27937
  }
27506
27938
 
27507
- .message-time,
27508
- .message-duration {
27509
- font-size: 12px;
27510
- color: #64748b;
27939
+ .message-bubble {
27940
+ padding: 14px 16px;
27941
+ border-radius: 16px;
27942
+ background: var(--chat-export-bubble-theirs-bg);
27943
+ color: var(--chat-export-bubble-theirs-text);
27944
+ border: 1px solid var(--chat-export-bubble-theirs-border);
27945
+ border-top-left-radius: 4px;
27946
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
27947
+ max-width: 100%;
27948
+ overflow-wrap: anywhere;
27949
+ }
27950
+
27951
+ .message--mine .message-bubble {
27952
+ background: var(--chat-export-bubble-mine-bg);
27953
+ color: var(--chat-export-bubble-mine-text);
27954
+ border-color: transparent;
27955
+ border-top-left-radius: 16px;
27956
+ border-top-right-radius: 4px;
27957
+ }
27958
+
27959
+ .message--mine .message-bubble a {
27960
+ color: #d4e8ff;
27961
+ }
27962
+
27963
+ .message--mine .message-bubble code {
27964
+ background: rgba(255, 255, 255, 0.18);
27965
+ color: inherit;
27511
27966
  }
27512
27967
 
27513
- .message-duration {
27514
- margin: 12px 0 0;
27968
+ .message--mine .message-bubble pre {
27969
+ background: rgba(15, 23, 42, 0.35);
27970
+ color: #f8fafc;
27971
+ }
27972
+
27973
+ .message--mine .message-bubble blockquote {
27974
+ color: rgba(255, 255, 255, 0.85);
27975
+ border-color: rgba(255, 255, 255, 0.5);
27976
+ }
27977
+
27978
+ .message-footnote {
27979
+ margin: 2px 4px 0;
27980
+ font-size: 11px;
27981
+ color: var(--chat-export-muted);
27515
27982
  }
27516
27983
 
27517
27984
  .message-empty {
27518
- color: #64748b;
27985
+ color: var(--chat-export-muted);
27519
27986
  font-style: italic;
27987
+ margin: 0;
27520
27988
  }
27521
27989
 
27522
27990
  .reply-context {
27523
- margin-bottom: 14px;
27524
- padding: 12px 14px;
27991
+ margin: 0 0 10px;
27992
+ padding: 8px 12px;
27525
27993
  border-radius: 10px;
27526
- border: 1px solid #e2e8f0;
27527
- background: #f8fafc;
27994
+ border-left: 3px solid var(--message-accent, #cbd5e1);
27995
+ background: rgba(15, 23, 42, 0.04);
27996
+ }
27997
+
27998
+ .message--mine .reply-context {
27999
+ background: rgba(255, 255, 255, 0.18);
28000
+ border-left-color: rgba(255, 255, 255, 0.7);
27528
28001
  }
27529
28002
 
27530
28003
  .reply-context-title,
27531
28004
  .message-supporting-title {
27532
- margin-bottom: 8px;
28005
+ margin-bottom: 6px;
27533
28006
  font-size: 11px;
27534
- letter-spacing: 0.12em;
28007
+ letter-spacing: 0.1em;
27535
28008
  text-transform: uppercase;
27536
- color: #64748b;
28009
+ color: var(--chat-export-muted);
28010
+ }
28011
+
28012
+ .message--mine .reply-context-title {
28013
+ color: rgba(255, 255, 255, 0.8);
27537
28014
  }
27538
28015
 
27539
28016
  .message-supporting-list {
27540
- margin-top: 14px;
27541
- padding-top: 12px;
27542
- border-top: 1px solid #e2e8f0;
28017
+ margin-top: 10px;
28018
+ padding: 10px 14px;
28019
+ border-radius: 10px;
28020
+ background: rgba(15, 23, 42, 0.03);
27543
28021
  }
27544
28022
 
27545
28023
  .message-supporting-list ul {
@@ -27549,18 +28027,14 @@ function buildChatHtml(title, messages, participants) {
27549
28027
 
27550
28028
  .message-supporting-meta {
27551
28029
  margin-right: 6px;
27552
- color: #64748b;
28030
+ color: var(--chat-export-muted);
27553
28031
  font-size: 12px;
27554
28032
  }
27555
28033
 
27556
28034
  .message-supporting-excerpt {
27557
- margin-top: 6px;
28035
+ margin-top: 4px;
27558
28036
  color: #475569;
27559
- }
27560
-
27561
- .markdown-content {
27562
- font-size: 15px;
27563
- line-height: 1.65;
28037
+ font-size: 13px;
27564
28038
  }
27565
28039
 
27566
28040
  .markdown-content > *:first-child {
@@ -27588,7 +28062,7 @@ function buildChatHtml(title, messages, participants) {
27588
28062
  .markdown-content pre,
27589
28063
  .markdown-content table,
27590
28064
  .markdown-content details {
27591
- margin: 0 0 14px;
28065
+ margin: 0 0 12px;
27592
28066
  }
27593
28067
 
27594
28068
  .markdown-content ul,
@@ -27598,7 +28072,7 @@ function buildChatHtml(title, messages, participants) {
27598
28072
 
27599
28073
  .markdown-content code {
27600
28074
  font-family: ui-monospace, "SFMono-Regular", monospace;
27601
- background: #f1f5f9;
28075
+ background: rgba(15, 23, 42, 0.06);
27602
28076
  border-radius: 4px;
27603
28077
  padding: 1px 5px;
27604
28078
  font-size: 0.94em;
@@ -27650,7 +28124,7 @@ function buildChatHtml(title, messages, participants) {
27650
28124
 
27651
28125
  .markdown-content details {
27652
28126
  padding: 12px 14px;
27653
- border: 1px solid #e2e8f0;
28127
+ border: 1px solid var(--chat-export-soft-border);
27654
28128
  border-radius: 10px;
27655
28129
  background: #f8fafc;
27656
28130
  }
@@ -27672,7 +28146,7 @@ function buildChatHtml(title, messages, participants) {
27672
28146
  }
27673
28147
 
27674
28148
  .markdown-content sup[data-citation-footnote] a {
27675
- color: #0f6cbd;
28149
+ color: inherit;
27676
28150
  text-decoration: none;
27677
28151
  }
27678
28152
 
@@ -27685,14 +28159,14 @@ function buildChatHtml(title, messages, participants) {
27685
28159
  border: 1px dashed #cbd5e1;
27686
28160
  border-radius: 14px;
27687
28161
  text-align: center;
27688
- color: #64748b;
27689
- background: #ffffff;
28162
+ color: var(--chat-export-muted);
28163
+ background: var(--chat-export-surface);
27690
28164
  }
27691
28165
 
27692
28166
  .document-sources {
27693
- margin-top: 24px;
27694
- padding-top: 16px;
27695
- border-top: 1px solid #e2e8f0;
28167
+ margin-top: 32px;
28168
+ padding-top: 18px;
28169
+ border-top: 1px solid var(--chat-export-soft-border);
27696
28170
  }
27697
28171
 
27698
28172
  .document-sources-title {
@@ -27718,26 +28192,50 @@ function buildChatHtml(title, messages, participants) {
27718
28192
  }
27719
28193
 
27720
28194
  .document-footer {
27721
- margin-top: 24px;
28195
+ margin-top: 28px;
27722
28196
  padding-top: 16px;
27723
- border-top: 1px solid #e2e8f0;
27724
- color: #64748b;
28197
+ border-top: 1px solid var(--chat-export-soft-border);
28198
+ color: var(--chat-export-muted);
27725
28199
  font-size: 12px;
28200
+ text-align: center;
27726
28201
  }
27727
28202
 
27728
28203
  .document-footer strong {
27729
28204
  color: #475569;
27730
28205
  }
27731
28206
 
28207
+ @page {
28208
+ size: Letter;
28209
+ margin: 0.5in;
28210
+ }
28211
+
27732
28212
  @media print {
27733
- body {
28213
+ body,
28214
+ .${CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME} {
27734
28215
  background: #ffffff;
28216
+ font-size: 12pt;
27735
28217
  }
27736
28218
 
27737
28219
  .document {
27738
28220
  max-width: none;
27739
28221
  padding: 0;
27740
28222
  }
28223
+
28224
+ .message {
28225
+ page-break-inside: avoid;
28226
+ break-inside: avoid;
28227
+ }
28228
+
28229
+ .message-bubble {
28230
+ box-shadow: none;
28231
+ }
28232
+
28233
+ .markdown-content pre,
28234
+ .message-supporting-list,
28235
+ .reply-context {
28236
+ page-break-inside: avoid;
28237
+ break-inside: avoid;
28238
+ }
27741
28239
  }
27742
28240
  </style>
27743
28241
  </head>
@@ -28203,7 +28701,11 @@ const PROMPTBOOK_REACT_EXPORT_BRANDING_COMMENT = createPromptbookReactExportBran
28203
28701
  */
28204
28702
  function createPromptbookReactExportBrandingComment() {
28205
28703
  const branding = getPromptbookExportBranding();
28206
- return ['/*', ...branding.commentLines.map((line) => ` * ${line}`), ' */'].join('\n');
28704
+ return spaceTrim$1((block) => `
28705
+ /*
28706
+ ${block(branding.commentLines.map((line) => ` * ${line}`).join('\n'))}
28707
+ */
28708
+ `);
28207
28709
  }
28208
28710
  /**
28209
28711
  * React <jsx/> export plugin (full metadata)
@@ -28746,6 +29248,54 @@ const MarkdownContent = memo(function MarkdownContent(props) {
28746
29248
  } }));
28747
29249
  });
28748
29250
 
29251
+ /**
29252
+ * Creates a stable key for one citation label lookup.
29253
+ *
29254
+ * @param citation - Citation metadata.
29255
+ * @returns Stable citation key.
29256
+ *
29257
+ * @private hook helper of `<Chat/>`
29258
+ */
29259
+ function createCitationLabelLookupKey(citation) {
29260
+ return [citation.id, citation.source, citation.url || '', citation.title || '', citation.excerpt || ''].join('\n');
29261
+ }
29262
+ /**
29263
+ * Resolves the best available label for one citation.
29264
+ *
29265
+ * @param citation - Citation metadata.
29266
+ * @param resolveCitationLabel - Optional async host resolver.
29267
+ * @returns Current label, starting with a synchronous fallback and updating when the resolver finishes.
29268
+ *
29269
+ * @private hook of `<Chat/>`
29270
+ */
29271
+ function useResolvedCitationLabel(citation, resolveCitationLabel) {
29272
+ const fallbackLabel = useMemo(() => getCitationLabel(citation), [citation]);
29273
+ const citationKey = useMemo(() => createCitationLabelLookupKey(citation), [citation]);
29274
+ const [resolvedLabel, setResolvedLabel] = useState(null);
29275
+ useEffect(() => {
29276
+ setResolvedLabel(null);
29277
+ if (!resolveCitationLabel) {
29278
+ return;
29279
+ }
29280
+ let isCurrent = true;
29281
+ Promise.resolve(resolveCitationLabel(citation))
29282
+ .then((label) => {
29283
+ if (!isCurrent) {
29284
+ return;
29285
+ }
29286
+ const normalizedLabel = label === null || label === void 0 ? void 0 : label.trim();
29287
+ if (normalizedLabel) {
29288
+ setResolvedLabel(normalizedLabel);
29289
+ }
29290
+ })
29291
+ .catch(() => undefined);
29292
+ return () => {
29293
+ isCurrent = false;
29294
+ };
29295
+ }, [citation, citationKey, resolveCitationLabel]);
29296
+ return resolvedLabel || fallbackLabel;
29297
+ }
29298
+
28749
29299
  /**
28750
29300
  * Renders a citation URL preview as an iframe when the target page allows embedding,
28751
29301
  * or falls back to a server-side screenshot with an "Open in new tab" link when it
@@ -28786,6 +29336,15 @@ function CitationIframePreview({ src, title }) {
28786
29336
  return jsx("iframe", { src: src, className: styles$7.citationIframe, title: title });
28787
29337
  }
28788
29338
 
29339
+ /**
29340
+ * Empty citation used to keep modal hooks unconditional while the modal is closed.
29341
+ *
29342
+ * @private component constant of `<ChatCitationModal/>`
29343
+ */
29344
+ const EMPTY_MODAL_CITATION = {
29345
+ id: '',
29346
+ source: '',
29347
+ };
28789
29348
  /**
28790
29349
  * Modal that previews a citation source or excerpt.
28791
29350
  *
@@ -28793,7 +29352,9 @@ function CitationIframePreview({ src, title }) {
28793
29352
  */
28794
29353
  function ChatCitationModal(props) {
28795
29354
  var _a, _b;
28796
- const { isOpen, citation, participants, soundSystem, onClose } = props;
29355
+ const { isOpen, citation, participants, resolveCitationLabel, soundSystem, onClose } = props;
29356
+ const resolvedCitation = citation || EMPTY_MODAL_CITATION;
29357
+ const label = useResolvedCitationLabel(resolvedCitation, resolveCitationLabel);
28797
29358
  if (!isOpen || !citation) {
28798
29359
  return null;
28799
29360
  }
@@ -28804,7 +29365,6 @@ function ChatCitationModal(props) {
28804
29365
  const previewSegment = previewBase.split('/').pop() || previewBase;
28805
29366
  const extension = (_a = previewSegment.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
28806
29367
  const isImage = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(extension || '');
28807
- const label = getCitationLabel(citation);
28808
29368
  const isTextCitation = isPlainTextCitation(citation);
28809
29369
  const hasTextPreview = !isValidUrl && (citation.excerpt || isTextCitation);
28810
29370
  const textPreviewContent = isTextCitation ? citation.source : (_b = citation.excerpt) !== null && _b !== void 0 ? _b : '';
@@ -30903,21 +31463,34 @@ function ChatInputArea(props) {
30903
31463
 
30904
31464
  // Note: [💞] Ignore a discrepancy between file name and entity name
30905
31465
  /**
30906
- * Target frames per second for the shared avatar animation loop.
31466
+ * Target frames per second for a single animated avatar.
30907
31467
  *
30908
- * Animated octopus visuals change slowly enough that 24 fps is indistinguishable
30909
- * from 60 fps in practice, while cutting rendering work by ~60% when multiple
30910
- * avatars are on screen simultaneously.
31468
+ * Animated octopus visuals change slowly enough that ~24 fps per avatar is indistinguishable
31469
+ * from 60 fps in practice. Combined with `MAX_AVATAR_LISTENERS_PER_FRAME` this bounds total
31470
+ * rendering work even when many avatars are visible.
30911
31471
  *
30912
31472
  * @private utility of the avatar rendering system
30913
31473
  */
30914
31474
  const AVATAR_TARGET_FPS = 24;
30915
31475
  /**
30916
- * Minimum elapsed time in milliseconds required between avatar render passes.
31476
+ * Minimum elapsed time in milliseconds required between two consecutive renders of the same avatar.
30917
31477
  *
30918
31478
  * @private utility of the avatar rendering system
30919
31479
  */
30920
31480
  const AVATAR_TARGET_FRAME_INTERVAL_MS = 1000 / AVATAR_TARGET_FPS;
31481
+ /**
31482
+ * Hard cap on how many avatar listeners can run inside the same animation frame.
31483
+ *
31484
+ * The default visual takes several milliseconds to render, so firing every visible avatar
31485
+ * on the same animation frame stalls the main thread for tens of milliseconds and drops the
31486
+ * overall page FPS to single digits. Bounding the number of listeners that fire per frame
31487
+ * spreads the work across animation frames and keeps the UI responsive even with many
31488
+ * visible avatars — each individual avatar still reaches `AVATAR_TARGET_FPS` because work
31489
+ * is interleaved instead of skipped.
31490
+ *
31491
+ * @private utility of the avatar rendering system
31492
+ */
31493
+ const MAX_AVATAR_LISTENERS_PER_FRAME = 2;
30921
31494
  /**
30922
31495
  * Next registration id used by the shared avatar animation scheduler.
30923
31496
  *
@@ -30936,17 +31509,13 @@ const avatarAnimationListeners = new Map();
30936
31509
  * @private utility of the avatar rendering system
30937
31510
  */
30938
31511
  let avatarAnimationFrameId = null;
30939
- /**
30940
- * Timestamp of the most recently rendered avatar frame.
30941
- *
30942
- * Used to throttle callbacks to `AVATAR_TARGET_FRAME_INTERVAL_MS`.
30943
- *
30944
- * @private utility of the avatar rendering system
30945
- */
30946
- let lastAvatarFrameTime = 0;
30947
31512
  /**
30948
31513
  * Registers one avatar animation callback in the shared animation loop.
30949
31514
  *
31515
+ * Each listener is staggered at registration time so it does not fire on the same animation
31516
+ * frame as the existing listeners, and the shared loop further caps how many listeners run
31517
+ * per frame, keeping the main thread responsive when many avatars are mounted.
31518
+ *
30950
31519
  * @param avatarAnimationListener Frame callback invoked on every animation frame.
30951
31520
  * @returns Cleanup function that unregisters the callback.
30952
31521
  *
@@ -30957,7 +31526,14 @@ function retainAvatarAnimationListener(avatarAnimationListener) {
30957
31526
  return () => undefined;
30958
31527
  }
30959
31528
  const listenerId = nextAvatarAnimationListenerId++;
30960
- avatarAnimationListeners.set(listenerId, avatarAnimationListener);
31529
+ const existingListenerCount = avatarAnimationListeners.size;
31530
+ const staggerOffsetMs = ((existingListenerCount % Math.max(1, MAX_AVATAR_LISTENERS_PER_FRAME)) * AVATAR_TARGET_FRAME_INTERVAL_MS) /
31531
+ Math.max(1, MAX_AVATAR_LISTENERS_PER_FRAME);
31532
+ avatarAnimationListeners.set(listenerId, {
31533
+ listener: avatarAnimationListener,
31534
+ // Phase the listener so multiple new listeners do not synchronize on the same frame.
31535
+ lastRunAtMs: performance.now() - AVATAR_TARGET_FRAME_INTERVAL_MS + staggerOffsetMs,
31536
+ });
30961
31537
  ensureAvatarAnimationLoop();
30962
31538
  return () => {
30963
31539
  avatarAnimationListeners.delete(listenerId);
@@ -30978,12 +31554,21 @@ function ensureAvatarAnimationLoop() {
30978
31554
  }
30979
31555
  const runFrame = (now) => {
30980
31556
  avatarAnimationFrameId = null;
30981
- if (now - lastAvatarFrameTime >= AVATAR_TARGET_FRAME_INTERVAL_MS) {
30982
- lastAvatarFrameTime = now;
30983
- for (const avatarAnimationListener of [...avatarAnimationListeners.values()]) {
30984
- avatarAnimationListener(now);
31557
+ const dueListenerEntries = [];
31558
+ for (const listenerEntry of avatarAnimationListeners.values()) {
31559
+ if (now - listenerEntry.lastRunAtMs >= AVATAR_TARGET_FRAME_INTERVAL_MS) {
31560
+ dueListenerEntries.push(listenerEntry);
30985
31561
  }
30986
31562
  }
31563
+ // Run the most stale listeners first so each avatar keeps its target FPS even when
31564
+ // total work exceeds the per-frame budget.
31565
+ dueListenerEntries.sort((firstListenerEntry, secondListenerEntry) => firstListenerEntry.lastRunAtMs - secondListenerEntry.lastRunAtMs);
31566
+ const listenersToRunCount = Math.min(dueListenerEntries.length, MAX_AVATAR_LISTENERS_PER_FRAME);
31567
+ for (let listenerIndex = 0; listenerIndex < listenersToRunCount; listenerIndex++) {
31568
+ const listenerEntry = dueListenerEntries[listenerIndex];
31569
+ listenerEntry.lastRunAtMs = now;
31570
+ listenerEntry.listener(now);
31571
+ }
30987
31572
  ensureAvatarAnimationLoop();
30988
31573
  };
30989
31574
  avatarAnimationFrameId = window.requestAnimationFrame(runFrame);
@@ -31687,8 +32272,8 @@ const AvatarProfileTooltip = forwardRef(function AvatarProfileTooltip({ agentSou
31687
32272
  }, children: [jsx("div", { className: styles$4.arrow }), jsx(AvatarProfileFromSource, { agentSource: agentSource })] }), document.body);
31688
32273
  });
31689
32274
 
31690
- var css_248z$3 = ".SourceChip-module_sourceChip__Cqbn0{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.24);background:color-mix(in srgb,var(--message-bg-color,#fff) 74%,hsla(0,0%,100%,.8) 26%);border:1px solid hsla(0,0%,49%,.3);border-color:color-mix(in srgb,var(--message-text-color,#0f172a) 16%,transparent);border-radius:999px;box-shadow:0 2px 8px rgba(15,23,42,.12);color:inherit;cursor:pointer;display:inline-flex;font-size:.8rem;font-weight:600;gap:6px;padding:5px 11px;transition:all .2s ease}.SourceChip-module_sourceChip__Cqbn0:hover{background:hsla(0,0%,100%,.34);background:color-mix(in srgb,var(--message-bg-color,#fff) 64%,hsla(0,0%,100%,.9) 36%);box-shadow:0 4px 12px rgba(15,23,42,.14);transform:translateY(-1px)}.SourceChip-module_icon__olwcc{flex-shrink:0;font-family:OpenMojiColor,OpenMojiBlack;font-size:1.1em}.SourceChip-module_label__yCkr-{align-items:center;display:flex;font-weight:600;gap:5px;white-space:nowrap}.SourceChip-module_citationId__W7xTo{font-size:.85em;font-weight:500;opacity:.78}.SourceChip-module_suffix__FSlC-{font-size:.85em;font-weight:500;opacity:.82;white-space:nowrap}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlNvdXJjZUNoaXAubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxQ0FFSSxrQkFBbUIsQ0FjbkIseUJBQTBCLENBVDFCLDhCQUFxQyxDQUNyQyxxRkFBa0csQ0FDbEcsa0NBQTBDLENBQzFDLGlGQUFxRixDQUNyRixtQkFBb0IsQ0FDcEIsdUNBQTRDLENBQzVDLGFBQWMsQ0FFZCxjQUFlLENBZGYsbUJBQW9CLENBR3BCLGVBQWlCLENBQ2pCLGVBQWdCLENBRmhCLE9BQVEsQ0FHUixnQkFBaUIsQ0FRakIsdUJBR0osQ0FFQSwyQ0FDSSw4QkFBcUMsQ0FDckMscUZBQWtHLENBRWxHLHdDQUE2QyxDQUQ3QywwQkFFSixDQUVBLCtCQUVJLGFBQWMsQ0FDZCx1Q0FBNkMsQ0FGN0MsZUFHSixDQUVBLGdDQUlJLGtCQUFtQixDQURuQixZQUFhLENBRmIsZUFBZ0IsQ0FJaEIsT0FBUSxDQUhSLGtCQUlKLENBRUEscUNBQ0ksZUFBaUIsQ0FFakIsZUFBZ0IsQ0FEaEIsV0FFSixDQUVBLGlDQUNJLGVBQWlCLENBRWpCLGVBQWdCLENBRGhCLFdBQWEsQ0FFYixrQkFDSiIsImZpbGUiOiJTb3VyY2VDaGlwLm1vZHVsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuc291cmNlQ2hpcCB7XG4gICAgZGlzcGxheTogaW5saW5lLWZsZXg7XG4gICAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgICBnYXA6IDZweDtcbiAgICBmb250LXNpemU6IDAuOHJlbTtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIHBhZGRpbmc6IDVweCAxMXB4O1xuICAgIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC4yNCk7XG4gICAgYmFja2dyb3VuZDogY29sb3ItbWl4KGluIHNyZ2IsIHZhcigtLW1lc3NhZ2UtYmctY29sb3IsICNmZmZmZmYpIDc0JSwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjgpIDI2JSk7XG4gICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgxMjUsIDEyNSwgMTI1LCAwLjMpO1xuICAgIGJvcmRlci1jb2xvcjogY29sb3ItbWl4KGluIHNyZ2IsIHZhcigtLW1lc3NhZ2UtdGV4dC1jb2xvciwgIzBmMTcyYSkgMTYlLCB0cmFuc3BhcmVudCk7XG4gICAgYm9yZGVyLXJhZGl1czogOTk5cHg7XG4gICAgYm94LXNoYWRvdzogMCAycHggOHB4IHJnYmEoMTUsIDIzLCA0MiwgMC4xMik7XG4gICAgY29sb3I6IGluaGVyaXQ7XG4gICAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgYmFja2Ryb3AtZmlsdGVyOiBibHVyKDhweCk7XG59XG5cbi5zb3VyY2VDaGlwOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMzQpO1xuICAgIGJhY2tncm91bmQ6IGNvbG9yLW1peChpbiBzcmdiLCB2YXIoLS1tZXNzYWdlLWJnLWNvbG9yLCAjZmZmZmZmKSA2NCUsIHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KSAzNiUpO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMXB4KTtcbiAgICBib3gtc2hhZG93OiAwIDRweCAxMnB4IHJnYmEoMTUsIDIzLCA0MiwgMC4xNCk7XG59XG5cbi5pY29uIHtcbiAgICBmb250LXNpemU6IDEuMWVtO1xuICAgIGZsZXgtc2hyaW5rOiAwO1xuICAgIGZvbnQtZmFtaWx5OiAnT3Blbk1vamlDb2xvcicsICdPcGVuTW9qaUJsYWNrJztcbn1cblxuLmxhYmVsIHtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGdhcDogNXB4O1xufVxuXG4uY2l0YXRpb25JZCB7XG4gICAgZm9udC1zaXplOiAwLjg1ZW07XG4gICAgb3BhY2l0eTogMC43ODtcbiAgICBmb250LXdlaWdodDogNTAwO1xufVxuXG4uc3VmZml4IHtcbiAgICBmb250LXNpemU6IDAuODVlbTtcbiAgICBvcGFjaXR5OiAwLjgyO1xuICAgIGZvbnQtd2VpZ2h0OiA1MDA7XG4gICAgd2hpdGUtc3BhY2U6IG5vd3JhcDtcbn1cbiJdfQ== */";
31691
- var styles$3 = {"sourceChip":"SourceChip-module_sourceChip__Cqbn0","icon":"SourceChip-module_icon__olwcc","label":"SourceChip-module_label__yCkr-","citationId":"SourceChip-module_citationId__W7xTo","suffix":"SourceChip-module_suffix__FSlC-"};
32275
+ var css_248z$3 = ".SourceChip-module_sourceChip__Cqbn0{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.24);background:color-mix(in srgb,var(--message-bg-color,#fff) 74%,hsla(0,0%,100%,.8) 26%);border:1px solid hsla(0,0%,49%,.3);border-color:color-mix(in srgb,var(--message-text-color,#0f172a) 16%,transparent);border-radius:999px;box-shadow:0 2px 8px rgba(15,23,42,.12);color:inherit;cursor:pointer;display:inline-flex;font-size:.8rem;font-weight:600;gap:6px;max-width:min(100%,32rem);padding:5px 11px;transition:all .2s ease}.SourceChip-module_sourceChip__Cqbn0:hover{background:hsla(0,0%,100%,.34);background:color-mix(in srgb,var(--message-bg-color,#fff) 64%,hsla(0,0%,100%,.9) 36%);box-shadow:0 4px 12px rgba(15,23,42,.14);transform:translateY(-1px)}.SourceChip-module_icon__olwcc{flex-shrink:0;font-family:OpenMojiColor,OpenMojiBlack;font-size:1.1em}.SourceChip-module_label__yCkr-{align-items:center;display:flex;font-weight:600;gap:5px;min-width:0;white-space:nowrap}.SourceChip-module_labelText__aFt2z{min-width:0;overflow:hidden;text-overflow:ellipsis}.SourceChip-module_citationId__W7xTo{font-size:.85em;font-weight:500;opacity:.78}.SourceChip-module_suffix__FSlC-{font-size:.85em;font-weight:500;opacity:.82;white-space:nowrap}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIlNvdXJjZUNoaXAubW9kdWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxQ0FFSSxrQkFBbUIsQ0FlbkIseUJBQTBCLENBVDFCLDhCQUFxQyxDQUNyQyxxRkFBa0csQ0FDbEcsa0NBQTBDLENBQzFDLGlGQUFxRixDQUNyRixtQkFBb0IsQ0FDcEIsdUNBQTRDLENBQzVDLGFBQWMsQ0FFZCxjQUFlLENBZmYsbUJBQW9CLENBSXBCLGVBQWlCLENBQ2pCLGVBQWdCLENBSGhCLE9BQVEsQ0FDUix5QkFBMkIsQ0FHM0IsZ0JBQWlCLENBUWpCLHVCQUdKLENBRUEsMkNBQ0ksOEJBQXFDLENBQ3JDLHFGQUFrRyxDQUVsRyx3Q0FBNkMsQ0FEN0MsMEJBRUosQ0FFQSwrQkFFSSxhQUFjLENBQ2QsdUNBQTZDLENBRjdDLGVBR0osQ0FFQSxnQ0FJSSxrQkFBbUIsQ0FEbkIsWUFBYSxDQUZiLGVBQWdCLENBSWhCLE9BQVEsQ0FDUixXQUFZLENBSlosa0JBS0osQ0FFQSxvQ0FDSSxXQUFZLENBQ1osZUFBZ0IsQ0FDaEIsc0JBQ0osQ0FFQSxxQ0FDSSxlQUFpQixDQUVqQixlQUFnQixDQURoQixXQUVKLENBRUEsaUNBQ0ksZUFBaUIsQ0FFakIsZUFBZ0IsQ0FEaEIsV0FBYSxDQUViLGtCQUNKIiwiZmlsZSI6IlNvdXJjZUNoaXAubW9kdWxlLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi5zb3VyY2VDaGlwIHtcbiAgICBkaXNwbGF5OiBpbmxpbmUtZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGdhcDogNnB4O1xuICAgIG1heC13aWR0aDogbWluKDEwMCUsIDMycmVtKTtcbiAgICBmb250LXNpemU6IDAuOHJlbTtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIHBhZGRpbmc6IDVweCAxMXB4O1xuICAgIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC4yNCk7XG4gICAgYmFja2dyb3VuZDogY29sb3ItbWl4KGluIHNyZ2IsIHZhcigtLW1lc3NhZ2UtYmctY29sb3IsICNmZmZmZmYpIDc0JSwgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjgpIDI2JSk7XG4gICAgYm9yZGVyOiAxcHggc29saWQgcmdiYSgxMjUsIDEyNSwgMTI1LCAwLjMpO1xuICAgIGJvcmRlci1jb2xvcjogY29sb3ItbWl4KGluIHNyZ2IsIHZhcigtLW1lc3NhZ2UtdGV4dC1jb2xvciwgIzBmMTcyYSkgMTYlLCB0cmFuc3BhcmVudCk7XG4gICAgYm9yZGVyLXJhZGl1czogOTk5cHg7XG4gICAgYm94LXNoYWRvdzogMCAycHggOHB4IHJnYmEoMTUsIDIzLCA0MiwgMC4xMik7XG4gICAgY29sb3I6IGluaGVyaXQ7XG4gICAgdHJhbnNpdGlvbjogYWxsIDAuMnMgZWFzZTtcbiAgICBjdXJzb3I6IHBvaW50ZXI7XG4gICAgYmFja2Ryb3AtZmlsdGVyOiBibHVyKDhweCk7XG59XG5cbi5zb3VyY2VDaGlwOmhvdmVyIHtcbiAgICBiYWNrZ3JvdW5kOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMzQpO1xuICAgIGJhY2tncm91bmQ6IGNvbG9yLW1peChpbiBzcmdiLCB2YXIoLS1tZXNzYWdlLWJnLWNvbG9yLCAjZmZmZmZmKSA2NCUsIHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KSAzNiUpO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgtMXB4KTtcbiAgICBib3gtc2hhZG93OiAwIDRweCAxMnB4IHJnYmEoMTUsIDIzLCA0MiwgMC4xNCk7XG59XG5cbi5pY29uIHtcbiAgICBmb250LXNpemU6IDEuMWVtO1xuICAgIGZsZXgtc2hyaW5rOiAwO1xuICAgIGZvbnQtZmFtaWx5OiAnT3Blbk1vamlDb2xvcicsICdPcGVuTW9qaUJsYWNrJztcbn1cblxuLmxhYmVsIHtcbiAgICBmb250LXdlaWdodDogNjAwO1xuICAgIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gICAgZGlzcGxheTogZmxleDtcbiAgICBhbGlnbi1pdGVtczogY2VudGVyO1xuICAgIGdhcDogNXB4O1xuICAgIG1pbi13aWR0aDogMDtcbn1cblxuLmxhYmVsVGV4dCB7XG4gICAgbWluLXdpZHRoOiAwO1xuICAgIG92ZXJmbG93OiBoaWRkZW47XG4gICAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7XG59XG5cbi5jaXRhdGlvbklkIHtcbiAgICBmb250LXNpemU6IDAuODVlbTtcbiAgICBvcGFjaXR5OiAwLjc4O1xuICAgIGZvbnQtd2VpZ2h0OiA1MDA7XG59XG5cbi5zdWZmaXgge1xuICAgIGZvbnQtc2l6ZTogMC44NWVtO1xuICAgIG9wYWNpdHk6IDAuODI7XG4gICAgZm9udC13ZWlnaHQ6IDUwMDtcbiAgICB3aGl0ZS1zcGFjZTogbm93cmFwO1xufVxuIl19 */";
32276
+ var styles$3 = {"sourceChip":"SourceChip-module_sourceChip__Cqbn0","icon":"SourceChip-module_icon__olwcc","label":"SourceChip-module_label__yCkr-","labelText":"SourceChip-module_labelText__aFt2z","citationId":"SourceChip-module_citationId__W7xTo","suffix":"SourceChip-module_suffix__FSlC-"};
31692
32277
  styleInject(css_248z$3);
31693
32278
 
31694
32279
  /**
@@ -31708,7 +32293,7 @@ styleInject(css_248z$3);
31708
32293
  *
31709
32294
  * @private utility of `ChatMessageItem` component
31710
32295
  */
31711
- function SourceChip({ citation, onClick, className, suffix, isCitationIdVisible = true }) {
32296
+ function SourceChip({ citation, onClick, className, suffix, resolveCitationLabel, isCitationIdVisible = true, }) {
31712
32297
  var _a;
31713
32298
  const handleClick = (event) => {
31714
32299
  event.stopPropagation();
@@ -31718,7 +32303,8 @@ function SourceChip({ citation, onClick, className, suffix, isCitationIdVisible
31718
32303
  };
31719
32304
  // Keep source chips concise and human-readable for CDN-backed knowledge files.
31720
32305
  const normalizedSource = citation.source.trim();
31721
- const displayName = getCitationLabel(citation);
32306
+ const displayName = useResolvedCitationLabel(citation, resolveCitationLabel);
32307
+ const title = displayName === citation.source ? citation.source : `${displayName}\n${citation.source}`;
31722
32308
  // Get file extension for icon
31723
32309
  const fileExtension = ((_a = (normalizedSource || citation.source).split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || 'file';
31724
32310
  const icon = isPlainTextCitation(citation)
@@ -31726,7 +32312,7 @@ function SourceChip({ citation, onClick, className, suffix, isCitationIdVisible
31726
32312
  : isCitationUrl(normalizedSource)
31727
32313
  ? '🌐'
31728
32314
  : getFileIcon(fileExtension);
31729
- return (jsxs("button", { className: `${styles$3.sourceChip} ${className || ''}`, onClick: handleClick, title: citation.source, children: [jsx("span", { className: styles$3.icon, children: icon }), jsxs("span", { className: styles$3.label, children: [displayName, isCitationIdVisible && jsxs("span", { className: styles$3.citationId, children: [" [", citation.id, "]"] })] }), suffix && jsx("span", { className: styles$3.suffix, children: suffix })] }));
32315
+ return (jsxs("button", { className: `${styles$3.sourceChip} ${className || ''}`, onClick: handleClick, title: title, children: [jsx("span", { className: styles$3.icon, children: icon }), jsxs("span", { className: styles$3.label, children: [jsx("span", { className: styles$3.labelText, children: displayName }), isCitationIdVisible && jsxs("span", { className: styles$3.citationId, children: [" [", citation.id, "]"] })] }), suffix && jsx("span", { className: styles$3.suffix, children: suffix })] }));
31730
32316
  }
31731
32317
  /**
31732
32318
  * Gets an appropriate emoji icon for a file type
@@ -31811,63 +32397,6 @@ function formatGenerationDurationLabel(durationMs) {
31811
32397
  return `${minutes}m ${seconds}s`;
31812
32398
  }
31813
32399
 
31814
- /**
31815
- * Extracts one quick button definition from a markdown link query string.
31816
- *
31817
- * @param text The visible button label.
31818
- * @param query Raw query-string payload captured from markdown.
31819
- * @returns Parsed quick button definition or `null` when the link is not a supported quick button.
31820
- *
31821
- * @private internal helper of `parseMessageButtons`
31822
- */
31823
- function parseQuickButtonDefinition(text, query) {
31824
- const searchParams = new URLSearchParams(query);
31825
- const message = searchParams.get('message');
31826
- if (message !== null) {
31827
- return {
31828
- type: 'message',
31829
- text,
31830
- message,
31831
- };
31832
- }
31833
- const code = searchParams.get('action');
31834
- if (code !== null) {
31835
- return {
31836
- type: 'action',
31837
- text,
31838
- code,
31839
- };
31840
- }
31841
- return null;
31842
- }
31843
- /**
31844
- * Parses markdown quick buttons in the format `[Button Text](?message=...)` or `[Button Text](?action=...)`.
31845
- * Returns both the content without supported quick buttons and the extracted button definitions.
31846
- *
31847
- * @param content The markdown content that may contain buttons
31848
- * @returns Object with contentWithoutButtons and buttons array
31849
- *
31850
- * @public exported from `@promptbook/components`
31851
- */
31852
- function parseMessageButtons(content) {
31853
- const buttonRegex = /\[([^\]]+)\]\(\?([^)]+)\)/g;
31854
- const buttons = [];
31855
- const contentWithoutButtons = content
31856
- .replace(buttonRegex, (match, text, query) => {
31857
- const button = parseQuickButtonDefinition(text, query);
31858
- if (!button) {
31859
- return match;
31860
- }
31861
- buttons.push(button);
31862
- return '';
31863
- })
31864
- .trim();
31865
- return {
31866
- contentWithoutButtons,
31867
- buttons,
31868
- };
31869
- }
31870
-
31871
32400
  /**
31872
32401
  * Searches for the last unclosed code fence using the provided delimiter.
31873
32402
  */
@@ -35339,7 +35868,7 @@ const ChatMessageItem = memo(
35339
35868
  // <- TODO: [🧠] Should we wrap more components in `React.memo`
35340
35869
  // Or make normal function from this?
35341
35870
  (props) => {
35342
- const { message, participant, participants, isLastMessage, onMessage, onActionButton, onQuickMessageButton, setExpandedMessageId, isExpanded, currentRating, handleRating, mode, isCopyButtonEnabled, isFeedbackEnabled, feedbackMode = 'stars', feedbackTranslations, timingTranslations, chatLocale, onCopy, onCreateAgent, toolTitles, teammates, onReplyToMessage, canReplyToMessage, teamAgentProfiles, visualMode = 'ARTICLE_MODE', onToolCallClick, onCitationClick, soundSystem, isSpeechPlaybackEnabled, elevenLabsVoiceId, chatUiTranslations, } = props;
35871
+ const { message, participant, participants, isLastMessage, onMessage, onActionButton, onQuickMessageButton, setExpandedMessageId, isExpanded, currentRating, handleRating, mode, isCopyButtonEnabled, isFeedbackEnabled, feedbackMode = 'stars', feedbackTranslations, timingTranslations, resolveCitationLabel, chatLocale, onCopy, onCreateAgent, toolTitles, teammates, onReplyToMessage, canReplyToMessage, teamAgentProfiles, visualMode = 'ARTICLE_MODE', onToolCallClick, onCitationClick, soundSystem, isSpeechPlaybackEnabled, elevenLabsVoiceId, chatUiTranslations, } = props;
35343
35872
  const { isComplete = true,
35344
35873
  // <- TODO: Destruct all `messages` properties like `isComplete`
35345
35874
  } = message;
@@ -35387,6 +35916,10 @@ const ChatMessageItem = memo(
35387
35916
  content: sanitizedContentWithoutButtons,
35388
35917
  citations: message.citations,
35389
35918
  }), [message.citations, sanitizedContentWithoutButtons]);
35919
+ const enrichCitationForRendering = useCallback((citation) => {
35920
+ const previewUrl = resolveCitationPreviewUrl(citation, participants !== null && participants !== void 0 ? participants : []);
35921
+ return previewUrl && !citation.url ? { ...citation, url: previewUrl } : citation;
35922
+ }, [participants]);
35390
35923
  const contentSegments = useMemo(() => splitMessageContentIntoSegments(citationFootnoteRenderModel.content), [citationFootnoteRenderModel.content]);
35391
35924
  const hasMapSegment = useMemo(() => contentSegments.some((segment) => segment.type === 'map'), [contentSegments]);
35392
35925
  const [localHoveredRating, setLocalHoveredRating] = useState(0);
@@ -35658,7 +36191,7 @@ const ChatMessageItem = memo(
35658
36191
  '--message-bg-color': isAgentArticleMode ? articleModeBackgroundColor : color.toHex(),
35659
36192
  '--message-text-color': isAgentArticleMode ? articleModeTextColor : colorOfText.toHex(),
35660
36193
  '--chat-message-swipe-offset': swipeTranslation,
35661
- }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsx("div", { className: classNames(styles$7.replySwipeIndicator, isMe && styles$7.replySwipeIndicatorRight, isReplySwipeArmed && styles$7.replySwipeIndicatorActive), "aria-hidden": "true", children: jsx(Reply, { className: styles$7.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsx("div", { className: styles$7.voiceCallIndicator, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }) })), replyingToMessage && replyPreviewText && replySenderLabel && (jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$7.replyBubblePreview })), jsx("div", { ref: contentWithoutButtonsRef, children: jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsx("div", { className: styles$7.attachments, children: message.attachments.map((attachment, index) => (jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$7.attachment, title: attachment.name, children: [jsx("span", { className: styles$7.attachmentIcon, children: "\uD83D\uDCCE" }), jsx("span", { className: styles$7.attachmentName, children: attachment.name })] }, index))) })), jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsx("div", { className: styles$7.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxs("div", { className: styles$7.citationFootnoteItem, children: [jsx("span", { className: styles$7.citationFootnoteNumber, children: footnote.number }), jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsx("div", { className: styles$7.sourceCitations, children: transitiveCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsx("div", { className: styles$7.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsx("button", { type: "button", className: classNames(styles$7.messageButton, button.type === 'action' && styles$7.actionMessageButton), onClick: (event) => {
36194
+ }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsx("div", { className: classNames(styles$7.replySwipeIndicator, isMe && styles$7.replySwipeIndicatorRight, isReplySwipeArmed && styles$7.replySwipeIndicatorActive), "aria-hidden": "true", children: jsx(Reply, { className: styles$7.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsx("div", { className: styles$7.voiceCallIndicator, children: jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }) })), replyingToMessage && replyPreviewText && replySenderLabel && (jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$7.replyBubblePreview })), jsx("div", { ref: contentWithoutButtonsRef, children: jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsx("div", { className: styles$7.attachments, children: message.attachments.map((attachment, index) => (jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$7.attachment, title: attachment.name, children: [jsx("span", { className: styles$7.attachmentIcon, children: "\uD83D\uDCCE" }), jsx("span", { className: styles$7.attachmentName, children: attachment.name })] }, index))) })), jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsx("div", { className: styles$7.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxs("div", { className: styles$7.citationFootnoteItem, children: [jsx("span", { className: styles$7.citationFootnoteNumber, children: footnote.number }), jsx(SourceChip, { citation: enrichCitationForRendering(footnote.citation), onClick: onCitationClick, resolveCitationLabel: resolveCitationLabel, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsx("div", { className: styles$7.sourceCitations, children: transitiveCitations.map((citation, index) => (jsx(SourceChip, { citation: enrichCitationForRendering(citation), suffix: `by ${citation.origin.label}`, onClick: onCitationClick, resolveCitationLabel: resolveCitationLabel }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsx("div", { className: styles$7.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsx("button", { type: "button", className: classNames(styles$7.messageButton, button.type === 'action' && styles$7.actionMessageButton), onClick: (event) => {
35662
36195
  event.stopPropagation();
35663
36196
  if (button.type === 'message') {
35664
36197
  const quickMessageHandler = onQuickMessageButton || onMessage;
@@ -35764,6 +36297,9 @@ const ChatMessageItem = memo(
35764
36297
  if (prev.feedbackTranslations !== next.feedbackTranslations) {
35765
36298
  return false;
35766
36299
  }
36300
+ if (prev.resolveCitationLabel !== next.resolveCitationLabel) {
36301
+ return false;
36302
+ }
35767
36303
  if (prev.handleRating !== next.handleRating) {
35768
36304
  return false;
35769
36305
  }
@@ -35792,7 +36328,7 @@ ChatMessageItem.displayName = 'ChatMessageItem';
35792
36328
  * @private component of `<Chat/>`
35793
36329
  */
35794
36330
  function ChatMessageList(props) {
35795
- const { messages, participants, expandedMessageId, messageRatings, setExpandedMessageId, handleRating, mode, isCopyButtonEnabled, isFeedbackEnabled, feedbackMode, feedbackTranslations, timingTranslations, chatLocale, onCopy, onMessage, onActionButton, onQuickMessageButton, onReplyToMessage, canReplyToMessage, onCreateAgent, toolTitles, teammates, onToolCallClick, onCitationClick, visualMode, soundSystem, isSpeechPlaybackEnabled, elevenLabsVoiceId, teamAgentProfiles, setChatMessagesElement, onScroll, chatMessagesClassName, hasActions, chatUiTranslations, } = props;
36331
+ const { messages, participants, expandedMessageId, messageRatings, setExpandedMessageId, handleRating, mode, isCopyButtonEnabled, isFeedbackEnabled, feedbackMode, feedbackTranslations, timingTranslations, resolveCitationLabel, chatLocale, onCopy, onMessage, onActionButton, onQuickMessageButton, onReplyToMessage, canReplyToMessage, onCreateAgent, toolTitles, teammates, onToolCallClick, onCitationClick, visualMode, soundSystem, isSpeechPlaybackEnabled, elevenLabsVoiceId, teamAgentProfiles, setChatMessagesElement, onScroll, chatMessagesClassName, hasActions, chatUiTranslations, } = props;
35796
36332
  const firstMessage = messages[0];
35797
36333
  const firstMsgContent = (firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) || '';
35798
36334
  const firstMsgLines = firstMsgContent.split(/\r?\n/).length;
@@ -35804,7 +36340,7 @@ function ChatMessageList(props) {
35804
36340
  const isLastMessage = index === messages.length - 1;
35805
36341
  const isExpanded = expandedMessageId === message.id;
35806
36342
  const currentRating = messageRatings.get(message.id || message.content /* <-[??] */) || 0;
35807
- return (jsx(ChatMessageItem, { message: message, participant: participant, participants: participants, isLastMessage: isLastMessage, onMessage: onMessage, onActionButton: onActionButton, onQuickMessageButton: onQuickMessageButton, onReplyToMessage: onReplyToMessage, canReplyToMessage: canReplyToMessage, setExpandedMessageId: setExpandedMessageId, isExpanded: isExpanded, currentRating: currentRating, handleRating: handleRating, mode: mode, isCopyButtonEnabled: isCopyButtonEnabled, isFeedbackEnabled: isFeedbackEnabled, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, timingTranslations: timingTranslations, chatLocale: chatLocale, onCopy: onCopy, onCreateAgent: onCreateAgent, toolTitles: toolTitles, teammates: teammates, teamAgentProfiles: teamAgentProfiles, visualMode: visualMode, onToolCallClick: onToolCallClick, onCitationClick: onCitationClick, soundSystem: soundSystem, isSpeechPlaybackEnabled: isSpeechPlaybackEnabled, elevenLabsVoiceId: elevenLabsVoiceId, chatUiTranslations: chatUiTranslations }, resolveRenderedMessageKey(message)));
36343
+ return (jsx(ChatMessageItem, { message: message, participant: participant, participants: participants, isLastMessage: isLastMessage, onMessage: onMessage, onActionButton: onActionButton, onQuickMessageButton: onQuickMessageButton, onReplyToMessage: onReplyToMessage, canReplyToMessage: canReplyToMessage, setExpandedMessageId: setExpandedMessageId, isExpanded: isExpanded, currentRating: currentRating, handleRating: handleRating, mode: mode, isCopyButtonEnabled: isCopyButtonEnabled, isFeedbackEnabled: isFeedbackEnabled, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, timingTranslations: timingTranslations, resolveCitationLabel: resolveCitationLabel, chatLocale: chatLocale, onCopy: onCopy, onCreateAgent: onCreateAgent, toolTitles: toolTitles, teammates: teammates, teamAgentProfiles: teamAgentProfiles, visualMode: visualMode, onToolCallClick: onToolCallClick, onCitationClick: onCitationClick, soundSystem: soundSystem, isSpeechPlaybackEnabled: isSpeechPlaybackEnabled, elevenLabsVoiceId: elevenLabsVoiceId, chatUiTranslations: chatUiTranslations }, resolveRenderedMessageKey(message)));
35808
36344
  }), jsx("div", { style: { height: 100 } })] }));
35809
36345
  }
35810
36346
  /**
@@ -39250,11 +39786,11 @@ function TeamToolCallActionsGroup(props) {
39250
39786
  * @private component of ChatToolCallModal
39251
39787
  */
39252
39788
  function TeamToolCallSourcesGroup(props) {
39253
- const { teamCitations } = props;
39789
+ const { teamCitations, resolveCitationLabel } = props;
39254
39790
  if (teamCitations.length === 0) {
39255
39791
  return null;
39256
39792
  }
39257
- return (jsxs("div", { className: styles$7.teamToolCallGroup, children: [jsx("div", { className: styles$7.teamToolCallHeading, children: "Sources" }), jsx("div", { className: styles$7.teamToolCallChips, children: teamCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}` }, `team-source-${citation.source}-${index}`))) })] }));
39793
+ return (jsxs("div", { className: styles$7.teamToolCallGroup, children: [jsx("div", { className: styles$7.teamToolCallHeading, children: "Sources" }), jsx("div", { className: styles$7.teamToolCallChips, children: teamCitations.map((citation, index) => (jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, resolveCitationLabel: resolveCitationLabel }, `team-source-${citation.source}-${index}`))) })] }));
39258
39794
  }
39259
39795
  /**
39260
39796
  * Renders the nested action details for the selected teammate tool call.
@@ -39279,12 +39815,12 @@ function TeamToolCallDetailsPanel(props) {
39279
39815
  * @private component of ChatToolCallModal
39280
39816
  */
39281
39817
  function TeamToolCallSummarySection(props) {
39282
- const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, } = props;
39818
+ const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, resolveCitationLabel, } = props;
39283
39819
  const hasSummaryContent = teamToolCallSummary.toolCalls.length > 0 || teamToolCallSummary.citations.length > 0;
39284
39820
  if (!hasSummaryContent) {
39285
39821
  return null;
39286
39822
  }
39287
- return (jsxs("div", { className: styles$7.teamToolCallSection, children: [jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations }), jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
39823
+ return (jsxs("div", { className: styles$7.teamToolCallSection, children: [jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations, resolveCitationLabel: resolveCitationLabel }), jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
39288
39824
  }
39289
39825
  /**
39290
39826
  * Renders TEAM conversation details, nested actions, and citations.
@@ -39292,7 +39828,7 @@ function TeamToolCallSummarySection(props) {
39292
39828
  * @private function of ChatToolCallModal
39293
39829
  */
39294
39830
  function TeamToolCallModalContent(options) {
39295
- const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, agentParticipant, buttonColor, } = options;
39831
+ const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, resolveCitationLabel, agentParticipant, buttonColor, } = options;
39296
39832
  const baseTime = toolCallDate ? toolCallDate.getTime() : Date.now();
39297
39833
  const messages = createTeamConversationMessages(teamResult, baseTime);
39298
39834
  const { agentHeader, teammateHeader, participants } = createTeamConversationViewModel({
@@ -39300,7 +39836,7 @@ function TeamToolCallModalContent(options) {
39300
39836
  teamProfiles,
39301
39837
  agentParticipant,
39302
39838
  });
39303
- return (jsxs(Fragment, { children: [jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxs("div", { className: styles$7.searchModalContent, children: [jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
39839
+ return (jsxs(Fragment, { children: [jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxs("div", { className: styles$7.searchModalContent, children: [jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
39304
39840
  }
39305
39841
 
39306
39842
  /**
@@ -39308,7 +39844,7 @@ function TeamToolCallModalContent(options) {
39308
39844
  *
39309
39845
  * @private component of `ChatToolCallModal`
39310
39846
  */
39311
- function ChatToolCallModalContent({ agentParticipant, availableTools, buttonColor, chatUiTranslations, focusedToolCall, isAdvancedView, locale, mode, onClearSelectedTeamToolCall, onRequestAdvancedView, onSelectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toolCall, toolCallDate, toolTitles, }) {
39847
+ function ChatToolCallModalContent({ agentParticipant, availableTools, buttonColor, chatUiTranslations, focusedToolCall, isAdvancedView, locale, mode, resolveCitationLabel, onClearSelectedTeamToolCall, onRequestAdvancedView, onSelectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toolCall, toolCallDate, toolTitles, }) {
39312
39848
  if (isAdvancedView) {
39313
39849
  return renderAdvancedToolCallDetails({
39314
39850
  toolCall: focusedToolCall,
@@ -39318,7 +39854,7 @@ function ChatToolCallModalContent({ agentParticipant, availableTools, buttonColo
39318
39854
  });
39319
39855
  }
39320
39856
  if (teamResult === null || teamResult === void 0 ? void 0 : teamResult.teammate) {
39321
- return (jsx(TeamToolCallModalContent, { teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor }));
39857
+ return (jsx(TeamToolCallModalContent, { teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor }));
39322
39858
  }
39323
39859
  return renderToolCallDetails({
39324
39860
  toolCall,
@@ -39581,7 +40117,7 @@ function handleModalBackdropClick(event, onClose) {
39581
40117
  * @private component of `<Chat/>`
39582
40118
  */
39583
40119
  function ChatToolCallModal(props) {
39584
- const { isOpen, toolCall, mode = 'LIGHT', toolCallIdentity, onClose, toolTitles, agentParticipant, buttonColor, teamAgentProfiles, chatUiTranslations, locale, availableTools, } = props;
40120
+ const { isOpen, toolCall, mode = 'LIGHT', toolCallIdentity, onClose, toolTitles, resolveCitationLabel, agentParticipant, buttonColor, teamAgentProfiles, chatUiTranslations, locale, availableTools, } = props;
39585
40121
  const { clearSelectedTeamToolCall, exportAdvancedToolCallReport, focusedToolCall, isAdvancedView, modalDialogRef, openAdvancedView, selectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toggleViewMode, toolCallDate, } = useChatToolCallModalState({
39586
40122
  isOpen,
39587
40123
  toolCall,
@@ -39603,7 +40139,7 @@ function ChatToolCallModal(props) {
39603
40139
  : (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallModalAdvancedLabel) || 'Advanced';
39604
40140
  return (jsx("div", { className: styles$7.ratingModal, "data-chat-modal": "tool-call", "data-chat-theme": mode.toLowerCase(), onClick: (event) => {
39605
40141
  handleModalBackdropClick(event, onClose);
39606
- }, children: jsxs("div", { ref: modalDialogRef, className: classNames(styles$7.ratingModalContent, styles$7.toolCallModal), role: "dialog", "aria-modal": "true", "aria-label": modalTitle, tabIndex: -1, children: [jsx("button", { type: "button", className: styles$7.modalCloseButton, onClick: onClose, "aria-label": closeButtonLabel, children: jsx(CloseIcon, {}) }), jsx(ChatToolCallModalContent, { toolCall: toolCall, focusedToolCall: resolvedFocusedToolCall, isAdvancedView: isAdvancedView, teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: selectTeamToolCall, onClearSelectedTeamToolCall: clearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor, locale: locale, chatUiTranslations: chatUiTranslations, availableTools: availableTools, onRequestAdvancedView: openAdvancedView, mode: mode }), jsxs("div", { className: styles$7.toolCallModeFooter, children: [isAdvancedView && (jsxs(Fragment, { children: [jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
40142
+ }, children: jsxs("div", { ref: modalDialogRef, className: classNames(styles$7.ratingModalContent, styles$7.toolCallModal), role: "dialog", "aria-modal": "true", "aria-label": modalTitle, tabIndex: -1, children: [jsx("button", { type: "button", className: styles$7.modalCloseButton, onClick: onClose, "aria-label": closeButtonLabel, children: jsx(CloseIcon, {}) }), jsx(ChatToolCallModalContent, { toolCall: toolCall, focusedToolCall: resolvedFocusedToolCall, isAdvancedView: isAdvancedView, teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: selectTeamToolCall, onClearSelectedTeamToolCall: clearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor, locale: locale, chatUiTranslations: chatUiTranslations, availableTools: availableTools, onRequestAdvancedView: openAdvancedView, mode: mode }), jsxs("div", { className: styles$7.toolCallModeFooter, children: [isAdvancedView && (jsxs(Fragment, { children: [jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
39607
40143
  void exportAdvancedToolCallReport('clipboard');
39608
40144
  }, children: copyButtonLabel }), jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
39609
40145
  void exportAdvancedToolCallReport('file');
@@ -39746,6 +40282,146 @@ function promptbookifyAiText(text) {
39746
40282
  }
39747
40283
  // TODO: [🧠][✌️] Make some Promptbook-native token system
39748
40284
 
40285
+ /**
40286
+ * Pattern for detecting JSON-style Unicode escape sequences in model text.
40287
+ *
40288
+ * @private utility of chat message postprocessing
40289
+ */
40290
+ const JSON_UNICODE_ESCAPE_DETECTION_PATTERN = /\\u[0-9a-fA-F]{4}/;
40291
+ /**
40292
+ * Pattern for decoding JSON-style Unicode escape sequences in model text.
40293
+ *
40294
+ * @private utility of chat message postprocessing
40295
+ */
40296
+ const JSON_UNICODE_ESCAPE_PATTERN = /\\u([0-9a-fA-F]{4})/g;
40297
+ /**
40298
+ * Pattern for detecting fenced markdown code-block boundaries.
40299
+ *
40300
+ * @private utility of chat message postprocessing
40301
+ */
40302
+ const MARKDOWN_CODE_FENCE_PATTERN = /^[ \t]{0,3}(`{3,}|~{3,})/;
40303
+ /**
40304
+ * Minimum code point decoded by the chat Unicode escape normalizer.
40305
+ *
40306
+ * ASCII escapes can be intentional in JSON and security-sensitive examples, so this
40307
+ * helper only decodes non-ASCII characters that were visibly broken in chat text.
40308
+ *
40309
+ * @private utility of chat message postprocessing
40310
+ */
40311
+ const FIRST_NON_ASCII_CODE_POINT = 0x80;
40312
+ /**
40313
+ * Decodes JSON-style Unicode escape sequences in markdown prose while preserving code.
40314
+ *
40315
+ * Model/tool output can occasionally arrive as already JSON-escaped text, which makes
40316
+ * natural-language replies render `\u00fd` instead of the decoded character. This
40317
+ * keeps code examples intact by skipping fenced code blocks and inline code spans.
40318
+ *
40319
+ * @param markdown - Markdown chat content to normalize for display.
40320
+ * @returns Markdown chat content with non-ASCII JSON Unicode escapes decoded.
40321
+ *
40322
+ * @private internal utility of `<Chat/>`
40323
+ */
40324
+ function decodeJsonUnicodeEscapesInMarkdownText(markdown) {
40325
+ var _a, _b;
40326
+ if (!JSON_UNICODE_ESCAPE_DETECTION_PATTERN.test(markdown)) {
40327
+ return markdown;
40328
+ }
40329
+ const parts = markdown.split(/(\r\n|\r|\n)/);
40330
+ let fenceState = null;
40331
+ let normalizedMarkdown = '';
40332
+ for (let index = 0; index < parts.length; index += 2) {
40333
+ const line = (_a = parts[index]) !== null && _a !== void 0 ? _a : '';
40334
+ const newline = (_b = parts[index + 1]) !== null && _b !== void 0 ? _b : '';
40335
+ const fenceBoundary = resolveMarkdownFenceBoundary(line);
40336
+ if (fenceState) {
40337
+ normalizedMarkdown += line + newline;
40338
+ if (fenceBoundary &&
40339
+ fenceBoundary.marker === fenceState.marker &&
40340
+ fenceBoundary.length >= fenceState.length) {
40341
+ fenceState = null;
40342
+ }
40343
+ continue;
40344
+ }
40345
+ if (fenceBoundary) {
40346
+ fenceState = fenceBoundary;
40347
+ normalizedMarkdown += line + newline;
40348
+ continue;
40349
+ }
40350
+ normalizedMarkdown += decodeJsonUnicodeEscapesOutsideInlineCode(line) + newline;
40351
+ }
40352
+ return normalizedMarkdown;
40353
+ }
40354
+ /**
40355
+ * Resolves one markdown code-fence boundary from a line.
40356
+ *
40357
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40358
+ */
40359
+ function resolveMarkdownFenceBoundary(line) {
40360
+ const match = line.match(MARKDOWN_CODE_FENCE_PATTERN);
40361
+ const fenceMarker = match === null || match === void 0 ? void 0 : match[1];
40362
+ if (!fenceMarker) {
40363
+ return null;
40364
+ }
40365
+ return {
40366
+ marker: fenceMarker[0],
40367
+ length: fenceMarker.length,
40368
+ };
40369
+ }
40370
+ /**
40371
+ * Decodes Unicode escapes in one markdown line while skipping inline code spans.
40372
+ *
40373
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40374
+ */
40375
+ function decodeJsonUnicodeEscapesOutsideInlineCode(line) {
40376
+ let normalizedLine = '';
40377
+ let cursor = 0;
40378
+ while (cursor < line.length) {
40379
+ const openingDelimiterStart = line.indexOf('`', cursor);
40380
+ if (openingDelimiterStart === -1) {
40381
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor));
40382
+ break;
40383
+ }
40384
+ const openingDelimiterEnd = findBacktickRunEnd(line, openingDelimiterStart);
40385
+ const openingDelimiter = line.slice(openingDelimiterStart, openingDelimiterEnd);
40386
+ const closingDelimiterStart = line.indexOf(openingDelimiter, openingDelimiterEnd);
40387
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor, openingDelimiterStart));
40388
+ if (closingDelimiterStart === -1) {
40389
+ normalizedLine += line.slice(openingDelimiterStart);
40390
+ break;
40391
+ }
40392
+ const closingDelimiterEnd = closingDelimiterStart + openingDelimiter.length;
40393
+ normalizedLine += line.slice(openingDelimiterStart, closingDelimiterEnd);
40394
+ cursor = closingDelimiterEnd;
40395
+ }
40396
+ return normalizedLine;
40397
+ }
40398
+ /**
40399
+ * Finds the first character after one contiguous run of backticks.
40400
+ *
40401
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40402
+ */
40403
+ function findBacktickRunEnd(line, startIndex) {
40404
+ let cursor = startIndex;
40405
+ while (cursor < line.length && line[cursor] === '`') {
40406
+ cursor++;
40407
+ }
40408
+ return cursor;
40409
+ }
40410
+ /**
40411
+ * Decodes non-ASCII JSON Unicode escapes in plain text.
40412
+ *
40413
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40414
+ */
40415
+ function decodeJsonUnicodeEscapesInText(text) {
40416
+ return text.replace(JSON_UNICODE_ESCAPE_PATTERN, (match, hexadecimalCodePoint) => {
40417
+ const codePoint = Number.parseInt(hexadecimalCodePoint, 16);
40418
+ if (codePoint < FIRST_NON_ASCII_CODE_POINT) {
40419
+ return match;
40420
+ }
40421
+ return String.fromCharCode(codePoint);
40422
+ });
40423
+ }
40424
+
39749
40425
  /**
39750
40426
  * Applies the same text normalization that Chat historically performed inline.
39751
40427
  *
@@ -39759,15 +40435,24 @@ function postprocessChatMessage(message) {
39759
40435
  const normalizedReplyingTo = messageWithCitations.replyingTo
39760
40436
  ? {
39761
40437
  ...messageWithCitations.replyingTo,
39762
- content: promptbookifyAiText(humanizeAiText(messageWithCitations.replyingTo.content)),
40438
+ content: postprocessChatMessageContent(messageWithCitations.replyingTo.content),
39763
40439
  }
39764
40440
  : undefined;
39765
40441
  return {
39766
40442
  ...messageWithCitations,
39767
- content: promptbookifyAiText(humanizeAiText(messageWithCitations.content)),
40443
+ content: postprocessChatMessageContent(messageWithCitations.content),
39768
40444
  ...(normalizedReplyingTo ? { replyingTo: normalizedReplyingTo } : {}),
39769
40445
  };
39770
40446
  }
40447
+ /**
40448
+ * Applies markdown-safe text normalization to one chat message content value.
40449
+ *
40450
+ * @private utility of `useChatPostprocessedMessages`
40451
+ */
40452
+ function postprocessChatMessageContent(content) {
40453
+ const decodedContent = decodeJsonUnicodeEscapesInMarkdownText(content);
40454
+ return promptbookifyAiText(humanizeAiText(decodedContent));
40455
+ }
39771
40456
  /**
39772
40457
  * Memoizes the render-only message normalization used by `<Chat/>`.
39773
40458
  *
@@ -40579,7 +41264,7 @@ function hasChatActions(postprocessedMessages, { onReset, newChatButtonHref, onU
40579
41264
  * @public exported from `@promptbook/components`
40580
41265
  */
40581
41266
  function Chat(props) {
40582
- const { title = 'Chat', messages, onChange, onMessage, onActionButton, onQuickMessageButton, onReplyToMessage, onCancelReply, onReset, resetRequiresConfirmation = true, newChatButtonHref, onFeedback, feedbackMode = 'stars', feedbackTranslations, timingTranslations, onFileUpload, chatLocale, speechRecognition, placeholderMessageContent, defaultMessage, enterBehavior, resolveEnterBehavior, children, className, style, isAiTextHumanizedAndPromptbookified = true, isVoiceCalling = false, isFocusedOnLoad, participants = [], canReplyToMessage, replyingToMessage, extraActions, actionsContainer, saveFormats, saveFormatHandlers, isSaveButtonEnabled = true, isCopyButtonEnabled = true, buttonColor: buttonColorRaw, onUseTemplate, onCreateAgent, toolTitles, teammates, teamAgentProfiles, layout, visualMode = 'ARTICLE_MODE', theme = 'LIGHT', effectConfigs, soundSystem, speechRecognitionLanguage, isSpeechPlaybackEnabled = true, elevenLabsVoiceId, chatUiTranslations, } = props;
41267
+ const { title = 'Chat', messages, onChange, onMessage, onActionButton, onQuickMessageButton, onReplyToMessage, onCancelReply, onReset, resetRequiresConfirmation = true, newChatButtonHref, onFeedback, feedbackMode = 'stars', feedbackTranslations, timingTranslations, resolveCitationLabel, onFileUpload, chatLocale, speechRecognition, placeholderMessageContent, defaultMessage, enterBehavior, resolveEnterBehavior, children, className, style, isAiTextHumanizedAndPromptbookified = true, isVoiceCalling = false, isFocusedOnLoad, participants = [], canReplyToMessage, replyingToMessage, extraActions, actionsContainer, saveFormats, saveFormatHandlers, isSaveButtonEnabled = true, isCopyButtonEnabled = true, buttonColor: buttonColorRaw, onUseTemplate, onCreateAgent, toolTitles, teammates, teamAgentProfiles, layout, visualMode = 'ARTICLE_MODE', theme = 'LIGHT', effectConfigs, soundSystem, speechRecognitionLanguage, isSpeechPlaybackEnabled = true, elevenLabsVoiceId, chatUiTranslations, } = props;
40583
41268
  const buttonColor = useMemo(() => Color.from(buttonColorRaw || '#0066cc'), [buttonColorRaw]);
40584
41269
  const agentParticipant = useMemo(() => participants.find((participant) => participant.name === 'AGENT'), [participants]);
40585
41270
  const postprocessedMessages = useChatPostprocessedMessages({
@@ -40624,7 +41309,7 @@ function Chat(props) {
40624
41309
  useChatCompleteNotification(messages, soundSystem);
40625
41310
  return (jsxs(Fragment, { children: [feedbackStatus && (jsx("div", { className: classNames(styles$7.feedbackStatus, feedbackStatus.variant === 'success'
40626
41311
  ? styles$7.feedbackStatusSuccess
40627
- : styles$7.feedbackStatusError), "aria-live": "polite", role: "status", children: feedbackStatus.message })), effectConfigs && effectConfigs.length > 0 && (jsx(ChatEffectsSystem, { messages: postprocessedMessages, effectConfigs: effectConfigs, soundSystem: soundSystem })), jsx("div", { className: classNames(className, styles$7.Chat, layout === 'STANDALONE' && styles$7.standaloneVisual, layout === 'FULL_PAGE' && styles$7.fullPageVisual, isConstrainedArticleMode && styles$7.constrainedArticleVisual, getChatCssClassName('Chat'), chatCssClassNames.chat), "data-chat-theme": mode.toLowerCase(), style, children: jsxs("div", { className: classNames(className, styles$7.chatMainFlow, getChatCssClassName('chatMainFlow'), chatCssClassNames.chatMainFlow), children: [children && jsx("div", { className: classNames(styles$7.chatChildren), children: children }), shouldShowScrollToBottom && (jsx("div", { className: styles$7.scrollToBottomContainer, children: jsxs("div", { className: styles$7.scrollToBottomWrapper, children: [jsx(SolidArrowButton, { "data-button-type": "custom", direction: "down", iconSize: 33, className: classNames(styles$7.scrollToBottom, scrollToBottomCssClassName), onClick: handleButtonClick(() => scrollToBottom()), "aria-label": ariaLabel, title: ariaLabel }), badgeLabel && (jsx("span", { className: styles$7.scrollToBottomBadge, "aria-live": "polite", role: "status", children: badgeLabel }))] }) })), isVoiceCalling && (jsx("div", { className: styles$7.voiceCallIndicatorBar, children: jsxs("div", { className: styles$7.voiceCallIndicator, children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }), jsx("span", { children: "Voice call active" }), jsx("div", { className: styles$7.voiceCallPulse })] }) })), jsx(ChatActionsBar, { actionsRef: actionsRef, actionsContainer: actionsContainer, messages: postprocessedMessages, participants: participants, title: title, onReset: onReset, resetRequiresConfirmation: resetRequiresConfirmation, newChatButtonHref: newChatButtonHref, onUseTemplate: onUseTemplate, extraActions: extraActions, saveFormats: saveFormats, saveFormatHandlers: saveFormatHandlers, isSaveButtonEnabled: isSaveButtonEnabled, shouldFadeActions: shouldFadeActions, shouldDisableActions: shouldDisableActions, chatUiTranslations: chatUiTranslations, onButtonClick: handleButtonClick }), jsx(ChatMessageList, { messages: postprocessedMessages, participants: participants, expandedMessageId: expandedMessageId, messageRatings: messageRatings, setExpandedMessageId: setExpandedMessageId, handleRating: handleRating, mode: mode, isCopyButtonEnabled: isCopyButtonEnabled, isFeedbackEnabled: isFeedbackEnabled, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, timingTranslations: timingTranslations, chatLocale: chatLocale, onCopy: handleCopy, onMessage: onMessage, onActionButton: onActionButton, onQuickMessageButton: onQuickMessageButton, onReplyToMessage: onReplyToMessage, canReplyToMessage: canReplyToMessage, onCreateAgent: onCreateAgent, toolTitles: toolTitles, teammates: teammates, teamAgentProfiles: teamAgentProfiles, visualMode: visualMode, soundSystem: soundSystem, onToolCallClick: openToolCall, onCitationClick: openCitation, setChatMessagesElement: setChatMessagesElement, onScroll: handleChatScroll, isSpeechPlaybackEnabled: isSpeechPlaybackEnabled, elevenLabsVoiceId: elevenLabsVoiceId, chatUiTranslations: chatUiTranslations, chatMessagesClassName: classNames(isConstrainedArticleMode && styles$7.articleModeChatMessages, getChatCssClassName('chatMessages'), chatCssClassNames.chatMessages), hasActions: hasActions }), onMessage && (jsx(ChatInputArea, { onMessage: onMessage, onChange: onChange, onFileUpload: onFileUpload, speechRecognition: speechRecognition, speechRecognitionLanguage: speechRecognitionLanguage, replyingToMessage: replyingToMessage, onCancelReply: onCancelReply, defaultMessage: defaultMessage, enterBehavior: enterBehavior, resolveEnterBehavior: resolveEnterBehavior, placeholderMessageContent: placeholderMessageContent || (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.inputPlaceholder), isFocusedOnLoad: isFocusedOnLoad, isMobile: isMobile, isVoiceCalling: isVoiceCalling, participants: participants, buttonColor: buttonColor, soundSystem: soundSystem, onButtonClick: handleButtonClick, chatUiTranslations: chatUiTranslations, chatInputClassName: classNames(isConstrainedArticleMode && styles$7.articleModeChatInput, getChatCssClassName('chatInput'), chatCssClassNames.chatInput) }))] }) }), jsx(ChatToolCallModal, { isOpen: toolCallModalOpen, toolCall: selectedToolCall, toolCallIdentity: selectedToolCallIdentity, onClose: closeToolCallModal, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor, teamAgentProfiles: teamAgentProfiles, chatUiTranslations: chatUiTranslations, locale: chatLocale, availableTools: selectedMessageAvailableTools, mode: mode }), jsx(ChatCitationModal, { isOpen: citationModalOpen, citation: selectedCitation, participants: participants, soundSystem: soundSystem, onClose: closeCitationModal }), jsx(ChatRatingModal, { isOpen: ratingModalOpen, selectedMessage: selectedMessage, postprocessedMessages: postprocessedMessages, messages: messages, hoveredRating: hoveredRating, messageRatings: messageRatings, textRating: textRating, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, mode: mode, isMobile: isMobile, onClose: () => setRatingModalOpen(false), setHoveredRating: setHoveredRating, setMessageRatings: setMessageRatings, setSelectedMessage: setSelectedMessage, setTextRating: setTextRating, submitRating: submitRating })] }));
41312
+ : styles$7.feedbackStatusError), "aria-live": "polite", role: "status", children: feedbackStatus.message })), effectConfigs && effectConfigs.length > 0 && (jsx(ChatEffectsSystem, { messages: postprocessedMessages, effectConfigs: effectConfigs, soundSystem: soundSystem })), jsx("div", { className: classNames(className, styles$7.Chat, layout === 'STANDALONE' && styles$7.standaloneVisual, layout === 'FULL_PAGE' && styles$7.fullPageVisual, isConstrainedArticleMode && styles$7.constrainedArticleVisual, getChatCssClassName('Chat'), chatCssClassNames.chat), "data-chat-theme": mode.toLowerCase(), style, children: jsxs("div", { className: classNames(className, styles$7.chatMainFlow, getChatCssClassName('chatMainFlow'), chatCssClassNames.chatMainFlow), children: [children && jsx("div", { className: classNames(styles$7.chatChildren), children: children }), shouldShowScrollToBottom && (jsx("div", { className: styles$7.scrollToBottomContainer, children: jsxs("div", { className: styles$7.scrollToBottomWrapper, children: [jsx(SolidArrowButton, { "data-button-type": "custom", direction: "down", iconSize: 33, className: classNames(styles$7.scrollToBottom, scrollToBottomCssClassName), onClick: handleButtonClick(() => scrollToBottom()), "aria-label": ariaLabel, title: ariaLabel }), badgeLabel && (jsx("span", { className: styles$7.scrollToBottomBadge, "aria-live": "polite", role: "status", children: badgeLabel }))] }) })), isVoiceCalling && (jsx("div", { className: styles$7.voiceCallIndicatorBar, children: jsxs("div", { className: styles$7.voiceCallIndicator, children: [jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsx("path", { d: "M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" }) }), jsx("span", { children: "Voice call active" }), jsx("div", { className: styles$7.voiceCallPulse })] }) })), jsx(ChatActionsBar, { actionsRef: actionsRef, actionsContainer: actionsContainer, messages: postprocessedMessages, participants: participants, title: title, onReset: onReset, resetRequiresConfirmation: resetRequiresConfirmation, newChatButtonHref: newChatButtonHref, onUseTemplate: onUseTemplate, extraActions: extraActions, saveFormats: saveFormats, saveFormatHandlers: saveFormatHandlers, isSaveButtonEnabled: isSaveButtonEnabled, shouldFadeActions: shouldFadeActions, shouldDisableActions: shouldDisableActions, chatUiTranslations: chatUiTranslations, onButtonClick: handleButtonClick }), jsx(ChatMessageList, { messages: postprocessedMessages, participants: participants, expandedMessageId: expandedMessageId, messageRatings: messageRatings, setExpandedMessageId: setExpandedMessageId, handleRating: handleRating, mode: mode, isCopyButtonEnabled: isCopyButtonEnabled, isFeedbackEnabled: isFeedbackEnabled, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, timingTranslations: timingTranslations, resolveCitationLabel: resolveCitationLabel, chatLocale: chatLocale, onCopy: handleCopy, onMessage: onMessage, onActionButton: onActionButton, onQuickMessageButton: onQuickMessageButton, onReplyToMessage: onReplyToMessage, canReplyToMessage: canReplyToMessage, onCreateAgent: onCreateAgent, toolTitles: toolTitles, teammates: teammates, teamAgentProfiles: teamAgentProfiles, visualMode: visualMode, soundSystem: soundSystem, onToolCallClick: openToolCall, onCitationClick: openCitation, setChatMessagesElement: setChatMessagesElement, onScroll: handleChatScroll, isSpeechPlaybackEnabled: isSpeechPlaybackEnabled, elevenLabsVoiceId: elevenLabsVoiceId, chatUiTranslations: chatUiTranslations, chatMessagesClassName: classNames(isConstrainedArticleMode && styles$7.articleModeChatMessages, getChatCssClassName('chatMessages'), chatCssClassNames.chatMessages), hasActions: hasActions }), onMessage && (jsx(ChatInputArea, { onMessage: onMessage, onChange: onChange, onFileUpload: onFileUpload, speechRecognition: speechRecognition, speechRecognitionLanguage: speechRecognitionLanguage, replyingToMessage: replyingToMessage, onCancelReply: onCancelReply, defaultMessage: defaultMessage, enterBehavior: enterBehavior, resolveEnterBehavior: resolveEnterBehavior, placeholderMessageContent: placeholderMessageContent || (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.inputPlaceholder), isFocusedOnLoad: isFocusedOnLoad, isMobile: isMobile, isVoiceCalling: isVoiceCalling, participants: participants, buttonColor: buttonColor, soundSystem: soundSystem, onButtonClick: handleButtonClick, chatUiTranslations: chatUiTranslations, chatInputClassName: classNames(isConstrainedArticleMode && styles$7.articleModeChatInput, getChatCssClassName('chatInput'), chatCssClassNames.chatInput) }))] }) }), jsx(ChatToolCallModal, { isOpen: toolCallModalOpen, toolCall: selectedToolCall, toolCallIdentity: selectedToolCallIdentity, onClose: closeToolCallModal, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor, teamAgentProfiles: teamAgentProfiles, chatUiTranslations: chatUiTranslations, locale: chatLocale, availableTools: selectedMessageAvailableTools, mode: mode }), jsx(ChatCitationModal, { isOpen: citationModalOpen, citation: selectedCitation, participants: participants, resolveCitationLabel: resolveCitationLabel, soundSystem: soundSystem, onClose: closeCitationModal }), jsx(ChatRatingModal, { isOpen: ratingModalOpen, selectedMessage: selectedMessage, postprocessedMessages: postprocessedMessages, messages: messages, hoveredRating: hoveredRating, messageRatings: messageRatings, textRating: textRating, feedbackMode: feedbackMode, feedbackTranslations: feedbackTranslations, mode: mode, isMobile: isMobile, onClose: () => setRatingModalOpen(false), setHoveredRating: setHoveredRating, setMessageRatings: setMessageRatings, setSelectedMessage: setSelectedMessage, setTextRating: setTextRating, submitRating: submitRating })] }));
40628
41313
  }
40629
41314
 
40630
41315
  /**
@@ -48631,11 +49316,19 @@ const JsonFileImportPlugin = {
48631
49316
  try {
48632
49317
  const json = JSON.parse(content);
48633
49318
  const formattedJson = JSON.stringify(json, null, 4);
48634
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
49319
+ return spaceTrim$1((block) => `
49320
+ \`\`\`json
49321
+ ${block(formattedJson)}
49322
+ \`\`\`
49323
+ `);
48635
49324
  }
48636
49325
  catch (error) {
48637
49326
  // If JSON is invalid, still import it but maybe not as pretty JSON
48638
- return `\`\`\`json\n${content}\n\`\`\``;
49327
+ return spaceTrim$1((block) => `
49328
+ \`\`\`json
49329
+ ${block(content)}
49330
+ \`\`\`
49331
+ `);
48639
49332
  }
48640
49333
  },
48641
49334
  };
@@ -48659,7 +49352,11 @@ const TextFileImportPlugin = {
48659
49352
  import(content, mimeType) {
48660
49353
  const extension = mimeTypeToExtension(mimeType);
48661
49354
  const codeBlockType = extension || 'txt';
48662
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
49355
+ return spaceTrim$1((block) => `
49356
+ \`\`\`${codeBlockType}
49357
+ ${block(content)}
49358
+ \`\`\`
49359
+ `);
48663
49360
  },
48664
49361
  };
48665
49362
 
@@ -48906,11 +49603,19 @@ function collectExampleInteractionLines(parseResult, samples) {
48906
49603
  const examples = [];
48907
49604
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
48908
49605
  if (initialMessage) {
48909
- examples.push(`**Agent:**\n${initialMessage}`);
49606
+ examples.push(spaceTrim$1((block) => `
49607
+ **Agent:**
49608
+ ${block(initialMessage)}
49609
+ `));
48910
49610
  }
48911
49611
  if (samples && samples.length > 0) {
48912
49612
  for (const sample of samples) {
48913
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
49613
+ examples.push(spaceTrim$1((block) => `
49614
+ **User:** ${block(String(sample.question))}
49615
+
49616
+ **Agent:**
49617
+ ${block(sample.answer)}
49618
+ `));
48914
49619
  }
48915
49620
  }
48916
49621
  return examples;
@@ -55456,7 +56161,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
55456
56161
  };
55457
56162
  }
55458
56163
  return {
55459
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
56164
+ content: spaceTrim$1((block) => `
56165
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
56166
+
56167
+ [...truncated...]
56168
+ `),
55460
56169
  isTruncated: true,
55461
56170
  };
55462
56171
  }