@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/umd/index.umd.js CHANGED
@@ -34,7 +34,7 @@
34
34
  * @generated
35
35
  * @see https://github.com/webgptorg/promptbook
36
36
  */
37
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-126';
37
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-128';
38
38
  /**
39
39
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
40
40
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -5269,41 +5269,91 @@
5269
5269
  context.fill();
5270
5270
  context.restore();
5271
5271
  }
5272
+ /**
5273
+ * Number of latitude segments used by the single blobby octopus mesh.
5274
+ *
5275
+ * @private helper of `octopus3d2AvatarVisual`
5276
+ */
5277
+ const LATITUDE_PATCH_COUNT$1 = 12;
5278
+ /**
5279
+ * Number of longitude segments used by the single blobby octopus mesh.
5280
+ *
5281
+ * @private helper of `octopus3d2AvatarVisual`
5282
+ */
5283
+ const LONGITUDE_PATCH_COUNT$1 = 24;
5272
5284
  /**
5273
5285
  * Resolves all visible projected patches for the single blobby octopus mesh.
5274
5286
  *
5287
+ * Within a single frame, mesh corner samples and longitude-only lobe-wave values are
5288
+ * quantized to the patch grid and computed once each rather than re-evaluated for every
5289
+ * patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
5290
+ * `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
5291
+ * corners are shared between neighboring patches.
5292
+ *
5275
5293
  * @private helper of `octopus3d2AvatarVisual`
5276
5294
  */
5277
5295
  function resolveVisibleBlobbyOctopusPatches(options) {
5278
5296
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
5279
- const latitudePatchCount = 12;
5280
- const longitudePatchCount = 24;
5297
+ const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
5298
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
5281
5299
  const surfacePatches = [];
5300
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
5301
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
5302
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
5303
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
5304
+ }
5305
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5306
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
5307
+ }
5308
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
5309
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5310
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
5311
+ }
5312
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
5313
+ const transformedCornerSamples = new Array(cornerCount);
5314
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
5315
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
5316
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
5317
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
5318
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
5319
+ const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
5320
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
5321
+ }
5322
+ }
5282
5323
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
5283
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
5284
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
5324
+ const startLatitude = latitudeBoundaries[latitudeIndex];
5325
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
5285
5326
  const centerLatitude = (startLatitude + endLatitude) / 2;
5286
5327
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
5328
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
5329
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
5287
5330
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5288
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
5289
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
5331
+ const startLongitude = longitudeBoundaries[longitudeIndex];
5332
+ const endLongitude = longitudeBoundaries[longitudeIndex + 1];
5290
5333
  const centerLongitude = (startLongitude + endLongitude) / 2;
5291
- const localCorners = [
5292
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, startLongitude),
5293
- sampleBlobbyOctopusSurfacePoint(options, startLatitude, endLongitude),
5294
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, endLongitude),
5295
- sampleBlobbyOctopusSurfacePoint(options, endLatitude, startLongitude),
5334
+ const transformedCorners = [
5335
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
5336
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
5337
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
5338
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
5296
5339
  ];
5297
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
5298
5340
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
5299
5341
  if (surfaceNormal.z <= 0.01) {
5300
5342
  continue;
5301
5343
  }
5302
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
5344
+ const projectedCorners = [
5345
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
5346
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
5347
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
5348
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
5349
+ ];
5303
5350
  surfacePatches.push({
5304
5351
  corners: projectedCorners,
5305
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
5306
- transformedCorners.length,
5352
+ averageDepth: (transformedCorners[0].z +
5353
+ transformedCorners[1].z +
5354
+ transformedCorners[2].z +
5355
+ transformedCorners[3].z) /
5356
+ 4,
5307
5357
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
5308
5358
  fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
5309
5359
  outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
@@ -5321,12 +5371,21 @@
5321
5371
  * @private helper of `octopus3d2AvatarVisual`
5322
5372
  */
5323
5373
  function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
5374
+ const { morphologyProfile, animationPhase, timeMs } = options;
5375
+ return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
5376
+ }
5377
+ /**
5378
+ * Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
5379
+ * to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
5380
+ *
5381
+ * @private helper of `octopus3d2AvatarVisual`
5382
+ */
5383
+ function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
5324
5384
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
5325
5385
  const cosineLatitude = Math.max(0, Math.cos(latitude));
5326
5386
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
5327
5387
  const upperBlend = Math.pow(1 - verticalProgress, 1.2);
5328
5388
  const lowerBlend = Math.pow(verticalProgress, 1.42);
5329
- const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
5330
5389
  const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
5331
5390
  const horizontalScale = 1.02 +
5332
5391
  skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
@@ -5623,45 +5682,102 @@
5623
5682
  context.fill();
5624
5683
  context.restore();
5625
5684
  }
5685
+ /**
5686
+ * Number of latitude segments used by the continuous Octopus 3D 3 mesh.
5687
+ *
5688
+ * @private helper of `octopus3d3AvatarVisual`
5689
+ */
5690
+ const LATITUDE_PATCH_COUNT = 16;
5691
+ /**
5692
+ * Number of longitude segments used by the continuous Octopus 3D 3 mesh.
5693
+ *
5694
+ * @private helper of `octopus3d3AvatarVisual`
5695
+ */
5696
+ const LONGITUDE_PATCH_COUNT = 40;
5626
5697
  /**
5627
5698
  * Resolves visible projected patches for the continuous octopus mesh.
5628
5699
  *
5700
+ * Within a single frame, mesh corner samples and longitude-only computations (tentacle
5701
+ * influence and lobe wave) are quantized to the patch grid and computed once each rather
5702
+ * than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
5703
+ * for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
5704
+ * triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
5705
+ *
5629
5706
  * @private helper of `octopus3d3AvatarVisual`
5630
5707
  */
5631
5708
  function resolveVisibleContinuousOctopusPatches(options) {
5632
5709
  const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
5633
- const latitudePatchCount = 16;
5634
- const longitudePatchCount = 40;
5710
+ const latitudePatchCount = LATITUDE_PATCH_COUNT;
5711
+ const longitudePatchCount = LONGITUDE_PATCH_COUNT;
5635
5712
  const surfacePatches = [];
5713
+ const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
5714
+ const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
5715
+ for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
5716
+ latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
5717
+ }
5718
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5719
+ longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
5720
+ }
5721
+ const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
5722
+ const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
5723
+ const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
5724
+ const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
5725
+ const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
5726
+ for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
5727
+ const cornerLongitude = longitudeBoundaries[boundaryIndex];
5728
+ cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
5729
+ cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
5730
+ }
5731
+ for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5732
+ const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
5733
+ cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
5734
+ cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
5735
+ cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
5736
+ }
5737
+ const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
5738
+ const transformedCornerSamples = new Array(cornerCount);
5739
+ for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
5740
+ const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
5741
+ for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
5742
+ const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
5743
+ const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
5744
+ const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
5745
+ transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
5746
+ }
5747
+ }
5636
5748
  for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
5637
- const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
5638
- const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
5749
+ const startLatitude = latitudeBoundaries[latitudeIndex];
5750
+ const endLatitude = latitudeBoundaries[latitudeIndex + 1];
5639
5751
  const centerLatitude = (startLatitude + endLatitude) / 2;
5640
5752
  const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
5753
+ const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
5754
+ const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
5641
5755
  for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
5642
- const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
5643
- const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
5644
- const centerLongitude = (startLongitude + endLongitude) / 2;
5645
- const localCorners = [
5646
- sampleContinuousOctopusSurfacePoint(options, startLatitude, startLongitude),
5647
- sampleContinuousOctopusSurfacePoint(options, startLatitude, endLongitude),
5648
- sampleContinuousOctopusSurfacePoint(options, endLatitude, endLongitude),
5649
- sampleContinuousOctopusSurfacePoint(options, endLatitude, startLongitude),
5756
+ const transformedCorners = [
5757
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex],
5758
+ transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
5759
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
5760
+ transformedCornerSamples[endCornerRowOffset + longitudeIndex],
5650
5761
  ];
5651
- const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
5652
5762
  const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
5653
5763
  if (surfaceNormal.z <= 0.008) {
5654
5764
  continue;
5655
5765
  }
5656
- const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
5657
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, centerLongitude);
5658
- const lowerLobeWave = resolveContinuousLobeWave(options, centerLongitude);
5766
+ const projectedCorners = [
5767
+ projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
5768
+ projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
5769
+ projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
5770
+ projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
5771
+ ];
5659
5772
  surfacePatches.push({
5660
5773
  corners: projectedCorners,
5661
- averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
5662
- transformedCorners.length,
5774
+ averageDepth: (transformedCorners[0].z +
5775
+ transformedCorners[1].z +
5776
+ transformedCorners[2].z +
5777
+ transformedCorners[3].z) /
5778
+ 4,
5663
5779
  lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
5664
- fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), tentacleInfluence.core, lowerLobeWave),
5780
+ fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
5665
5781
  outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
5666
5782
  });
5667
5783
  }
@@ -5677,16 +5793,27 @@
5677
5793
  * @private helper of `octopus3d3AvatarVisual`
5678
5794
  */
5679
5795
  function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
5796
+ return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
5797
+ }
5798
+ /**
5799
+ * Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
5800
+ * values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
5801
+ *
5802
+ * The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
5803
+ * corner grid, so the same longitude is reused across every latitude row and each
5804
+ * tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
5805
+ *
5806
+ * @private helper of `octopus3d3AvatarVisual`
5807
+ */
5808
+ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
5680
5809
  const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
5681
5810
  const cosineLatitude = Math.max(0, Math.cos(latitude));
5682
5811
  const verticalProgress = (Math.sin(latitude) + 1) / 2;
5683
5812
  const upperBlend = Math.pow(1 - verticalProgress, 1.28);
5684
5813
  const lowerBlend = smoothStep(0.38, 1, verticalProgress);
5685
5814
  const tipBlend = smoothStep(0.68, 1, verticalProgress);
5686
- const tentacleInfluence = resolveContinuousTentacleInfluence(options, longitude);
5687
5815
  const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
5688
5816
  const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
5689
- const lowerLobeWave = resolveContinuousLobeWave(options, longitude);
5690
5817
  const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
5691
5818
  animationPhase * 0.6 +
5692
5819
  timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
@@ -9375,8 +9502,11 @@
9375
9502
  if (!trimmedContent) {
9376
9503
  return requirements;
9377
9504
  }
9378
- // Add goal as a proper h2 section to the system message
9379
- const goalSection = `## Goal\n\n${trimmedContent}`;
9505
+ const goalSection = spacetrim.spaceTrim((block) => `
9506
+ ## Goal
9507
+
9508
+ ${block(trimmedContent)}
9509
+ `);
9380
9510
  const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
9381
9511
  return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
9382
9512
  }
@@ -9898,8 +10028,11 @@
9898
10028
  if (!trimmedContent) {
9899
10029
  return requirements;
9900
10030
  }
9901
- // Add language as a bullet under a ## Language section
9902
- const languageSection = `## Language\n\n- Your language is ${trimmedContent}`;
10031
+ const languageSection = spacetrim.spaceTrim((block) => `
10032
+ ## Language
10033
+
10034
+ - Your language is ${block(trimmedContent)}
10035
+ `);
9903
10036
  return this.appendToSystemMessage(requirements, languageSection, '\n\n');
9904
10037
  }
9905
10038
  }
@@ -13479,7 +13612,15 @@
13479
13612
  * Builds the teammate request text, optionally including context.
13480
13613
  */
13481
13614
  function buildTeammateRequest(message, context) {
13482
- return context ? `${message}\n\nContext:\n${context}` : message;
13615
+ if (!context) {
13616
+ return message;
13617
+ }
13618
+ return spacetrim.spaceTrim((block) => `
13619
+ ${block(message)}
13620
+
13621
+ Context:
13622
+ ${block(context)}
13623
+ `);
13483
13624
  }
13484
13625
  /**
13485
13626
  * Builds a minimal chat prompt for teammate calls.
@@ -17502,7 +17643,11 @@
17502
17643
  const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
17503
17644
  const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
17504
17645
  const contentToReturn = wasCharacterTruncated
17505
- ? `${lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS)}\n\n[...truncated...]`
17646
+ ? spacetrim.spaceTrim((block) => `
17647
+ ${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
17648
+
17649
+ [...truncated...]
17650
+ `)
17506
17651
  : lineRangedContent;
17507
17652
  const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
17508
17653
  if (wasCharacterTruncated) {
@@ -25804,11 +25949,19 @@
25804
25949
  * Pattern matching punctuation that should be trimmed from citation tails.
25805
25950
  */
25806
25951
  const TRAILING_PUNCTUATION_REGEX = /[.,;:!?)+\]]+$/;
25952
+ /**
25953
+ * Pattern matching filename separators that should become spaces in source labels.
25954
+ */
25955
+ const FILENAME_SEPARATOR_REGEX = /[-_]+/g;
25956
+ /**
25957
+ * Pattern matching consecutive whitespace in display labels.
25958
+ */
25959
+ const WHITESPACE_REGEX = /\s+/g;
25807
25960
  /**
25808
25961
  * Collapses consecutive whitespace into single spaces.
25809
25962
  */
25810
25963
  function collapseWhitespace(value) {
25811
- return value.replace(/\s+/g, ' ');
25964
+ return value.replace(WHITESPACE_REGEX, ' ');
25812
25965
  }
25813
25966
  /**
25814
25967
  * Trims a value before further citation normalization.
@@ -25881,6 +26034,10 @@
25881
26034
  * @private utility of `<Chat/>` citation rendering
25882
26035
  */
25883
26036
  function getCitationLabel(citation) {
26037
+ const title = normalizeCitationDisplayLabel(citation.title);
26038
+ if (title) {
26039
+ return title;
26040
+ }
25884
26041
  const trimmed = trimToNormalized(citation.source);
25885
26042
  if (!trimmed) {
25886
26043
  return citation.source;
@@ -25892,7 +26049,7 @@
25892
26049
  }
25893
26050
  return collapsed.slice(0, TEXT_LABEL_LENGTH) + LABEL_ELLIPSIS;
25894
26051
  }
25895
- return simplifyKnowledgeLabel(trimmed);
26052
+ return createReadableCitationSourceLabel(trimmed);
25896
26053
  }
25897
26054
  /**
25898
26055
  * Resolves the preview URL used inside the citation modal iframe.
@@ -25911,8 +26068,134 @@
25911
26068
  const knowledgeUrl = resolveCitationUrl(trimmed, participants);
25912
26069
  return explicitUrl || literalUrl || knowledgeUrl || null;
25913
26070
  }
26071
+ /**
26072
+ * Creates a readable fallback label from a citation source when no title metadata is available.
26073
+ *
26074
+ * @param source - Raw citation source value.
26075
+ * @returns Human-friendly source label.
26076
+ *
26077
+ * @private utility of `<Chat/>` citation rendering
26078
+ */
26079
+ function createReadableCitationSourceLabel(source) {
26080
+ const trimmed = trimToNormalized(source);
26081
+ if (!trimmed) {
26082
+ return source;
26083
+ }
26084
+ const parsedUrl = parseCitationUrl(trimmed);
26085
+ const filenameCandidate = parsedUrl
26086
+ ? getUrlLabelCandidate(parsedUrl) || parsedUrl.hostname.replace(/^www\./i, '')
26087
+ : simplifyKnowledgeLabel(trimmed);
26088
+ return normalizeCitationDisplayLabel(filenameCandidate) || simplifyKnowledgeLabel(trimmed);
26089
+ }
26090
+ /**
26091
+ * Normalizes a citation label candidate for display.
26092
+ *
26093
+ * @param label - Raw candidate label.
26094
+ * @returns Cleaned label or `null` when empty.
26095
+ *
26096
+ * @private utility of `<Chat/>` citation rendering
26097
+ */
26098
+ function normalizeCitationDisplayLabel(label) {
26099
+ const normalized = collapseWhitespace((label || '').replace(FILENAME_SEPARATOR_REGEX, ' ')).trim();
26100
+ return normalized || null;
26101
+ }
26102
+ /**
26103
+ * Parses one HTTP(S) citation URL, returning null for non-URL values.
26104
+ *
26105
+ * @param value - Candidate URL value.
26106
+ * @returns Parsed URL or null.
26107
+ *
26108
+ * @private utility of `<Chat/>` citation rendering
26109
+ */
26110
+ function parseCitationUrl(value) {
26111
+ try {
26112
+ const url = new URL(value);
26113
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url : null;
26114
+ }
26115
+ catch (_a) {
26116
+ return null;
26117
+ }
26118
+ }
26119
+ /**
26120
+ * Extracts a readable candidate from a URL path.
26121
+ *
26122
+ * @param url - Parsed citation URL.
26123
+ * @returns URL path label candidate or null.
26124
+ *
26125
+ * @private utility of `<Chat/>` citation rendering
26126
+ */
26127
+ function getUrlLabelCandidate(url) {
26128
+ const pathSegments = url.pathname.split('/').filter(Boolean);
26129
+ const lastPathSegment = pathSegments[pathSegments.length - 1];
26130
+ if (!lastPathSegment) {
26131
+ return null;
26132
+ }
26133
+ try {
26134
+ return simplifyKnowledgeLabel(decodeURIComponent(lastPathSegment));
26135
+ }
26136
+ catch (_a) {
26137
+ return simplifyKnowledgeLabel(lastPathSegment);
26138
+ }
26139
+ }
25914
26140
  // TODO: [💞] Spread into multiple files
25915
26141
 
26142
+ /**
26143
+ * Extracts one quick button definition from a markdown link query string.
26144
+ *
26145
+ * @param text The visible button label.
26146
+ * @param query Raw query-string payload captured from markdown.
26147
+ * @returns Parsed quick button definition or `null` when the link is not a supported quick button.
26148
+ *
26149
+ * @private internal helper of `parseMessageButtons`
26150
+ */
26151
+ function parseQuickButtonDefinition(text, query) {
26152
+ const searchParams = new URLSearchParams(query);
26153
+ const message = searchParams.get('message');
26154
+ if (message !== null) {
26155
+ return {
26156
+ type: 'message',
26157
+ text,
26158
+ message,
26159
+ };
26160
+ }
26161
+ const code = searchParams.get('action');
26162
+ if (code !== null) {
26163
+ return {
26164
+ type: 'action',
26165
+ text,
26166
+ code,
26167
+ };
26168
+ }
26169
+ return null;
26170
+ }
26171
+ /**
26172
+ * Parses markdown quick buttons in the format `[Button Text](?message=...)` or `[Button Text](?action=...)`.
26173
+ * Returns both the content without supported quick buttons and the extracted button definitions.
26174
+ *
26175
+ * @param content The markdown content that may contain buttons
26176
+ * @returns Object with contentWithoutButtons and buttons array
26177
+ *
26178
+ * @public exported from `@promptbook/components`
26179
+ */
26180
+ function parseMessageButtons(content) {
26181
+ const buttonRegex = /\[([^\]]+)\]\(\?([^)]+)\)/g;
26182
+ const buttons = [];
26183
+ const contentWithoutButtons = content
26184
+ .replace(buttonRegex, (match, text, query) => {
26185
+ const button = parseQuickButtonDefinition(text, query);
26186
+ if (!button) {
26187
+ return match;
26188
+ }
26189
+ buttons.push(button);
26190
+ return '';
26191
+ })
26192
+ .trim();
26193
+ return {
26194
+ contentWithoutButtons,
26195
+ buttons,
26196
+ };
26197
+ }
26198
+
25916
26199
  /**
25917
26200
  * Default citation identifier used for simplified citation markers without an explicit position.
25918
26201
  *
@@ -26803,12 +27086,13 @@
26803
27086
  * @private utility of `<Chat/>`
26804
27087
  */
26805
27088
  function normalizeParsedCitation(citation) {
26806
- var _a, _b;
27089
+ var _a, _b, _c;
26807
27090
  return {
26808
27091
  id: citation.id.trim(),
26809
27092
  source: citation.source.trim(),
26810
27093
  url: ((_a = citation.url) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
26811
- excerpt: ((_b = citation.excerpt) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
27094
+ title: ((_b = citation.title) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
27095
+ excerpt: ((_c = citation.excerpt) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
26812
27096
  };
26813
27097
  }
26814
27098
  /**
@@ -26825,11 +27109,13 @@
26825
27109
  id: currentCitation.id || incomingCitation.id,
26826
27110
  source: resolvePreferredCitationSource(currentCitation, incomingCitation),
26827
27111
  url: currentCitation.url || incomingCitation.url,
27112
+ title: currentCitation.title || incomingCitation.title,
26828
27113
  excerpt: currentCitation.excerpt || incomingCitation.excerpt,
26829
27114
  };
26830
27115
  if (mergedCitation.id === currentCitation.id &&
26831
27116
  mergedCitation.source === currentCitation.source &&
26832
27117
  mergedCitation.url === currentCitation.url &&
27118
+ mergedCitation.title === currentCitation.title &&
26833
27119
  mergedCitation.excerpt === currentCitation.excerpt) {
26834
27120
  return currentCitation;
26835
27121
  }
@@ -27179,6 +27465,12 @@
27179
27465
  * @private Internal helper shared by HTML and PDF chat exports.
27180
27466
  */
27181
27467
  const CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME = 'chat-html-export-render-root';
27468
+ /**
27469
+ * Roles whose messages are aligned to the right side of the exported transcript.
27470
+ *
27471
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27472
+ */
27473
+ const RIGHT_ALIGNED_SENDER_ROLES = new Set(['USER']);
27182
27474
  /**
27183
27475
  * Escapes HTML-sensitive text before embedding it into the export document.
27184
27476
  *
@@ -27192,6 +27484,38 @@
27192
27484
  .replace(/"/g, '&quot;')
27193
27485
  .replace(/'/g, '&#39;');
27194
27486
  }
27487
+ /**
27488
+ * Returns the first uppercase character of a display name, used for the fallback avatar glyph.
27489
+ *
27490
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27491
+ */
27492
+ function getAvatarInitial(displayName) {
27493
+ const trimmedName = displayName.trim();
27494
+ if (!trimmedName) {
27495
+ return '?';
27496
+ }
27497
+ return Array.from(trimmedName)[0].toUpperCase();
27498
+ }
27499
+ /**
27500
+ * Renders the avatar bubble shown next to one exported message.
27501
+ *
27502
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27503
+ */
27504
+ function buildAvatarMarkup(participant, displayName, accentColor) {
27505
+ const avatarStyle = `background-color:${escapeHtml$1(accentColor)};`;
27506
+ if (participant === null || participant === void 0 ? void 0 : participant.avatarSrc) {
27507
+ return spacetrim.spaceTrim(`
27508
+ <div class="message-avatar" aria-hidden="true" style="${avatarStyle}">
27509
+ <img src="${escapeHtml$1(participant.avatarSrc)}" alt="" loading="lazy" />
27510
+ </div>
27511
+ `);
27512
+ }
27513
+ return spacetrim.spaceTrim(`
27514
+ <div class="message-avatar message-avatar--initial" aria-hidden="true" style="${avatarStyle}">
27515
+ <span>${escapeHtml$1(getAvatarInitial(displayName))}</span>
27516
+ </div>
27517
+ `);
27518
+ }
27195
27519
  /**
27196
27520
  * Renders attachments for one exported message as a compact supporting section.
27197
27521
  *
@@ -27255,13 +27579,26 @@
27255
27579
  </section>
27256
27580
  `);
27257
27581
  }
27582
+ /**
27583
+ * Removes quick action and quick message buttons from the markdown body before rendering.
27584
+ *
27585
+ * Quick buttons are interactive UI affordances and have no meaning inside a static export.
27586
+ *
27587
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27588
+ */
27589
+ function stripQuickButtonsFromContent(content) {
27590
+ return parseMessageButtons(content).contentWithoutButtons;
27591
+ }
27258
27592
  /**
27259
27593
  * Converts one markdown message body to HTML with document-wide source references.
27260
27594
  *
27261
27595
  * @private Internal helper of `htmlSaveFormatDefinition`.
27262
27596
  */
27263
27597
  function renderFootnotedMarkdown(message, citationFootnotes) {
27264
- const renderModel = createChatExportCitationRenderModel(citationFootnotes, message);
27598
+ const renderModel = createChatExportCitationRenderModel(citationFootnotes, {
27599
+ ...message,
27600
+ content: stripQuickButtonsFromContent(message.content),
27601
+ });
27265
27602
  const html = linkCitationFootnoteReferences(renderMarkdown(renderModel.content));
27266
27603
  return {
27267
27604
  html,
@@ -27299,34 +27636,74 @@
27299
27636
  `);
27300
27637
  }
27301
27638
  /**
27302
- * Renders one message card for the standalone HTML export.
27639
+ * Determines whether a message bubble should sit on the right side of the transcript.
27640
+ *
27641
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27642
+ */
27643
+ function isRightAlignedMessage(participant, sender) {
27644
+ if ((participant === null || participant === void 0 ? void 0 : participant.isMe) === true) {
27645
+ return true;
27646
+ }
27647
+ return RIGHT_ALIGNED_SENDER_ROLES.has(sender.toUpperCase());
27648
+ }
27649
+ /**
27650
+ * Detects whether a message would render with no body, attachments, or supporting metadata after sanitization.
27651
+ *
27652
+ * Such messages exist only to carry quick buttons in the live chat UI and add no value to a printed transcript.
27653
+ *
27654
+ * @private Internal helper of `htmlSaveFormatDefinition`.
27655
+ */
27656
+ function isMessageEmptyForExport(message) {
27657
+ const trimmedContent = stripQuickButtonsFromContent(message.content).trim();
27658
+ if (trimmedContent.length > 0) {
27659
+ return false;
27660
+ }
27661
+ if (message.attachments && message.attachments.length > 0) {
27662
+ return false;
27663
+ }
27664
+ if (message.citations && message.citations.length > 0) {
27665
+ return false;
27666
+ }
27667
+ if (message.replyingTo) {
27668
+ return false;
27669
+ }
27670
+ return true;
27671
+ }
27672
+ /**
27673
+ * Renders one message bubble for the standalone HTML export.
27303
27674
  *
27304
27675
  * @private Internal helper of `htmlSaveFormatDefinition`.
27305
27676
  */
27306
27677
  function renderMessageBlock(message, participants, citationFootnotes) {
27678
+ var _a;
27307
27679
  const sender = String(message.sender || 'SYSTEM');
27308
- const upperSender = sender.toUpperCase();
27680
+ const participant = (_a = participants.get(sender)) !== null && _a !== void 0 ? _a : participants.get(sender.toUpperCase());
27309
27681
  const visuals = resolveChatExportParticipantVisuals(participants, sender);
27310
27682
  const timestamp = formatChatExportTimestamp(message.createdAt);
27311
27683
  const durationLabel = typeof message.generationDurationMs === 'number' ? `${(message.generationDurationMs / 1000).toFixed(1)}s` : '';
27312
27684
  const replyingToMarkup = buildReplyingToMarkup(message, citationFootnotes);
27313
27685
  const { html: messageBody, renderModel } = renderFootnotedMarkdown(message, citationFootnotes);
27686
+ const isRightAligned = isRightAlignedMessage(participant, sender);
27687
+ const avatarMarkup = buildAvatarMarkup(participant, visuals.displayName, visuals.accentColor);
27688
+ const alignmentClass = isRightAligned ? 'message--mine' : 'message--theirs';
27314
27689
  return spacetrim.spaceTrim(`
27315
- <article class="message-card" style="--message-accent:${escapeHtml$1(visuals.accentColor)};">
27316
- <header class="message-header">
27317
- <div class="message-header-main">
27690
+ <article class="message ${alignmentClass}" style="--message-accent:${escapeHtml$1(visuals.accentColor)};">
27691
+ ${avatarMarkup}
27692
+ <div class="message-body">
27693
+ <header class="message-meta">
27318
27694
  <span class="message-sender">${escapeHtml$1(visuals.displayName)}</span>
27319
- <span class="message-role">${escapeHtml$1(upperSender)}</span>
27695
+ ${timestamp ? `<time class="message-time">${escapeHtml$1(timestamp)}</time>` : ''}
27696
+ </header>
27697
+ <div class="message-bubble">
27698
+ ${replyingToMarkup}
27699
+ <div class="message-content markdown-content">
27700
+ ${messageBody || '<p class="message-empty">No text provided.</p>'}
27701
+ </div>
27320
27702
  </div>
27321
- ${timestamp ? `<time class="message-time">${escapeHtml$1(timestamp)}</time>` : ''}
27322
- </header>
27323
- ${replyingToMarkup}
27324
- <section class="message-content markdown-content">
27325
- ${messageBody || '<p class="message-empty">No text provided.</p>'}
27326
- </section>
27327
- ${durationLabel ? `<p class="message-duration">Responded in ${escapeHtml$1(durationLabel)}</p>` : ''}
27328
- ${buildAttachmentsMarkup(message)}
27329
- ${renderModel.footnotes.length === 0 ? buildCitationsMarkup(message) : ''}
27703
+ ${durationLabel ? `<p class="message-footnote">Responded in ${escapeHtml$1(durationLabel)}</p>` : ''}
27704
+ ${buildAttachmentsMarkup(message)}
27705
+ ${renderModel.footnotes.length === 0 ? buildCitationsMarkup(message) : ''}
27706
+ </div>
27330
27707
  </article>
27331
27708
  `);
27332
27709
  }
@@ -27390,8 +27767,11 @@
27390
27767
  const participantLookup = buildChatExportParticipantMap(participants);
27391
27768
  const citationFootnotes = createChatExportCitationFootnoteRegistry();
27392
27769
  const exportedLabel = formatChatExportTimestamp(new Date());
27393
- const messageMarkup = messages.length > 0
27394
- ? messages.map((message) => renderMessageBlock(message, participantLookup, citationFootnotes)).join('')
27770
+ const messagesForExport = messages.filter((message) => !isMessageEmptyForExport(message));
27771
+ const messageMarkup = messagesForExport.length > 0
27772
+ ? messagesForExport
27773
+ .map((message) => renderMessageBlock(message, participantLookup, citationFootnotes))
27774
+ .join('')
27395
27775
  : '<div class="empty-state">No messages were available in this chat export.</div>';
27396
27776
  const documentSourcesMarkup = buildDocumentSourcesMarkup(citationFootnotes, participants);
27397
27777
  return spacetrim.spaceTrim(`
@@ -27405,6 +27785,17 @@
27405
27785
  <style>
27406
27786
  :root {
27407
27787
  color-scheme: light;
27788
+ --chat-export-background: #f4f6fb;
27789
+ --chat-export-surface: #ffffff;
27790
+ --chat-export-text: #0f172a;
27791
+ --chat-export-muted: #64748b;
27792
+ --chat-export-soft-border: #e2e8f0;
27793
+ --chat-export-bubble-theirs-bg: #ffffff;
27794
+ --chat-export-bubble-theirs-text: #0f172a;
27795
+ --chat-export-bubble-theirs-border: #e2e8f0;
27796
+ --chat-export-bubble-mine-bg: #115EB6;
27797
+ --chat-export-bubble-mine-text: #ffffff;
27798
+ --chat-export-link: #0f6cbd;
27408
27799
  }
27409
27800
 
27410
27801
  * {
@@ -27414,25 +27805,27 @@
27414
27805
  body,
27415
27806
  .${CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME} {
27416
27807
  margin: 0;
27417
- background: #f8fafc;
27418
- color: #0f172a;
27419
- font-family: Inter, "Segoe UI", Arial, sans-serif;
27808
+ background: var(--chat-export-background);
27809
+ color: var(--chat-export-text);
27810
+ font-family: Inter, "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
27811
+ font-size: 15px;
27812
+ line-height: 1.55;
27420
27813
  }
27421
27814
 
27422
27815
  a {
27423
- color: #0f6cbd;
27816
+ color: var(--chat-export-link);
27424
27817
  }
27425
27818
 
27426
27819
  .document {
27427
- max-width: 860px;
27820
+ max-width: 820px;
27428
27821
  margin: 0 auto;
27429
- padding: 32px 20px 48px;
27822
+ padding: 40px 24px 56px;
27430
27823
  }
27431
27824
 
27432
27825
  .document-header {
27433
- margin-bottom: 24px;
27434
- padding-bottom: 16px;
27435
- border-bottom: 1px solid #e2e8f0;
27826
+ margin-bottom: 28px;
27827
+ padding-bottom: 18px;
27828
+ border-bottom: 1px solid var(--chat-export-soft-border);
27436
27829
  }
27437
27830
 
27438
27831
  .document-eyebrow {
@@ -27440,7 +27833,7 @@
27440
27833
  font-size: 11px;
27441
27834
  letter-spacing: 0.14em;
27442
27835
  text-transform: uppercase;
27443
- color: #64748b;
27836
+ color: var(--chat-export-muted);
27444
27837
  }
27445
27838
 
27446
27839
  .document-title {
@@ -27451,89 +27844,174 @@
27451
27844
 
27452
27845
  .document-meta {
27453
27846
  margin: 10px 0 0;
27454
- color: #475569;
27455
- font-size: 14px;
27847
+ color: var(--chat-export-muted);
27848
+ font-size: 13px;
27456
27849
  }
27457
27850
 
27458
27851
  .message-list {
27459
27852
  display: flex;
27460
27853
  flex-direction: column;
27461
- gap: 16px;
27854
+ gap: 22px;
27462
27855
  }
27463
27856
 
27464
- .message-card {
27465
- background: #ffffff;
27466
- border: 1px solid #dbe4f0;
27467
- border-left-width: 4px;
27468
- border-left-color: var(--message-accent, #64748b);
27469
- border-radius: 14px;
27470
- padding: 16px 18px;
27471
- }
27472
-
27473
- .message-header {
27857
+ .message {
27474
27858
  display: flex;
27475
- justify-content: space-between;
27476
27859
  align-items: flex-start;
27477
27860
  gap: 12px;
27478
- margin-bottom: 12px;
27479
27861
  }
27480
27862
 
27481
- .message-header-main {
27863
+ .message--mine {
27864
+ flex-direction: row-reverse;
27865
+ }
27866
+
27867
+ .message-avatar {
27868
+ flex-shrink: 0;
27869
+ width: 40px;
27870
+ height: 40px;
27871
+ border-radius: 50%;
27872
+ overflow: hidden;
27873
+ background-color: var(--message-accent, #64748b);
27874
+ color: #ffffff;
27482
27875
  display: flex;
27483
- flex-wrap: wrap;
27484
27876
  align-items: center;
27877
+ justify-content: center;
27878
+ }
27879
+
27880
+ .message-avatar img {
27881
+ width: 100%;
27882
+ height: 100%;
27883
+ object-fit: cover;
27884
+ display: block;
27885
+ }
27886
+
27887
+ .message-avatar--initial span {
27888
+ font-size: 16px;
27889
+ font-weight: 600;
27890
+ line-height: 1;
27891
+ }
27892
+
27893
+ .message-body {
27894
+ flex: 1 1 auto;
27895
+ min-width: 0;
27896
+ max-width: 78%;
27897
+ display: flex;
27898
+ flex-direction: column;
27899
+ gap: 6px;
27900
+ }
27901
+
27902
+ .message--mine .message-body {
27903
+ align-items: flex-end;
27904
+ }
27905
+
27906
+ .message-meta {
27907
+ display: flex;
27908
+ flex-wrap: wrap;
27909
+ align-items: baseline;
27485
27910
  gap: 8px;
27911
+ padding: 0 4px;
27912
+ }
27913
+
27914
+ .message--mine .message-meta {
27915
+ justify-content: flex-end;
27486
27916
  }
27487
27917
 
27488
27918
  .message-sender {
27489
- font-size: 16px;
27919
+ font-size: 13px;
27490
27920
  font-weight: 600;
27491
- color: var(--message-accent, #0f172a);
27921
+ color: var(--message-accent, var(--chat-export-text));
27922
+ }
27923
+
27924
+ .message--mine .message-sender {
27925
+ color: var(--chat-export-text);
27492
27926
  }
27493
27927
 
27494
- .message-role {
27928
+ .message-time {
27495
27929
  font-size: 11px;
27496
- letter-spacing: 0.12em;
27497
- text-transform: uppercase;
27498
- color: #64748b;
27930
+ color: var(--chat-export-muted);
27499
27931
  }
27500
27932
 
27501
- .message-time,
27502
- .message-duration {
27503
- font-size: 12px;
27504
- color: #64748b;
27933
+ .message-bubble {
27934
+ padding: 14px 16px;
27935
+ border-radius: 16px;
27936
+ background: var(--chat-export-bubble-theirs-bg);
27937
+ color: var(--chat-export-bubble-theirs-text);
27938
+ border: 1px solid var(--chat-export-bubble-theirs-border);
27939
+ border-top-left-radius: 4px;
27940
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
27941
+ max-width: 100%;
27942
+ overflow-wrap: anywhere;
27943
+ }
27944
+
27945
+ .message--mine .message-bubble {
27946
+ background: var(--chat-export-bubble-mine-bg);
27947
+ color: var(--chat-export-bubble-mine-text);
27948
+ border-color: transparent;
27949
+ border-top-left-radius: 16px;
27950
+ border-top-right-radius: 4px;
27951
+ }
27952
+
27953
+ .message--mine .message-bubble a {
27954
+ color: #d4e8ff;
27955
+ }
27956
+
27957
+ .message--mine .message-bubble code {
27958
+ background: rgba(255, 255, 255, 0.18);
27959
+ color: inherit;
27505
27960
  }
27506
27961
 
27507
- .message-duration {
27508
- margin: 12px 0 0;
27962
+ .message--mine .message-bubble pre {
27963
+ background: rgba(15, 23, 42, 0.35);
27964
+ color: #f8fafc;
27965
+ }
27966
+
27967
+ .message--mine .message-bubble blockquote {
27968
+ color: rgba(255, 255, 255, 0.85);
27969
+ border-color: rgba(255, 255, 255, 0.5);
27970
+ }
27971
+
27972
+ .message-footnote {
27973
+ margin: 2px 4px 0;
27974
+ font-size: 11px;
27975
+ color: var(--chat-export-muted);
27509
27976
  }
27510
27977
 
27511
27978
  .message-empty {
27512
- color: #64748b;
27979
+ color: var(--chat-export-muted);
27513
27980
  font-style: italic;
27981
+ margin: 0;
27514
27982
  }
27515
27983
 
27516
27984
  .reply-context {
27517
- margin-bottom: 14px;
27518
- padding: 12px 14px;
27985
+ margin: 0 0 10px;
27986
+ padding: 8px 12px;
27519
27987
  border-radius: 10px;
27520
- border: 1px solid #e2e8f0;
27521
- background: #f8fafc;
27988
+ border-left: 3px solid var(--message-accent, #cbd5e1);
27989
+ background: rgba(15, 23, 42, 0.04);
27990
+ }
27991
+
27992
+ .message--mine .reply-context {
27993
+ background: rgba(255, 255, 255, 0.18);
27994
+ border-left-color: rgba(255, 255, 255, 0.7);
27522
27995
  }
27523
27996
 
27524
27997
  .reply-context-title,
27525
27998
  .message-supporting-title {
27526
- margin-bottom: 8px;
27999
+ margin-bottom: 6px;
27527
28000
  font-size: 11px;
27528
- letter-spacing: 0.12em;
28001
+ letter-spacing: 0.1em;
27529
28002
  text-transform: uppercase;
27530
- color: #64748b;
28003
+ color: var(--chat-export-muted);
28004
+ }
28005
+
28006
+ .message--mine .reply-context-title {
28007
+ color: rgba(255, 255, 255, 0.8);
27531
28008
  }
27532
28009
 
27533
28010
  .message-supporting-list {
27534
- margin-top: 14px;
27535
- padding-top: 12px;
27536
- border-top: 1px solid #e2e8f0;
28011
+ margin-top: 10px;
28012
+ padding: 10px 14px;
28013
+ border-radius: 10px;
28014
+ background: rgba(15, 23, 42, 0.03);
27537
28015
  }
27538
28016
 
27539
28017
  .message-supporting-list ul {
@@ -27543,18 +28021,14 @@
27543
28021
 
27544
28022
  .message-supporting-meta {
27545
28023
  margin-right: 6px;
27546
- color: #64748b;
28024
+ color: var(--chat-export-muted);
27547
28025
  font-size: 12px;
27548
28026
  }
27549
28027
 
27550
28028
  .message-supporting-excerpt {
27551
- margin-top: 6px;
28029
+ margin-top: 4px;
27552
28030
  color: #475569;
27553
- }
27554
-
27555
- .markdown-content {
27556
- font-size: 15px;
27557
- line-height: 1.65;
28031
+ font-size: 13px;
27558
28032
  }
27559
28033
 
27560
28034
  .markdown-content > *:first-child {
@@ -27582,7 +28056,7 @@
27582
28056
  .markdown-content pre,
27583
28057
  .markdown-content table,
27584
28058
  .markdown-content details {
27585
- margin: 0 0 14px;
28059
+ margin: 0 0 12px;
27586
28060
  }
27587
28061
 
27588
28062
  .markdown-content ul,
@@ -27592,7 +28066,7 @@
27592
28066
 
27593
28067
  .markdown-content code {
27594
28068
  font-family: ui-monospace, "SFMono-Regular", monospace;
27595
- background: #f1f5f9;
28069
+ background: rgba(15, 23, 42, 0.06);
27596
28070
  border-radius: 4px;
27597
28071
  padding: 1px 5px;
27598
28072
  font-size: 0.94em;
@@ -27644,7 +28118,7 @@
27644
28118
 
27645
28119
  .markdown-content details {
27646
28120
  padding: 12px 14px;
27647
- border: 1px solid #e2e8f0;
28121
+ border: 1px solid var(--chat-export-soft-border);
27648
28122
  border-radius: 10px;
27649
28123
  background: #f8fafc;
27650
28124
  }
@@ -27666,7 +28140,7 @@
27666
28140
  }
27667
28141
 
27668
28142
  .markdown-content sup[data-citation-footnote] a {
27669
- color: #0f6cbd;
28143
+ color: inherit;
27670
28144
  text-decoration: none;
27671
28145
  }
27672
28146
 
@@ -27679,14 +28153,14 @@
27679
28153
  border: 1px dashed #cbd5e1;
27680
28154
  border-radius: 14px;
27681
28155
  text-align: center;
27682
- color: #64748b;
27683
- background: #ffffff;
28156
+ color: var(--chat-export-muted);
28157
+ background: var(--chat-export-surface);
27684
28158
  }
27685
28159
 
27686
28160
  .document-sources {
27687
- margin-top: 24px;
27688
- padding-top: 16px;
27689
- border-top: 1px solid #e2e8f0;
28161
+ margin-top: 32px;
28162
+ padding-top: 18px;
28163
+ border-top: 1px solid var(--chat-export-soft-border);
27690
28164
  }
27691
28165
 
27692
28166
  .document-sources-title {
@@ -27712,26 +28186,50 @@
27712
28186
  }
27713
28187
 
27714
28188
  .document-footer {
27715
- margin-top: 24px;
28189
+ margin-top: 28px;
27716
28190
  padding-top: 16px;
27717
- border-top: 1px solid #e2e8f0;
27718
- color: #64748b;
28191
+ border-top: 1px solid var(--chat-export-soft-border);
28192
+ color: var(--chat-export-muted);
27719
28193
  font-size: 12px;
28194
+ text-align: center;
27720
28195
  }
27721
28196
 
27722
28197
  .document-footer strong {
27723
28198
  color: #475569;
27724
28199
  }
27725
28200
 
28201
+ @page {
28202
+ size: Letter;
28203
+ margin: 0.5in;
28204
+ }
28205
+
27726
28206
  @media print {
27727
- body {
28207
+ body,
28208
+ .${CHAT_HTML_EXPORT_RENDER_ROOT_CLASS_NAME} {
27728
28209
  background: #ffffff;
28210
+ font-size: 12pt;
27729
28211
  }
27730
28212
 
27731
28213
  .document {
27732
28214
  max-width: none;
27733
28215
  padding: 0;
27734
28216
  }
28217
+
28218
+ .message {
28219
+ page-break-inside: avoid;
28220
+ break-inside: avoid;
28221
+ }
28222
+
28223
+ .message-bubble {
28224
+ box-shadow: none;
28225
+ }
28226
+
28227
+ .markdown-content pre,
28228
+ .message-supporting-list,
28229
+ .reply-context {
28230
+ page-break-inside: avoid;
28231
+ break-inside: avoid;
28232
+ }
27735
28233
  }
27736
28234
  </style>
27737
28235
  </head>
@@ -28197,7 +28695,11 @@
28197
28695
  */
28198
28696
  function createPromptbookReactExportBrandingComment() {
28199
28697
  const branding = getPromptbookExportBranding();
28200
- return ['/*', ...branding.commentLines.map((line) => ` * ${line}`), ' */'].join('\n');
28698
+ return spacetrim.spaceTrim((block) => `
28699
+ /*
28700
+ ${block(branding.commentLines.map((line) => ` * ${line}`).join('\n'))}
28701
+ */
28702
+ `);
28201
28703
  }
28202
28704
  /**
28203
28705
  * React <jsx/> export plugin (full metadata)
@@ -28740,6 +29242,54 @@
28740
29242
  } }));
28741
29243
  });
28742
29244
 
29245
+ /**
29246
+ * Creates a stable key for one citation label lookup.
29247
+ *
29248
+ * @param citation - Citation metadata.
29249
+ * @returns Stable citation key.
29250
+ *
29251
+ * @private hook helper of `<Chat/>`
29252
+ */
29253
+ function createCitationLabelLookupKey(citation) {
29254
+ return [citation.id, citation.source, citation.url || '', citation.title || '', citation.excerpt || ''].join('\n');
29255
+ }
29256
+ /**
29257
+ * Resolves the best available label for one citation.
29258
+ *
29259
+ * @param citation - Citation metadata.
29260
+ * @param resolveCitationLabel - Optional async host resolver.
29261
+ * @returns Current label, starting with a synchronous fallback and updating when the resolver finishes.
29262
+ *
29263
+ * @private hook of `<Chat/>`
29264
+ */
29265
+ function useResolvedCitationLabel(citation, resolveCitationLabel) {
29266
+ const fallbackLabel = react.useMemo(() => getCitationLabel(citation), [citation]);
29267
+ const citationKey = react.useMemo(() => createCitationLabelLookupKey(citation), [citation]);
29268
+ const [resolvedLabel, setResolvedLabel] = react.useState(null);
29269
+ react.useEffect(() => {
29270
+ setResolvedLabel(null);
29271
+ if (!resolveCitationLabel) {
29272
+ return;
29273
+ }
29274
+ let isCurrent = true;
29275
+ Promise.resolve(resolveCitationLabel(citation))
29276
+ .then((label) => {
29277
+ if (!isCurrent) {
29278
+ return;
29279
+ }
29280
+ const normalizedLabel = label === null || label === void 0 ? void 0 : label.trim();
29281
+ if (normalizedLabel) {
29282
+ setResolvedLabel(normalizedLabel);
29283
+ }
29284
+ })
29285
+ .catch(() => undefined);
29286
+ return () => {
29287
+ isCurrent = false;
29288
+ };
29289
+ }, [citation, citationKey, resolveCitationLabel]);
29290
+ return resolvedLabel || fallbackLabel;
29291
+ }
29292
+
28743
29293
  /**
28744
29294
  * Renders a citation URL preview as an iframe when the target page allows embedding,
28745
29295
  * or falls back to a server-side screenshot with an "Open in new tab" link when it
@@ -28780,6 +29330,15 @@
28780
29330
  return jsxRuntime.jsx("iframe", { src: src, className: styles$7.citationIframe, title: title });
28781
29331
  }
28782
29332
 
29333
+ /**
29334
+ * Empty citation used to keep modal hooks unconditional while the modal is closed.
29335
+ *
29336
+ * @private component constant of `<ChatCitationModal/>`
29337
+ */
29338
+ const EMPTY_MODAL_CITATION = {
29339
+ id: '',
29340
+ source: '',
29341
+ };
28783
29342
  /**
28784
29343
  * Modal that previews a citation source or excerpt.
28785
29344
  *
@@ -28787,7 +29346,9 @@
28787
29346
  */
28788
29347
  function ChatCitationModal(props) {
28789
29348
  var _a, _b;
28790
- const { isOpen, citation, participants, soundSystem, onClose } = props;
29349
+ const { isOpen, citation, participants, resolveCitationLabel, soundSystem, onClose } = props;
29350
+ const resolvedCitation = citation || EMPTY_MODAL_CITATION;
29351
+ const label = useResolvedCitationLabel(resolvedCitation, resolveCitationLabel);
28791
29352
  if (!isOpen || !citation) {
28792
29353
  return null;
28793
29354
  }
@@ -28798,7 +29359,6 @@
28798
29359
  const previewSegment = previewBase.split('/').pop() || previewBase;
28799
29360
  const extension = (_a = previewSegment.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
28800
29361
  const isImage = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'].includes(extension || '');
28801
- const label = getCitationLabel(citation);
28802
29362
  const isTextCitation = isPlainTextCitation(citation);
28803
29363
  const hasTextPreview = !isValidUrl && (citation.excerpt || isTextCitation);
28804
29364
  const textPreviewContent = isTextCitation ? citation.source : (_b = citation.excerpt) !== null && _b !== void 0 ? _b : '';
@@ -30897,21 +31457,34 @@
30897
31457
 
30898
31458
  // Note: [💞] Ignore a discrepancy between file name and entity name
30899
31459
  /**
30900
- * Target frames per second for the shared avatar animation loop.
31460
+ * Target frames per second for a single animated avatar.
30901
31461
  *
30902
- * Animated octopus visuals change slowly enough that 24 fps is indistinguishable
30903
- * from 60 fps in practice, while cutting rendering work by ~60% when multiple
30904
- * avatars are on screen simultaneously.
31462
+ * Animated octopus visuals change slowly enough that ~24 fps per avatar is indistinguishable
31463
+ * from 60 fps in practice. Combined with `MAX_AVATAR_LISTENERS_PER_FRAME` this bounds total
31464
+ * rendering work even when many avatars are visible.
30905
31465
  *
30906
31466
  * @private utility of the avatar rendering system
30907
31467
  */
30908
31468
  const AVATAR_TARGET_FPS = 24;
30909
31469
  /**
30910
- * Minimum elapsed time in milliseconds required between avatar render passes.
31470
+ * Minimum elapsed time in milliseconds required between two consecutive renders of the same avatar.
30911
31471
  *
30912
31472
  * @private utility of the avatar rendering system
30913
31473
  */
30914
31474
  const AVATAR_TARGET_FRAME_INTERVAL_MS = 1000 / AVATAR_TARGET_FPS;
31475
+ /**
31476
+ * Hard cap on how many avatar listeners can run inside the same animation frame.
31477
+ *
31478
+ * The default visual takes several milliseconds to render, so firing every visible avatar
31479
+ * on the same animation frame stalls the main thread for tens of milliseconds and drops the
31480
+ * overall page FPS to single digits. Bounding the number of listeners that fire per frame
31481
+ * spreads the work across animation frames and keeps the UI responsive even with many
31482
+ * visible avatars — each individual avatar still reaches `AVATAR_TARGET_FPS` because work
31483
+ * is interleaved instead of skipped.
31484
+ *
31485
+ * @private utility of the avatar rendering system
31486
+ */
31487
+ const MAX_AVATAR_LISTENERS_PER_FRAME = 2;
30915
31488
  /**
30916
31489
  * Next registration id used by the shared avatar animation scheduler.
30917
31490
  *
@@ -30930,17 +31503,13 @@
30930
31503
  * @private utility of the avatar rendering system
30931
31504
  */
30932
31505
  let avatarAnimationFrameId = null;
30933
- /**
30934
- * Timestamp of the most recently rendered avatar frame.
30935
- *
30936
- * Used to throttle callbacks to `AVATAR_TARGET_FRAME_INTERVAL_MS`.
30937
- *
30938
- * @private utility of the avatar rendering system
30939
- */
30940
- let lastAvatarFrameTime = 0;
30941
31506
  /**
30942
31507
  * Registers one avatar animation callback in the shared animation loop.
30943
31508
  *
31509
+ * Each listener is staggered at registration time so it does not fire on the same animation
31510
+ * frame as the existing listeners, and the shared loop further caps how many listeners run
31511
+ * per frame, keeping the main thread responsive when many avatars are mounted.
31512
+ *
30944
31513
  * @param avatarAnimationListener Frame callback invoked on every animation frame.
30945
31514
  * @returns Cleanup function that unregisters the callback.
30946
31515
  *
@@ -30951,7 +31520,14 @@
30951
31520
  return () => undefined;
30952
31521
  }
30953
31522
  const listenerId = nextAvatarAnimationListenerId++;
30954
- avatarAnimationListeners.set(listenerId, avatarAnimationListener);
31523
+ const existingListenerCount = avatarAnimationListeners.size;
31524
+ const staggerOffsetMs = ((existingListenerCount % Math.max(1, MAX_AVATAR_LISTENERS_PER_FRAME)) * AVATAR_TARGET_FRAME_INTERVAL_MS) /
31525
+ Math.max(1, MAX_AVATAR_LISTENERS_PER_FRAME);
31526
+ avatarAnimationListeners.set(listenerId, {
31527
+ listener: avatarAnimationListener,
31528
+ // Phase the listener so multiple new listeners do not synchronize on the same frame.
31529
+ lastRunAtMs: performance.now() - AVATAR_TARGET_FRAME_INTERVAL_MS + staggerOffsetMs,
31530
+ });
30955
31531
  ensureAvatarAnimationLoop();
30956
31532
  return () => {
30957
31533
  avatarAnimationListeners.delete(listenerId);
@@ -30972,12 +31548,21 @@
30972
31548
  }
30973
31549
  const runFrame = (now) => {
30974
31550
  avatarAnimationFrameId = null;
30975
- if (now - lastAvatarFrameTime >= AVATAR_TARGET_FRAME_INTERVAL_MS) {
30976
- lastAvatarFrameTime = now;
30977
- for (const avatarAnimationListener of [...avatarAnimationListeners.values()]) {
30978
- avatarAnimationListener(now);
31551
+ const dueListenerEntries = [];
31552
+ for (const listenerEntry of avatarAnimationListeners.values()) {
31553
+ if (now - listenerEntry.lastRunAtMs >= AVATAR_TARGET_FRAME_INTERVAL_MS) {
31554
+ dueListenerEntries.push(listenerEntry);
30979
31555
  }
30980
31556
  }
31557
+ // Run the most stale listeners first so each avatar keeps its target FPS even when
31558
+ // total work exceeds the per-frame budget.
31559
+ dueListenerEntries.sort((firstListenerEntry, secondListenerEntry) => firstListenerEntry.lastRunAtMs - secondListenerEntry.lastRunAtMs);
31560
+ const listenersToRunCount = Math.min(dueListenerEntries.length, MAX_AVATAR_LISTENERS_PER_FRAME);
31561
+ for (let listenerIndex = 0; listenerIndex < listenersToRunCount; listenerIndex++) {
31562
+ const listenerEntry = dueListenerEntries[listenerIndex];
31563
+ listenerEntry.lastRunAtMs = now;
31564
+ listenerEntry.listener(now);
31565
+ }
30981
31566
  ensureAvatarAnimationLoop();
30982
31567
  };
30983
31568
  avatarAnimationFrameId = window.requestAnimationFrame(runFrame);
@@ -31681,8 +32266,8 @@
31681
32266
  }, children: [jsxRuntime.jsx("div", { className: styles$4.arrow }), jsxRuntime.jsx(AvatarProfileFromSource, { agentSource: agentSource })] }), document.body);
31682
32267
  });
31683
32268
 
31684
- 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== */";
31685
- 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-"};
32269
+ 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 */";
32270
+ 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-"};
31686
32271
  styleInject(css_248z$3);
31687
32272
 
31688
32273
  /**
@@ -31702,7 +32287,7 @@
31702
32287
  *
31703
32288
  * @private utility of `ChatMessageItem` component
31704
32289
  */
31705
- function SourceChip({ citation, onClick, className, suffix, isCitationIdVisible = true }) {
32290
+ function SourceChip({ citation, onClick, className, suffix, resolveCitationLabel, isCitationIdVisible = true, }) {
31706
32291
  var _a;
31707
32292
  const handleClick = (event) => {
31708
32293
  event.stopPropagation();
@@ -31712,7 +32297,8 @@
31712
32297
  };
31713
32298
  // Keep source chips concise and human-readable for CDN-backed knowledge files.
31714
32299
  const normalizedSource = citation.source.trim();
31715
- const displayName = getCitationLabel(citation);
32300
+ const displayName = useResolvedCitationLabel(citation, resolveCitationLabel);
32301
+ const title = displayName === citation.source ? citation.source : `${displayName}\n${citation.source}`;
31716
32302
  // Get file extension for icon
31717
32303
  const fileExtension = ((_a = (normalizedSource || citation.source).split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || 'file';
31718
32304
  const icon = isPlainTextCitation(citation)
@@ -31720,7 +32306,7 @@
31720
32306
  : isCitationUrl(normalizedSource)
31721
32307
  ? '🌐'
31722
32308
  : getFileIcon(fileExtension);
31723
- return (jsxRuntime.jsxs("button", { className: `${styles$3.sourceChip} ${className || ''}`, onClick: handleClick, title: citation.source, children: [jsxRuntime.jsx("span", { className: styles$3.icon, children: icon }), jsxRuntime.jsxs("span", { className: styles$3.label, children: [displayName, isCitationIdVisible && jsxRuntime.jsxs("span", { className: styles$3.citationId, children: [" [", citation.id, "]"] })] }), suffix && jsxRuntime.jsx("span", { className: styles$3.suffix, children: suffix })] }));
32309
+ return (jsxRuntime.jsxs("button", { className: `${styles$3.sourceChip} ${className || ''}`, onClick: handleClick, title: title, children: [jsxRuntime.jsx("span", { className: styles$3.icon, children: icon }), jsxRuntime.jsxs("span", { className: styles$3.label, children: [jsxRuntime.jsx("span", { className: styles$3.labelText, children: displayName }), isCitationIdVisible && jsxRuntime.jsxs("span", { className: styles$3.citationId, children: [" [", citation.id, "]"] })] }), suffix && jsxRuntime.jsx("span", { className: styles$3.suffix, children: suffix })] }));
31724
32310
  }
31725
32311
  /**
31726
32312
  * Gets an appropriate emoji icon for a file type
@@ -31805,63 +32391,6 @@
31805
32391
  return `${minutes}m ${seconds}s`;
31806
32392
  }
31807
32393
 
31808
- /**
31809
- * Extracts one quick button definition from a markdown link query string.
31810
- *
31811
- * @param text The visible button label.
31812
- * @param query Raw query-string payload captured from markdown.
31813
- * @returns Parsed quick button definition or `null` when the link is not a supported quick button.
31814
- *
31815
- * @private internal helper of `parseMessageButtons`
31816
- */
31817
- function parseQuickButtonDefinition(text, query) {
31818
- const searchParams = new URLSearchParams(query);
31819
- const message = searchParams.get('message');
31820
- if (message !== null) {
31821
- return {
31822
- type: 'message',
31823
- text,
31824
- message,
31825
- };
31826
- }
31827
- const code = searchParams.get('action');
31828
- if (code !== null) {
31829
- return {
31830
- type: 'action',
31831
- text,
31832
- code,
31833
- };
31834
- }
31835
- return null;
31836
- }
31837
- /**
31838
- * Parses markdown quick buttons in the format `[Button Text](?message=...)` or `[Button Text](?action=...)`.
31839
- * Returns both the content without supported quick buttons and the extracted button definitions.
31840
- *
31841
- * @param content The markdown content that may contain buttons
31842
- * @returns Object with contentWithoutButtons and buttons array
31843
- *
31844
- * @public exported from `@promptbook/components`
31845
- */
31846
- function parseMessageButtons(content) {
31847
- const buttonRegex = /\[([^\]]+)\]\(\?([^)]+)\)/g;
31848
- const buttons = [];
31849
- const contentWithoutButtons = content
31850
- .replace(buttonRegex, (match, text, query) => {
31851
- const button = parseQuickButtonDefinition(text, query);
31852
- if (!button) {
31853
- return match;
31854
- }
31855
- buttons.push(button);
31856
- return '';
31857
- })
31858
- .trim();
31859
- return {
31860
- contentWithoutButtons,
31861
- buttons,
31862
- };
31863
- }
31864
-
31865
32394
  /**
31866
32395
  * Searches for the last unclosed code fence using the provided delimiter.
31867
32396
  */
@@ -35333,7 +35862,7 @@
35333
35862
  // <- TODO: [🧠] Should we wrap more components in `React.memo`
35334
35863
  // Or make normal function from this?
35335
35864
  (props) => {
35336
- 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;
35865
+ 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;
35337
35866
  const { isComplete = true,
35338
35867
  // <- TODO: Destruct all `messages` properties like `isComplete`
35339
35868
  } = message;
@@ -35381,6 +35910,10 @@
35381
35910
  content: sanitizedContentWithoutButtons,
35382
35911
  citations: message.citations,
35383
35912
  }), [message.citations, sanitizedContentWithoutButtons]);
35913
+ const enrichCitationForRendering = react.useCallback((citation) => {
35914
+ const previewUrl = resolveCitationPreviewUrl(citation, participants !== null && participants !== void 0 ? participants : []);
35915
+ return previewUrl && !citation.url ? { ...citation, url: previewUrl } : citation;
35916
+ }, [participants]);
35384
35917
  const contentSegments = react.useMemo(() => splitMessageContentIntoSegments(citationFootnoteRenderModel.content), [citationFootnoteRenderModel.content]);
35385
35918
  const hasMapSegment = react.useMemo(() => contentSegments.some((segment) => segment.type === 'map'), [contentSegments]);
35386
35919
  const [localHoveredRating, setLocalHoveredRating] = react.useState(0);
@@ -35652,7 +36185,7 @@
35652
36185
  '--message-bg-color': isAgentArticleMode ? articleModeBackgroundColor : color.toHex(),
35653
36186
  '--message-text-color': isAgentArticleMode ? articleModeTextColor : colorOfText.toHex(),
35654
36187
  '--chat-message-swipe-offset': swipeTranslation,
35655
- }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsxRuntime.jsx("div", { className: classNames(styles$7.replySwipeIndicator, isMe && styles$7.replySwipeIndicatorRight, isReplySwipeArmed && styles$7.replySwipeIndicatorActive), "aria-hidden": "true", children: jsxRuntime.jsx(lucideReact.Reply, { className: styles$7.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsxRuntime.jsx("div", { className: styles$7.voiceCallIndicator, children: jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.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 && (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$7.replyBubblePreview })), jsxRuntime.jsx("div", { ref: contentWithoutButtonsRef, children: jsxRuntime.jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.attachments, children: message.attachments.map((attachment, index) => (jsxRuntime.jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$7.attachment, title: attachment.name, children: [jsxRuntime.jsx("span", { className: styles$7.attachmentIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsx("span", { className: styles$7.attachmentName, children: attachment.name })] }, index))) })), jsxRuntime.jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxRuntime.jsxs("div", { className: styles$7.citationFootnoteItem, children: [jsxRuntime.jsx("span", { className: styles$7.citationFootnoteNumber, children: footnote.number }), jsxRuntime.jsx(SourceChip, { citation: footnote.citation, onClick: onCitationClick, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.sourceCitations, children: transitiveCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, onClick: onCitationClick }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsxRuntime.jsx("div", { className: styles$7.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsxRuntime.jsx("button", { type: "button", className: classNames(styles$7.messageButton, button.type === 'action' && styles$7.actionMessageButton), onClick: (event) => {
36188
+ }, onPointerDown: handleReplyPointerDown, onPointerMove: handleReplyPointerMove, onPointerUp: handleReplyPointerEnd, onPointerCancel: resetReplySwipe, children: [isReplyActionEnabled && (jsxRuntime.jsx("div", { className: classNames(styles$7.replySwipeIndicator, isMe && styles$7.replySwipeIndicatorRight, isReplySwipeArmed && styles$7.replySwipeIndicatorActive), "aria-hidden": "true", children: jsxRuntime.jsx(lucideReact.Reply, { className: styles$7.replySwipeIndicatorIcon }) })), !shouldRenderArticleActionsBar && renderMessageReadAndCopyControls(), message.isVoiceCall && (jsxRuntime.jsx("div", { className: styles$7.voiceCallIndicator, children: jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.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 && (jsxRuntime.jsx(ChatReplyPreview, { label: replyPreviewLabel, senderLabel: replySenderLabel, previewText: replyPreviewText, className: styles$7.replyBubblePreview })), jsxRuntime.jsx("div", { ref: contentWithoutButtonsRef, children: jsxRuntime.jsx(ChatMessageRichContent, { content: message.content, contentSegments: contentSegments, streamingFeaturePlaceholderKind: streamingFeaturePlaceholderKind, onCreateAgent: onCreateAgent, mode: mode }) }), message.attachments && message.attachments.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.attachments, children: message.attachments.map((attachment, index) => (jsxRuntime.jsxs("a", { href: attachment.url, target: "_blank", rel: "noopener noreferrer", className: styles$7.attachment, title: attachment.name, children: [jsxRuntime.jsx("span", { className: styles$7.attachmentIcon, children: "\uD83D\uDCCE" }), jsxRuntime.jsx("span", { className: styles$7.attachmentName, children: attachment.name })] }, index))) })), jsxRuntime.jsx(ChatMessageToolCallChips, { chips: toolCallChips, onToolCallClick: onToolCallClick }), citationFootnoteRenderModel.footnotes.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.citationFootnotes, children: citationFootnoteRenderModel.footnotes.map((footnote) => (jsxRuntime.jsxs("div", { className: styles$7.citationFootnoteItem, children: [jsxRuntime.jsx("span", { className: styles$7.citationFootnoteNumber, children: footnote.number }), jsxRuntime.jsx(SourceChip, { citation: enrichCitationForRendering(footnote.citation), onClick: onCitationClick, resolveCitationLabel: resolveCitationLabel, isCitationIdVisible: false })] }, `citation-footnote-${footnote.number}-${footnote.citation.source}`))) })), transitiveCitations.length > 0 && (jsxRuntime.jsx("div", { className: styles$7.sourceCitations, children: transitiveCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: enrichCitationForRendering(citation), suffix: `by ${citation.origin.label}`, onClick: onCitationClick, resolveCitationLabel: resolveCitationLabel }, `team-source-${citation.source}-${index}`))) })), shouldShowButtons && (jsxRuntime.jsx("div", { className: styles$7.messageButtons, children: renderableButtons.map(({ button, buttonIndex }) => (jsxRuntime.jsx("button", { type: "button", className: classNames(styles$7.messageButton, button.type === 'action' && styles$7.actionMessageButton), onClick: (event) => {
35656
36189
  event.stopPropagation();
35657
36190
  if (button.type === 'message') {
35658
36191
  const quickMessageHandler = onQuickMessageButton || onMessage;
@@ -35758,6 +36291,9 @@
35758
36291
  if (prev.feedbackTranslations !== next.feedbackTranslations) {
35759
36292
  return false;
35760
36293
  }
36294
+ if (prev.resolveCitationLabel !== next.resolveCitationLabel) {
36295
+ return false;
36296
+ }
35761
36297
  if (prev.handleRating !== next.handleRating) {
35762
36298
  return false;
35763
36299
  }
@@ -35786,7 +36322,7 @@
35786
36322
  * @private component of `<Chat/>`
35787
36323
  */
35788
36324
  function ChatMessageList(props) {
35789
- 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;
36325
+ 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;
35790
36326
  const firstMessage = messages[0];
35791
36327
  const firstMsgContent = (firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) || '';
35792
36328
  const firstMsgLines = firstMsgContent.split(/\r?\n/).length;
@@ -35798,7 +36334,7 @@
35798
36334
  const isLastMessage = index === messages.length - 1;
35799
36335
  const isExpanded = expandedMessageId === message.id;
35800
36336
  const currentRating = messageRatings.get(message.id || message.content /* <-[??] */) || 0;
35801
- return (jsxRuntime.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)));
36337
+ return (jsxRuntime.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)));
35802
36338
  }), jsxRuntime.jsx("div", { style: { height: 100 } })] }));
35803
36339
  }
35804
36340
  /**
@@ -39244,11 +39780,11 @@
39244
39780
  * @private component of ChatToolCallModal
39245
39781
  */
39246
39782
  function TeamToolCallSourcesGroup(props) {
39247
- const { teamCitations } = props;
39783
+ const { teamCitations, resolveCitationLabel } = props;
39248
39784
  if (teamCitations.length === 0) {
39249
39785
  return null;
39250
39786
  }
39251
- return (jsxRuntime.jsxs("div", { className: styles$7.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$7.teamToolCallHeading, children: "Sources" }), jsxRuntime.jsx("div", { className: styles$7.teamToolCallChips, children: teamCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}` }, `team-source-${citation.source}-${index}`))) })] }));
39787
+ return (jsxRuntime.jsxs("div", { className: styles$7.teamToolCallGroup, children: [jsxRuntime.jsx("div", { className: styles$7.teamToolCallHeading, children: "Sources" }), jsxRuntime.jsx("div", { className: styles$7.teamToolCallChips, children: teamCitations.map((citation, index) => (jsxRuntime.jsx(SourceChip, { citation: citation, suffix: `by ${citation.origin.label}`, resolveCitationLabel: resolveCitationLabel }, `team-source-${citation.source}-${index}`))) })] }));
39252
39788
  }
39253
39789
  /**
39254
39790
  * Renders the nested action details for the selected teammate tool call.
@@ -39273,12 +39809,12 @@
39273
39809
  * @private component of ChatToolCallModal
39274
39810
  */
39275
39811
  function TeamToolCallSummarySection(props) {
39276
- const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, } = props;
39812
+ const { agentParticipant, buttonColor, onClearSelectedTeamToolCall, onSelectTeamToolCall, selectedTeamToolCall, teamToolCallSummary, toolTitles, resolveCitationLabel, } = props;
39277
39813
  const hasSummaryContent = teamToolCallSummary.toolCalls.length > 0 || teamToolCallSummary.citations.length > 0;
39278
39814
  if (!hasSummaryContent) {
39279
39815
  return null;
39280
39816
  }
39281
- return (jsxRuntime.jsxs("div", { className: styles$7.teamToolCallSection, children: [jsxRuntime.jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsxRuntime.jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations }), jsxRuntime.jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
39817
+ return (jsxRuntime.jsxs("div", { className: styles$7.teamToolCallSection, children: [jsxRuntime.jsx(TeamToolCallActionsGroup, { teamToolCalls: teamToolCallSummary.toolCalls, onSelectTeamToolCall: onSelectTeamToolCall, toolTitles: toolTitles }), jsxRuntime.jsx(TeamToolCallSourcesGroup, { teamCitations: teamToolCallSummary.citations, resolveCitationLabel: resolveCitationLabel }), jsxRuntime.jsx(TeamToolCallDetailsPanel, { selectedTeamToolCall: selectedTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] }));
39282
39818
  }
39283
39819
  /**
39284
39820
  * Renders TEAM conversation details, nested actions, and citations.
@@ -39286,7 +39822,7 @@
39286
39822
  * @private function of ChatToolCallModal
39287
39823
  */
39288
39824
  function TeamToolCallModalContent(options) {
39289
- const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, agentParticipant, buttonColor, } = options;
39825
+ const { teamResult, toolCallDate, teamToolCallSummary, selectedTeamToolCall, onSelectTeamToolCall, onClearSelectedTeamToolCall, teamProfiles, toolTitles, resolveCitationLabel, agentParticipant, buttonColor, } = options;
39290
39826
  const baseTime = toolCallDate ? toolCallDate.getTime() : Date.now();
39291
39827
  const messages = createTeamConversationMessages(teamResult, baseTime);
39292
39828
  const { agentHeader, teammateHeader, participants } = createTeamConversationViewModel({
@@ -39294,7 +39830,7 @@
39294
39830
  teamProfiles,
39295
39831
  agentParticipant,
39296
39832
  });
39297
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxRuntime.jsxs("div", { className: styles$7.searchModalContent, children: [jsxRuntime.jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsxRuntime.jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
39833
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(TeamConversationHeader, { agentHeader: agentHeader, teammateHeader: teammateHeader }), jsxRuntime.jsxs("div", { className: styles$7.searchModalContent, children: [jsxRuntime.jsx(TeamConversationSection, { agentLabel: agentHeader.label, teammateLabel: teammateHeader.label, messages: messages, participants: participants }), jsxRuntime.jsx(TeamToolCallSummarySection, { teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor })] })] }));
39298
39834
  }
39299
39835
 
39300
39836
  /**
@@ -39302,7 +39838,7 @@
39302
39838
  *
39303
39839
  * @private component of `ChatToolCallModal`
39304
39840
  */
39305
- function ChatToolCallModalContent({ agentParticipant, availableTools, buttonColor, chatUiTranslations, focusedToolCall, isAdvancedView, locale, mode, onClearSelectedTeamToolCall, onRequestAdvancedView, onSelectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toolCall, toolCallDate, toolTitles, }) {
39841
+ function ChatToolCallModalContent({ agentParticipant, availableTools, buttonColor, chatUiTranslations, focusedToolCall, isAdvancedView, locale, mode, resolveCitationLabel, onClearSelectedTeamToolCall, onRequestAdvancedView, onSelectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toolCall, toolCallDate, toolTitles, }) {
39306
39842
  if (isAdvancedView) {
39307
39843
  return renderAdvancedToolCallDetails({
39308
39844
  toolCall: focusedToolCall,
@@ -39312,7 +39848,7 @@
39312
39848
  });
39313
39849
  }
39314
39850
  if (teamResult === null || teamResult === void 0 ? void 0 : teamResult.teammate) {
39315
- return (jsxRuntime.jsx(TeamToolCallModalContent, { teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, agentParticipant: agentParticipant, buttonColor: buttonColor }));
39851
+ return (jsxRuntime.jsx(TeamToolCallModalContent, { teamResult: teamResult, toolCallDate: toolCallDate, teamToolCallSummary: teamToolCallSummary, selectedTeamToolCall: selectedTeamToolCall, onSelectTeamToolCall: onSelectTeamToolCall, onClearSelectedTeamToolCall: onClearSelectedTeamToolCall, teamProfiles: teamProfiles, toolTitles: toolTitles, resolveCitationLabel: resolveCitationLabel, agentParticipant: agentParticipant, buttonColor: buttonColor }));
39316
39852
  }
39317
39853
  return renderToolCallDetails({
39318
39854
  toolCall,
@@ -39575,7 +40111,7 @@
39575
40111
  * @private component of `<Chat/>`
39576
40112
  */
39577
40113
  function ChatToolCallModal(props) {
39578
- const { isOpen, toolCall, mode = 'LIGHT', toolCallIdentity, onClose, toolTitles, agentParticipant, buttonColor, teamAgentProfiles, chatUiTranslations, locale, availableTools, } = props;
40114
+ const { isOpen, toolCall, mode = 'LIGHT', toolCallIdentity, onClose, toolTitles, resolveCitationLabel, agentParticipant, buttonColor, teamAgentProfiles, chatUiTranslations, locale, availableTools, } = props;
39579
40115
  const { clearSelectedTeamToolCall, exportAdvancedToolCallReport, focusedToolCall, isAdvancedView, modalDialogRef, openAdvancedView, selectTeamToolCall, selectedTeamToolCall, teamProfiles, teamResult, teamToolCallSummary, toggleViewMode, toolCallDate, } = useChatToolCallModalState({
39580
40116
  isOpen,
39581
40117
  toolCall,
@@ -39597,7 +40133,7 @@
39597
40133
  : (chatUiTranslations === null || chatUiTranslations === void 0 ? void 0 : chatUiTranslations.toolCallModalAdvancedLabel) || 'Advanced';
39598
40134
  return (jsxRuntime.jsx("div", { className: styles$7.ratingModal, "data-chat-modal": "tool-call", "data-chat-theme": mode.toLowerCase(), onClick: (event) => {
39599
40135
  handleModalBackdropClick(event, onClose);
39600
- }, children: jsxRuntime.jsxs("div", { ref: modalDialogRef, className: classNames(styles$7.ratingModalContent, styles$7.toolCallModal), role: "dialog", "aria-modal": "true", "aria-label": modalTitle, tabIndex: -1, children: [jsxRuntime.jsx("button", { type: "button", className: styles$7.modalCloseButton, onClick: onClose, "aria-label": closeButtonLabel, children: jsxRuntime.jsx(CloseIcon, {}) }), jsxRuntime.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 }), jsxRuntime.jsxs("div", { className: styles$7.toolCallModeFooter, children: [isAdvancedView && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
40136
+ }, children: jsxRuntime.jsxs("div", { ref: modalDialogRef, className: classNames(styles$7.ratingModalContent, styles$7.toolCallModal), role: "dialog", "aria-modal": "true", "aria-label": modalTitle, tabIndex: -1, children: [jsxRuntime.jsx("button", { type: "button", className: styles$7.modalCloseButton, onClick: onClose, "aria-label": closeButtonLabel, children: jsxRuntime.jsx(CloseIcon, {}) }), jsxRuntime.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 }), jsxRuntime.jsxs("div", { className: styles$7.toolCallModeFooter, children: [isAdvancedView && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
39601
40137
  void exportAdvancedToolCallReport('clipboard');
39602
40138
  }, children: copyButtonLabel }), jsxRuntime.jsx("button", { type: "button", className: styles$7.toolCallModeButton, onClick: () => {
39603
40139
  void exportAdvancedToolCallReport('file');
@@ -39740,6 +40276,146 @@
39740
40276
  }
39741
40277
  // TODO: [🧠][✌️] Make some Promptbook-native token system
39742
40278
 
40279
+ /**
40280
+ * Pattern for detecting JSON-style Unicode escape sequences in model text.
40281
+ *
40282
+ * @private utility of chat message postprocessing
40283
+ */
40284
+ const JSON_UNICODE_ESCAPE_DETECTION_PATTERN = /\\u[0-9a-fA-F]{4}/;
40285
+ /**
40286
+ * Pattern for decoding JSON-style Unicode escape sequences in model text.
40287
+ *
40288
+ * @private utility of chat message postprocessing
40289
+ */
40290
+ const JSON_UNICODE_ESCAPE_PATTERN = /\\u([0-9a-fA-F]{4})/g;
40291
+ /**
40292
+ * Pattern for detecting fenced markdown code-block boundaries.
40293
+ *
40294
+ * @private utility of chat message postprocessing
40295
+ */
40296
+ const MARKDOWN_CODE_FENCE_PATTERN = /^[ \t]{0,3}(`{3,}|~{3,})/;
40297
+ /**
40298
+ * Minimum code point decoded by the chat Unicode escape normalizer.
40299
+ *
40300
+ * ASCII escapes can be intentional in JSON and security-sensitive examples, so this
40301
+ * helper only decodes non-ASCII characters that were visibly broken in chat text.
40302
+ *
40303
+ * @private utility of chat message postprocessing
40304
+ */
40305
+ const FIRST_NON_ASCII_CODE_POINT = 0x80;
40306
+ /**
40307
+ * Decodes JSON-style Unicode escape sequences in markdown prose while preserving code.
40308
+ *
40309
+ * Model/tool output can occasionally arrive as already JSON-escaped text, which makes
40310
+ * natural-language replies render `\u00fd` instead of the decoded character. This
40311
+ * keeps code examples intact by skipping fenced code blocks and inline code spans.
40312
+ *
40313
+ * @param markdown - Markdown chat content to normalize for display.
40314
+ * @returns Markdown chat content with non-ASCII JSON Unicode escapes decoded.
40315
+ *
40316
+ * @private internal utility of `<Chat/>`
40317
+ */
40318
+ function decodeJsonUnicodeEscapesInMarkdownText(markdown) {
40319
+ var _a, _b;
40320
+ if (!JSON_UNICODE_ESCAPE_DETECTION_PATTERN.test(markdown)) {
40321
+ return markdown;
40322
+ }
40323
+ const parts = markdown.split(/(\r\n|\r|\n)/);
40324
+ let fenceState = null;
40325
+ let normalizedMarkdown = '';
40326
+ for (let index = 0; index < parts.length; index += 2) {
40327
+ const line = (_a = parts[index]) !== null && _a !== void 0 ? _a : '';
40328
+ const newline = (_b = parts[index + 1]) !== null && _b !== void 0 ? _b : '';
40329
+ const fenceBoundary = resolveMarkdownFenceBoundary(line);
40330
+ if (fenceState) {
40331
+ normalizedMarkdown += line + newline;
40332
+ if (fenceBoundary &&
40333
+ fenceBoundary.marker === fenceState.marker &&
40334
+ fenceBoundary.length >= fenceState.length) {
40335
+ fenceState = null;
40336
+ }
40337
+ continue;
40338
+ }
40339
+ if (fenceBoundary) {
40340
+ fenceState = fenceBoundary;
40341
+ normalizedMarkdown += line + newline;
40342
+ continue;
40343
+ }
40344
+ normalizedMarkdown += decodeJsonUnicodeEscapesOutsideInlineCode(line) + newline;
40345
+ }
40346
+ return normalizedMarkdown;
40347
+ }
40348
+ /**
40349
+ * Resolves one markdown code-fence boundary from a line.
40350
+ *
40351
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40352
+ */
40353
+ function resolveMarkdownFenceBoundary(line) {
40354
+ const match = line.match(MARKDOWN_CODE_FENCE_PATTERN);
40355
+ const fenceMarker = match === null || match === void 0 ? void 0 : match[1];
40356
+ if (!fenceMarker) {
40357
+ return null;
40358
+ }
40359
+ return {
40360
+ marker: fenceMarker[0],
40361
+ length: fenceMarker.length,
40362
+ };
40363
+ }
40364
+ /**
40365
+ * Decodes Unicode escapes in one markdown line while skipping inline code spans.
40366
+ *
40367
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40368
+ */
40369
+ function decodeJsonUnicodeEscapesOutsideInlineCode(line) {
40370
+ let normalizedLine = '';
40371
+ let cursor = 0;
40372
+ while (cursor < line.length) {
40373
+ const openingDelimiterStart = line.indexOf('`', cursor);
40374
+ if (openingDelimiterStart === -1) {
40375
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor));
40376
+ break;
40377
+ }
40378
+ const openingDelimiterEnd = findBacktickRunEnd(line, openingDelimiterStart);
40379
+ const openingDelimiter = line.slice(openingDelimiterStart, openingDelimiterEnd);
40380
+ const closingDelimiterStart = line.indexOf(openingDelimiter, openingDelimiterEnd);
40381
+ normalizedLine += decodeJsonUnicodeEscapesInText(line.slice(cursor, openingDelimiterStart));
40382
+ if (closingDelimiterStart === -1) {
40383
+ normalizedLine += line.slice(openingDelimiterStart);
40384
+ break;
40385
+ }
40386
+ const closingDelimiterEnd = closingDelimiterStart + openingDelimiter.length;
40387
+ normalizedLine += line.slice(openingDelimiterStart, closingDelimiterEnd);
40388
+ cursor = closingDelimiterEnd;
40389
+ }
40390
+ return normalizedLine;
40391
+ }
40392
+ /**
40393
+ * Finds the first character after one contiguous run of backticks.
40394
+ *
40395
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40396
+ */
40397
+ function findBacktickRunEnd(line, startIndex) {
40398
+ let cursor = startIndex;
40399
+ while (cursor < line.length && line[cursor] === '`') {
40400
+ cursor++;
40401
+ }
40402
+ return cursor;
40403
+ }
40404
+ /**
40405
+ * Decodes non-ASCII JSON Unicode escapes in plain text.
40406
+ *
40407
+ * @private utility of `decodeJsonUnicodeEscapesInMarkdownText`
40408
+ */
40409
+ function decodeJsonUnicodeEscapesInText(text) {
40410
+ return text.replace(JSON_UNICODE_ESCAPE_PATTERN, (match, hexadecimalCodePoint) => {
40411
+ const codePoint = Number.parseInt(hexadecimalCodePoint, 16);
40412
+ if (codePoint < FIRST_NON_ASCII_CODE_POINT) {
40413
+ return match;
40414
+ }
40415
+ return String.fromCharCode(codePoint);
40416
+ });
40417
+ }
40418
+
39743
40419
  /**
39744
40420
  * Applies the same text normalization that Chat historically performed inline.
39745
40421
  *
@@ -39753,15 +40429,24 @@
39753
40429
  const normalizedReplyingTo = messageWithCitations.replyingTo
39754
40430
  ? {
39755
40431
  ...messageWithCitations.replyingTo,
39756
- content: promptbookifyAiText(humanizeAiText(messageWithCitations.replyingTo.content)),
40432
+ content: postprocessChatMessageContent(messageWithCitations.replyingTo.content),
39757
40433
  }
39758
40434
  : undefined;
39759
40435
  return {
39760
40436
  ...messageWithCitations,
39761
- content: promptbookifyAiText(humanizeAiText(messageWithCitations.content)),
40437
+ content: postprocessChatMessageContent(messageWithCitations.content),
39762
40438
  ...(normalizedReplyingTo ? { replyingTo: normalizedReplyingTo } : {}),
39763
40439
  };
39764
40440
  }
40441
+ /**
40442
+ * Applies markdown-safe text normalization to one chat message content value.
40443
+ *
40444
+ * @private utility of `useChatPostprocessedMessages`
40445
+ */
40446
+ function postprocessChatMessageContent(content) {
40447
+ const decodedContent = decodeJsonUnicodeEscapesInMarkdownText(content);
40448
+ return promptbookifyAiText(humanizeAiText(decodedContent));
40449
+ }
39765
40450
  /**
39766
40451
  * Memoizes the render-only message normalization used by `<Chat/>`.
39767
40452
  *
@@ -40573,7 +41258,7 @@
40573
41258
  * @public exported from `@promptbook/components`
40574
41259
  */
40575
41260
  function Chat(props) {
40576
- 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;
41261
+ 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;
40577
41262
  const buttonColor = react.useMemo(() => Color.from(buttonColorRaw || '#0066cc'), [buttonColorRaw]);
40578
41263
  const agentParticipant = react.useMemo(() => participants.find((participant) => participant.name === 'AGENT'), [participants]);
40579
41264
  const postprocessedMessages = useChatPostprocessedMessages({
@@ -40618,7 +41303,7 @@
40618
41303
  useChatCompleteNotification(messages, soundSystem);
40619
41304
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [feedbackStatus && (jsxRuntime.jsx("div", { className: classNames(styles$7.feedbackStatus, feedbackStatus.variant === 'success'
40620
41305
  ? styles$7.feedbackStatusSuccess
40621
- : styles$7.feedbackStatusError), "aria-live": "polite", role: "status", children: feedbackStatus.message })), effectConfigs && effectConfigs.length > 0 && (jsxRuntime.jsx(ChatEffectsSystem, { messages: postprocessedMessages, effectConfigs: effectConfigs, soundSystem: soundSystem })), jsxRuntime.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: jsxRuntime.jsxs("div", { className: classNames(className, styles$7.chatMainFlow, getChatCssClassName('chatMainFlow'), chatCssClassNames.chatMainFlow), children: [children && jsxRuntime.jsx("div", { className: classNames(styles$7.chatChildren), children: children }), shouldShowScrollToBottom && (jsxRuntime.jsx("div", { className: styles$7.scrollToBottomContainer, children: jsxRuntime.jsxs("div", { className: styles$7.scrollToBottomWrapper, children: [jsxRuntime.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 && (jsxRuntime.jsx("span", { className: styles$7.scrollToBottomBadge, "aria-live": "polite", role: "status", children: badgeLabel }))] }) })), isVoiceCalling && (jsxRuntime.jsx("div", { className: styles$7.voiceCallIndicatorBar, children: jsxRuntime.jsxs("div", { className: styles$7.voiceCallIndicator, children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.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" }) }), jsxRuntime.jsx("span", { children: "Voice call active" }), jsxRuntime.jsx("div", { className: styles$7.voiceCallPulse })] }) })), jsxRuntime.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 }), jsxRuntime.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 && (jsxRuntime.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) }))] }) }), jsxRuntime.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 }), jsxRuntime.jsx(ChatCitationModal, { isOpen: citationModalOpen, citation: selectedCitation, participants: participants, soundSystem: soundSystem, onClose: closeCitationModal }), jsxRuntime.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 })] }));
41306
+ : styles$7.feedbackStatusError), "aria-live": "polite", role: "status", children: feedbackStatus.message })), effectConfigs && effectConfigs.length > 0 && (jsxRuntime.jsx(ChatEffectsSystem, { messages: postprocessedMessages, effectConfigs: effectConfigs, soundSystem: soundSystem })), jsxRuntime.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: jsxRuntime.jsxs("div", { className: classNames(className, styles$7.chatMainFlow, getChatCssClassName('chatMainFlow'), chatCssClassNames.chatMainFlow), children: [children && jsxRuntime.jsx("div", { className: classNames(styles$7.chatChildren), children: children }), shouldShowScrollToBottom && (jsxRuntime.jsx("div", { className: styles$7.scrollToBottomContainer, children: jsxRuntime.jsxs("div", { className: styles$7.scrollToBottomWrapper, children: [jsxRuntime.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 && (jsxRuntime.jsx("span", { className: styles$7.scrollToBottomBadge, "aria-live": "polite", role: "status", children: badgeLabel }))] }) })), isVoiceCalling && (jsxRuntime.jsx("div", { className: styles$7.voiceCallIndicatorBar, children: jsxRuntime.jsxs("div", { className: styles$7.voiceCallIndicator, children: [jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", children: jsxRuntime.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" }) }), jsxRuntime.jsx("span", { children: "Voice call active" }), jsxRuntime.jsx("div", { className: styles$7.voiceCallPulse })] }) })), jsxRuntime.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 }), jsxRuntime.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 && (jsxRuntime.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) }))] }) }), jsxRuntime.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 }), jsxRuntime.jsx(ChatCitationModal, { isOpen: citationModalOpen, citation: selectedCitation, participants: participants, resolveCitationLabel: resolveCitationLabel, soundSystem: soundSystem, onClose: closeCitationModal }), jsxRuntime.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 })] }));
40622
41307
  }
40623
41308
 
40624
41309
  /**
@@ -48625,11 +49310,19 @@
48625
49310
  try {
48626
49311
  const json = JSON.parse(content);
48627
49312
  const formattedJson = JSON.stringify(json, null, 4);
48628
- return `\`\`\`json\n${formattedJson}\n\`\`\``;
49313
+ return spacetrim.spaceTrim((block) => `
49314
+ \`\`\`json
49315
+ ${block(formattedJson)}
49316
+ \`\`\`
49317
+ `);
48629
49318
  }
48630
49319
  catch (error) {
48631
49320
  // If JSON is invalid, still import it but maybe not as pretty JSON
48632
- return `\`\`\`json\n${content}\n\`\`\``;
49321
+ return spacetrim.spaceTrim((block) => `
49322
+ \`\`\`json
49323
+ ${block(content)}
49324
+ \`\`\`
49325
+ `);
48633
49326
  }
48634
49327
  },
48635
49328
  };
@@ -48653,7 +49346,11 @@
48653
49346
  import(content, mimeType) {
48654
49347
  const extension = mimeTypeToExtension(mimeType);
48655
49348
  const codeBlockType = extension || 'txt';
48656
- return `\`\`\`${codeBlockType}\n${content}\n\`\`\``;
49349
+ return spacetrim.spaceTrim((block) => `
49350
+ \`\`\`${codeBlockType}
49351
+ ${block(content)}
49352
+ \`\`\`
49353
+ `);
48657
49354
  },
48658
49355
  };
48659
49356
 
@@ -48900,11 +49597,19 @@
48900
49597
  const examples = [];
48901
49598
  const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
48902
49599
  if (initialMessage) {
48903
- examples.push(`**Agent:**\n${initialMessage}`);
49600
+ examples.push(spacetrim.spaceTrim((block) => `
49601
+ **Agent:**
49602
+ ${block(initialMessage)}
49603
+ `));
48904
49604
  }
48905
49605
  if (samples && samples.length > 0) {
48906
49606
  for (const sample of samples) {
48907
- examples.push(`**User:** ${sample.question}\n\n**Agent:**\n${sample.answer}`);
49607
+ examples.push(spacetrim.spaceTrim((block) => `
49608
+ **User:** ${block(String(sample.question))}
49609
+
49610
+ **Agent:**
49611
+ ${block(sample.answer)}
49612
+ `));
48908
49613
  }
48909
49614
  }
48910
49615
  return examples;
@@ -55450,7 +56155,11 @@
55450
56155
  };
55451
56156
  }
55452
56157
  return {
55453
- content: `${content.slice(0, Math.max(0, maxCharacters))}\n\n[...truncated...]`,
56158
+ content: spacetrim.spaceTrim((block) => `
56159
+ ${block(content.slice(0, Math.max(0, maxCharacters)))}
56160
+
56161
+ [...truncated...]
56162
+ `),
55454
56163
  isTruncated: true,
55455
56164
  };
55456
56165
  }