conversation-replay 0.1.13 → 0.1.15
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.
- package/README.md +43 -19
- package/dist/cli.js +755 -84
- package/package.json +2 -1
- package/schema.json +292 -0
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
3
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
20
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
21
|
|
|
@@ -7270,7 +7286,7 @@ import { writeFile } from "node:fs/promises";
|
|
|
7270
7286
|
// package.json
|
|
7271
7287
|
var package_default = {
|
|
7272
7288
|
name: "conversation-replay",
|
|
7273
|
-
version: "0.1.
|
|
7289
|
+
version: "0.1.15",
|
|
7274
7290
|
description: "Create annotated replays of text conversations",
|
|
7275
7291
|
main: "dist/cli.js",
|
|
7276
7292
|
type: "module",
|
|
@@ -7279,6 +7295,7 @@ var package_default = {
|
|
|
7279
7295
|
},
|
|
7280
7296
|
files: [
|
|
7281
7297
|
"dist",
|
|
7298
|
+
"schema.json",
|
|
7282
7299
|
"README.md"
|
|
7283
7300
|
],
|
|
7284
7301
|
scripts: {
|
|
@@ -7431,21 +7448,107 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7431
7448
|
const radiusLg = cornerStyle === "straight" ? "0" : "12px";
|
|
7432
7449
|
const blurAmount = initialBlur ?? 1;
|
|
7433
7450
|
const tabCss = hasMultipleScenarios ? `
|
|
7434
|
-
/* Tabs -
|
|
7451
|
+
/* Tabs - horizontal scrolling with arrow navigation */
|
|
7452
|
+
.tabs-wrapper {
|
|
7453
|
+
position: relative;
|
|
7454
|
+
margin-left: 12px;
|
|
7455
|
+
margin-right: 12px;
|
|
7456
|
+
margin-bottom: -1px;
|
|
7457
|
+
overflow: hidden;
|
|
7458
|
+
}
|
|
7459
|
+
|
|
7460
|
+
.tab-scroll-btn {
|
|
7461
|
+
position: absolute;
|
|
7462
|
+
top: 0;
|
|
7463
|
+
bottom: 1px;
|
|
7464
|
+
width: 32px;
|
|
7465
|
+
border: none;
|
|
7466
|
+
background: transparent;
|
|
7467
|
+
cursor: pointer;
|
|
7468
|
+
z-index: 4;
|
|
7469
|
+
display: none;
|
|
7470
|
+
align-items: center;
|
|
7471
|
+
justify-content: center;
|
|
7472
|
+
padding: 0;
|
|
7473
|
+
color: var(--text-primary);
|
|
7474
|
+
transition: color 0.2s ease;
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7477
|
+
.tab-scroll-btn:hover {
|
|
7478
|
+
color: var(--accent);
|
|
7479
|
+
}
|
|
7480
|
+
|
|
7481
|
+
.tab-scroll-btn svg {
|
|
7482
|
+
width: 16px;
|
|
7483
|
+
height: 16px;
|
|
7484
|
+
fill: currentColor;
|
|
7485
|
+
/* Glow effect makes icon visible on any background */
|
|
7486
|
+
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 1))
|
|
7487
|
+
drop-shadow(0 0 4px rgba(255, 255, 255, 0.8))
|
|
7488
|
+
drop-shadow(0 0 6px rgba(255, 255, 255, 0.6));
|
|
7489
|
+
}
|
|
7490
|
+
|
|
7491
|
+
:root[data-theme="dark"] .tab-scroll-btn svg {
|
|
7492
|
+
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.8))
|
|
7493
|
+
drop-shadow(0 0 4px rgba(0, 0, 0, 0.6));
|
|
7494
|
+
}
|
|
7495
|
+
|
|
7496
|
+
.tab-scroll-btn.left {
|
|
7497
|
+
left: 0;
|
|
7498
|
+
}
|
|
7499
|
+
|
|
7500
|
+
.tab-scroll-btn.right {
|
|
7501
|
+
right: 0;
|
|
7502
|
+
}
|
|
7503
|
+
|
|
7504
|
+
/* Use mask-image to fade tabs at edges - works on any background */
|
|
7505
|
+
/* Fade zone is larger so text is fully transparent before reaching the chevron */
|
|
7506
|
+
.tabs-wrapper.can-scroll-right .tabs {
|
|
7507
|
+
-webkit-mask-image: linear-gradient(to right, black 0%, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7508
|
+
mask-image: linear-gradient(to right, black 0%, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7509
|
+
}
|
|
7510
|
+
|
|
7511
|
+
.tabs-wrapper.can-scroll-left .tabs {
|
|
7512
|
+
-webkit-mask-image: linear-gradient(to left, black 0%, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7513
|
+
mask-image: linear-gradient(to left, black 0%, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7514
|
+
}
|
|
7515
|
+
|
|
7516
|
+
.tabs-wrapper.can-scroll-left.can-scroll-right .tabs {
|
|
7517
|
+
-webkit-mask-image: linear-gradient(to right, transparent 0%, transparent 20px, black 60px, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7518
|
+
mask-image: linear-gradient(to right, transparent 0%, transparent 20px, black 60px, black calc(100% - 60px), transparent calc(100% - 20px), transparent 100%);
|
|
7519
|
+
}
|
|
7520
|
+
|
|
7521
|
+
.tabs-wrapper.can-scroll-left .tab-scroll-btn.left {
|
|
7522
|
+
display: flex;
|
|
7523
|
+
}
|
|
7524
|
+
|
|
7525
|
+
.tabs-wrapper.can-scroll-right .tab-scroll-btn.right {
|
|
7526
|
+
display: flex;
|
|
7527
|
+
}
|
|
7528
|
+
|
|
7435
7529
|
.tabs {
|
|
7436
7530
|
display: flex;
|
|
7437
7531
|
gap: 0;
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7532
|
+
flex-wrap: nowrap;
|
|
7533
|
+
overflow-x: auto;
|
|
7534
|
+
overflow-y: hidden;
|
|
7535
|
+
-webkit-overflow-scrolling: touch;
|
|
7536
|
+
scroll-snap-type: x mandatory;
|
|
7537
|
+
scroll-behavior: smooth;
|
|
7538
|
+
scrollbar-width: none;
|
|
7539
|
+
-ms-overflow-style: none;
|
|
7441
7540
|
position: relative;
|
|
7442
7541
|
z-index: 2;
|
|
7443
7542
|
}
|
|
7444
7543
|
|
|
7544
|
+
.tabs::-webkit-scrollbar {
|
|
7545
|
+
display: none;
|
|
7546
|
+
}
|
|
7547
|
+
|
|
7445
7548
|
.tab {
|
|
7446
7549
|
padding: 10px 20px;
|
|
7447
7550
|
border: 1px solid transparent;
|
|
7448
|
-
border-bottom:
|
|
7551
|
+
border-bottom: 1px solid var(--border-color);
|
|
7449
7552
|
background: transparent;
|
|
7450
7553
|
color: var(--tab-inactive-color, var(--text-secondary));
|
|
7451
7554
|
border-radius: var(--radius) var(--radius) 0 0;
|
|
@@ -7453,6 +7556,9 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7453
7556
|
font-size: 13px;
|
|
7454
7557
|
font-weight: 500;
|
|
7455
7558
|
transition: all 0.2s;
|
|
7559
|
+
flex-shrink: 0;
|
|
7560
|
+
scroll-snap-align: start;
|
|
7561
|
+
white-space: nowrap;
|
|
7456
7562
|
}
|
|
7457
7563
|
|
|
7458
7564
|
.tab:hover {
|
|
@@ -7463,50 +7569,32 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7463
7569
|
background: var(--bg-chat);
|
|
7464
7570
|
color: var(--text-primary);
|
|
7465
7571
|
border-color: var(--border-color);
|
|
7572
|
+
border-bottom-color: var(--bg-chat);
|
|
7466
7573
|
position: relative;
|
|
7467
7574
|
}
|
|
7468
7575
|
|
|
7469
|
-
.tab.active::after {
|
|
7470
|
-
content: '';
|
|
7471
|
-
position: absolute;
|
|
7472
|
-
bottom: -1px;
|
|
7473
|
-
left: 0;
|
|
7474
|
-
right: 0;
|
|
7475
|
-
height: 2px;
|
|
7476
|
-
background: var(--bg-chat);
|
|
7477
|
-
}
|
|
7478
|
-
|
|
7479
7576
|
@media (max-width: 600px) {
|
|
7577
|
+
.tabs-wrapper {
|
|
7578
|
+
margin-left: 12px;
|
|
7579
|
+
margin-right: 12px;
|
|
7580
|
+
}
|
|
7581
|
+
|
|
7480
7582
|
.tabs {
|
|
7481
|
-
|
|
7482
|
-
overflow: hidden; /* No scrolling - tabs will truncate */
|
|
7483
|
-
padding: 0 12px; /* Align with canvas border-radius */
|
|
7484
|
-
margin-left: 0;
|
|
7583
|
+
padding: 0;
|
|
7485
7584
|
}
|
|
7486
7585
|
|
|
7487
7586
|
.tab {
|
|
7488
|
-
padding: 10px
|
|
7587
|
+
padding: 10px 16px;
|
|
7489
7588
|
font-size: 12px;
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7589
|
+
min-width: 100px;
|
|
7590
|
+
max-width: 150px;
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
.tab-scroll-btn {
|
|
7594
|
+
width: 28px;
|
|
7496
7595
|
}
|
|
7497
7596
|
}
|
|
7498
|
-
|
|
7499
|
-
/* In iframe, ensure non-active tabs are visible against potentially dark/complex backgrounds */
|
|
7500
|
-
body.in-iframe .tab:not(.active) {
|
|
7501
|
-
background: rgba(255, 255, 255, 0.5);
|
|
7502
|
-
backdrop-filter: blur(4px);
|
|
7503
|
-
-webkit-backdrop-filter: blur(4px);
|
|
7504
|
-
}
|
|
7505
|
-
|
|
7506
|
-
:root[data-theme="dark"] body.in-iframe .tab:not(.active) {
|
|
7507
|
-
background: rgba(30, 41, 59, 0.5);
|
|
7508
|
-
color: rgba(248, 250, 252, 0.7);
|
|
7509
|
-
}
|
|
7597
|
+
|
|
7510
7598
|
` : "";
|
|
7511
7599
|
return `
|
|
7512
7600
|
*, *::before, *::after {
|
|
@@ -7601,6 +7689,11 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7601
7689
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3);
|
|
7602
7690
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
|
|
7603
7691
|
}
|
|
7692
|
+
|
|
7693
|
+
.tab-scroll-btn svg {
|
|
7694
|
+
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.8))
|
|
7695
|
+
drop-shadow(0 0 4px rgba(0, 0, 0, 0.6));
|
|
7696
|
+
}
|
|
7604
7697
|
}
|
|
7605
7698
|
|
|
7606
7699
|
/* Dark mode via data-theme attribute (for iframe sync with parent) */
|
|
@@ -7792,7 +7885,7 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7792
7885
|
display: flex;
|
|
7793
7886
|
flex-direction: column;
|
|
7794
7887
|
background: transparent !important;
|
|
7795
|
-
padding: 0
|
|
7888
|
+
padding: 0;
|
|
7796
7889
|
margin: 0 !important;
|
|
7797
7890
|
border: none !important;
|
|
7798
7891
|
box-shadow: none !important;
|
|
@@ -7813,17 +7906,14 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7813
7906
|
max-height: none !important; /* Remove height constraint in iframe */
|
|
7814
7907
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) !important; /* Subtle shadow in iframe */
|
|
7815
7908
|
background: var(--bg-chat) !important; /* Ensure opaque background in iframe */
|
|
7816
|
-
margin-bottom: clamp(70px, 15vh, 120px); /* Responsive space for fixed controls */
|
|
7817
7909
|
}
|
|
7818
7910
|
|
|
7819
7911
|
body.in-iframe .controls-wrapper {
|
|
7820
|
-
position:
|
|
7821
|
-
|
|
7822
|
-
left: 0;
|
|
7823
|
-
right: 0;
|
|
7912
|
+
position: relative;
|
|
7913
|
+
flex-shrink: 0;
|
|
7824
7914
|
display: flex;
|
|
7825
7915
|
justify-content: center;
|
|
7826
|
-
|
|
7916
|
+
padding: 12px 0;
|
|
7827
7917
|
z-index: 20;
|
|
7828
7918
|
}
|
|
7829
7919
|
|
|
@@ -7833,6 +7923,8 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7833
7923
|
flex-direction: column;
|
|
7834
7924
|
gap: 20px;
|
|
7835
7925
|
padding-bottom: 80px; /* Space for floating controls */
|
|
7926
|
+
padding-left: 32px; /* Symmetric with right */
|
|
7927
|
+
padding-right: 32px; /* Clearance for timer circle */
|
|
7836
7928
|
}
|
|
7837
7929
|
|
|
7838
7930
|
.play-overlay {
|
|
@@ -7920,8 +8012,7 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7920
8012
|
font-weight: 600;
|
|
7921
8013
|
letter-spacing: 0.02em;
|
|
7922
8014
|
margin-bottom: 6px;
|
|
7923
|
-
color: var(--text-
|
|
7924
|
-
opacity: 0.8;
|
|
8015
|
+
color: var(--text-secondary);
|
|
7925
8016
|
display: flex;
|
|
7926
8017
|
align-items: center;
|
|
7927
8018
|
gap: 6px;
|
|
@@ -7999,7 +8090,7 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
7999
8090
|
width: 100%;
|
|
8000
8091
|
margin: 24px 0;
|
|
8001
8092
|
padding: 0 0 0 20px;
|
|
8002
|
-
font-size:
|
|
8093
|
+
font-size: 16px;
|
|
8003
8094
|
font-style: italic;
|
|
8004
8095
|
color: var(--annotation-text);
|
|
8005
8096
|
opacity: 0;
|
|
@@ -8012,10 +8103,9 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
8012
8103
|
left: 0;
|
|
8013
8104
|
top: 2px;
|
|
8014
8105
|
bottom: 2px;
|
|
8015
|
-
width:
|
|
8106
|
+
width: 4px;
|
|
8016
8107
|
background: var(--annotation-border);
|
|
8017
|
-
border-radius:
|
|
8018
|
-
opacity: 0.5;
|
|
8108
|
+
border-radius: 2px;
|
|
8019
8109
|
}
|
|
8020
8110
|
|
|
8021
8111
|
.annotation.visible {
|
|
@@ -8025,7 +8115,7 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
8025
8115
|
}
|
|
8026
8116
|
|
|
8027
8117
|
.annotation-label {
|
|
8028
|
-
font-size:
|
|
8118
|
+
font-size: 11px;
|
|
8029
8119
|
font-weight: 700;
|
|
8030
8120
|
text-transform: uppercase;
|
|
8031
8121
|
letter-spacing: 0.05em;
|
|
@@ -8035,7 +8125,6 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
8035
8125
|
display: flex;
|
|
8036
8126
|
align-items: center;
|
|
8037
8127
|
gap: 6px;
|
|
8038
|
-
opacity: 0.8;
|
|
8039
8128
|
}
|
|
8040
8129
|
|
|
8041
8130
|
.annotation-content {
|
|
@@ -8354,10 +8443,6 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
8354
8443
|
min-height: 250px;
|
|
8355
8444
|
}
|
|
8356
8445
|
|
|
8357
|
-
body.in-iframe .chat-container {
|
|
8358
|
-
max-height: 400px;
|
|
8359
|
-
}
|
|
8360
|
-
|
|
8361
8446
|
.controls {
|
|
8362
8447
|
gap: 8px;
|
|
8363
8448
|
padding-top: 12px;
|
|
@@ -8416,20 +8501,37 @@ function generateCss(theme, hasMultipleScenarios, colors, cornerStyle, initialBl
|
|
|
8416
8501
|
}
|
|
8417
8502
|
|
|
8418
8503
|
.controls {
|
|
8419
|
-
flex-wrap:
|
|
8420
|
-
|
|
8504
|
+
flex-wrap: nowrap;
|
|
8505
|
+
gap: 4px;
|
|
8506
|
+
padding: 6px 8px;
|
|
8507
|
+
}
|
|
8508
|
+
|
|
8509
|
+
.controls .divider {
|
|
8510
|
+
display: none; /* Hide dividers to save space */
|
|
8421
8511
|
}
|
|
8422
8512
|
|
|
8423
8513
|
.control-btn {
|
|
8424
|
-
|
|
8514
|
+
width: 36px;
|
|
8515
|
+
height: 36px;
|
|
8516
|
+
min-width: 36px;
|
|
8517
|
+
min-height: 36px;
|
|
8518
|
+
padding: 0;
|
|
8425
8519
|
}
|
|
8426
8520
|
|
|
8427
8521
|
.control-btn.primary {
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8522
|
+
width: 40px;
|
|
8523
|
+
height: 40px;
|
|
8524
|
+
}
|
|
8525
|
+
|
|
8526
|
+
.speed-select {
|
|
8527
|
+
padding: 6px 8px;
|
|
8528
|
+
min-width: 32px;
|
|
8529
|
+
font-size: 12px;
|
|
8530
|
+
}
|
|
8531
|
+
|
|
8532
|
+
.progress {
|
|
8533
|
+
font-size: 11px;
|
|
8534
|
+
min-width: 36px;
|
|
8433
8535
|
}
|
|
8434
8536
|
}
|
|
8435
8537
|
|
|
@@ -8606,6 +8708,7 @@ function generateJs(demo, timerStyle) {
|
|
|
8606
8708
|
var isPaused = false;
|
|
8607
8709
|
var animationTimeout = null;
|
|
8608
8710
|
var speed = 1;
|
|
8711
|
+
var currentStepBaseDelay = 0; // Base delay before speed adjustment (for mid-playback speed changes)
|
|
8609
8712
|
var hasStarted = false;
|
|
8610
8713
|
|
|
8611
8714
|
// DOM elements
|
|
@@ -8620,6 +8723,8 @@ function generateJs(demo, timerStyle) {
|
|
|
8620
8723
|
var timerElement = document.getElementById('timer-element');
|
|
8621
8724
|
var chatContainer = document.getElementById('chat-container');
|
|
8622
8725
|
var tabs = document.querySelectorAll('.tab');
|
|
8726
|
+
var tabsContainer = document.querySelector('.tabs');
|
|
8727
|
+
var tabsWrapper = document.querySelector('.tabs-wrapper');
|
|
8623
8728
|
var infoBtn = document.getElementById('info-btn');
|
|
8624
8729
|
var infoPopover = document.getElementById('info-popover');
|
|
8625
8730
|
|
|
@@ -8788,7 +8893,7 @@ function generateJs(demo, timerStyle) {
|
|
|
8788
8893
|
|
|
8789
8894
|
if (step.footnote) {
|
|
8790
8895
|
var footnote = document.createElement('p');
|
|
8791
|
-
footnote.style.cssText = 'margin: 8px 0 0 0; font-style: italic; font-size:
|
|
8896
|
+
footnote.style.cssText = 'margin: 8px 0 0 0; font-style: italic; font-size: 15px; color: var(--text-secondary);';
|
|
8792
8897
|
footnote.textContent = step.footnote;
|
|
8793
8898
|
contentWrapper.appendChild(footnote);
|
|
8794
8899
|
}
|
|
@@ -8946,6 +9051,9 @@ function generateJs(demo, timerStyle) {
|
|
|
8946
9051
|
playOverlay.classList.add('hidden');
|
|
8947
9052
|
chatContainer.classList.remove('fading');
|
|
8948
9053
|
|
|
9054
|
+
// Ensure new active tab is visible (not in fade zone)
|
|
9055
|
+
scrollActiveTabIntoView();
|
|
9056
|
+
|
|
8949
9057
|
// Auto-play immediately
|
|
8950
9058
|
play();
|
|
8951
9059
|
}, 300);
|
|
@@ -8989,6 +9097,7 @@ function generateJs(demo, timerStyle) {
|
|
|
8989
9097
|
|
|
8990
9098
|
if (currentStepIndex < steps.length) {
|
|
8991
9099
|
var delay = calculateDelay(step);
|
|
9100
|
+
currentStepBaseDelay = delay * speed; // Store base delay for mid-playback speed changes
|
|
8992
9101
|
startTimer(delay);
|
|
8993
9102
|
animationTimeout = setTimeout(showNextStep, delay);
|
|
8994
9103
|
} else {
|
|
@@ -9013,7 +9122,16 @@ function generateJs(demo, timerStyle) {
|
|
|
9013
9122
|
|
|
9014
9123
|
var steps = getSteps();
|
|
9015
9124
|
var firstStep = steps[0];
|
|
9125
|
+
|
|
9126
|
+
// Show first step if not already visible (e.g., during auto-advance)
|
|
9127
|
+
if (chatMessages.children.length === 0) {
|
|
9128
|
+
var element = createStepElement(firstStep);
|
|
9129
|
+
element.classList.add('visible');
|
|
9130
|
+
chatMessages.appendChild(element);
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9016
9133
|
var delay = calculateDelay(firstStep);
|
|
9134
|
+
currentStepBaseDelay = delay * speed; // Store base delay for mid-playback speed changes
|
|
9017
9135
|
currentStepIndex = 1;
|
|
9018
9136
|
|
|
9019
9137
|
startTimer(delay);
|
|
@@ -9044,6 +9162,7 @@ function generateJs(demo, timerStyle) {
|
|
|
9044
9162
|
updateButtonStates();
|
|
9045
9163
|
|
|
9046
9164
|
var delay = calculateDelay(steps[0]);
|
|
9165
|
+
currentStepBaseDelay = delay * speed; // Store base delay for mid-playback speed changes
|
|
9047
9166
|
startTimer(delay);
|
|
9048
9167
|
animationTimeout = setTimeout(showNextStep, delay);
|
|
9049
9168
|
return;
|
|
@@ -9143,23 +9262,26 @@ function generateJs(demo, timerStyle) {
|
|
|
9143
9262
|
} else {
|
|
9144
9263
|
// Always show initial preview to render the first message
|
|
9145
9264
|
showInitialPreview();
|
|
9146
|
-
|
|
9265
|
+
|
|
9147
9266
|
if (wasPlaying) {
|
|
9148
9267
|
// If we were playing, continue playing (this will hide the overlay again)
|
|
9149
9268
|
play();
|
|
9150
9269
|
} else {
|
|
9151
|
-
// If we weren't playing, ensure the overlay is hidden?
|
|
9270
|
+
// If we weren't playing, ensure the overlay is hidden?
|
|
9152
9271
|
// The original comment said "keep overlay hidden" but showInitialPreview shows it.
|
|
9153
|
-
// If we pause, we usually show the play button?
|
|
9272
|
+
// If we pause, we usually show the play button?
|
|
9154
9273
|
// Actually, standard behavior is usually to show the play button on a fresh tab.
|
|
9155
9274
|
// But strictly following the intention of "keep overlay hidden" would mean adding it back?
|
|
9156
9275
|
// Let's stick to standard behavior: new tab = reset state = show play button.
|
|
9157
|
-
// showInitialPreview() handles that.
|
|
9158
|
-
|
|
9276
|
+
// showInitialPreview() handles that.
|
|
9277
|
+
|
|
9159
9278
|
// However, check if we want to suppress it:
|
|
9160
9279
|
// "User can click play button or canvas to start"
|
|
9161
9280
|
}
|
|
9162
9281
|
}
|
|
9282
|
+
|
|
9283
|
+
// Ensure active tab is fully visible (not obscured by scroll buttons)
|
|
9284
|
+
scrollActiveTabIntoView();
|
|
9163
9285
|
}, 300);
|
|
9164
9286
|
}
|
|
9165
9287
|
|
|
@@ -9236,7 +9358,29 @@ function generateJs(demo, timerStyle) {
|
|
|
9236
9358
|
}
|
|
9237
9359
|
|
|
9238
9360
|
speedSelect.addEventListener('change', function(e) {
|
|
9361
|
+
var oldSpeed = speed;
|
|
9239
9362
|
speed = parseFloat(e.target.value);
|
|
9363
|
+
|
|
9364
|
+
// If playing, recalculate and reschedule with new speed
|
|
9365
|
+
if (isPlaying && !isPaused && animationTimeout && currentStepBaseDelay > 0) {
|
|
9366
|
+
var elapsed = Date.now() - timerStartTime;
|
|
9367
|
+
var elapsedBase = elapsed * oldSpeed; // Content time consumed
|
|
9368
|
+
var remainingBase = currentStepBaseDelay - elapsedBase;
|
|
9369
|
+
|
|
9370
|
+
if (remainingBase > 0) {
|
|
9371
|
+
// Cancel current timeout (but keep timer animation running)
|
|
9372
|
+
clearTimeout(animationTimeout);
|
|
9373
|
+
|
|
9374
|
+
// Adjust timer variables so animation continues from current position
|
|
9375
|
+
// progress = elapsed / timerDuration should remain the same
|
|
9376
|
+
timerDuration = currentStepBaseDelay / speed;
|
|
9377
|
+
timerStartTime = Date.now() - (elapsedBase / speed);
|
|
9378
|
+
|
|
9379
|
+
// Reschedule with remaining time at new speed
|
|
9380
|
+
var remainingTime = remainingBase / speed;
|
|
9381
|
+
animationTimeout = setTimeout(showNextStep, remainingTime);
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9240
9384
|
});
|
|
9241
9385
|
|
|
9242
9386
|
// Handle page visibility changes (pause timer animation when tab is hidden)
|
|
@@ -9249,8 +9393,78 @@ function generateJs(demo, timerStyle) {
|
|
|
9249
9393
|
});
|
|
9250
9394
|
}
|
|
9251
9395
|
|
|
9396
|
+
// Scroll indicators for mobile tabs
|
|
9397
|
+
function updateTabScrollIndicators() {
|
|
9398
|
+
if (!tabsContainer || !tabsWrapper) return;
|
|
9399
|
+
|
|
9400
|
+
var scrollLeft = tabsContainer.scrollLeft;
|
|
9401
|
+
var maxScroll = tabsContainer.scrollWidth - tabsContainer.clientWidth;
|
|
9402
|
+
var threshold = 2; // Account for sub-pixel rendering
|
|
9403
|
+
|
|
9404
|
+
if (scrollLeft > threshold) {
|
|
9405
|
+
tabsWrapper.classList.add('can-scroll-left');
|
|
9406
|
+
} else {
|
|
9407
|
+
tabsWrapper.classList.remove('can-scroll-left');
|
|
9408
|
+
}
|
|
9409
|
+
|
|
9410
|
+
if (scrollLeft < maxScroll - threshold) {
|
|
9411
|
+
tabsWrapper.classList.add('can-scroll-right');
|
|
9412
|
+
} else {
|
|
9413
|
+
tabsWrapper.classList.remove('can-scroll-right');
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
|
|
9417
|
+
// Auto-scroll to ensure active tab is fully visible (outside the fade zone)
|
|
9418
|
+
function scrollActiveTabIntoView() {
|
|
9419
|
+
if (!tabsContainer) return;
|
|
9420
|
+
var activeTab = tabsContainer.querySelector('.tab.active');
|
|
9421
|
+
if (!activeTab) return;
|
|
9422
|
+
|
|
9423
|
+
var containerRect = tabsContainer.getBoundingClientRect();
|
|
9424
|
+
var tabRect = activeTab.getBoundingClientRect();
|
|
9425
|
+
var fadeZone = 70; // Must be larger than CSS mask fade zone (60px) to keep active tab fully opaque
|
|
9426
|
+
|
|
9427
|
+
// Check if tab is cut off on the right
|
|
9428
|
+
if (tabRect.right > containerRect.right - fadeZone) {
|
|
9429
|
+
tabsContainer.scrollBy({
|
|
9430
|
+
left: tabRect.right - containerRect.right + fadeZone + 8,
|
|
9431
|
+
behavior: 'smooth'
|
|
9432
|
+
});
|
|
9433
|
+
}
|
|
9434
|
+
// Check if tab is cut off on the left
|
|
9435
|
+
else if (tabRect.left < containerRect.left + fadeZone) {
|
|
9436
|
+
tabsContainer.scrollBy({
|
|
9437
|
+
left: tabRect.left - containerRect.left - fadeZone - 8,
|
|
9438
|
+
behavior: 'smooth'
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
9441
|
+
}
|
|
9442
|
+
|
|
9443
|
+
if (tabsContainer && tabsWrapper) {
|
|
9444
|
+
tabsContainer.addEventListener('scroll', updateTabScrollIndicators, { passive: true });
|
|
9445
|
+
window.addEventListener('resize', updateTabScrollIndicators, { passive: true });
|
|
9446
|
+
|
|
9447
|
+
// Arrow button click handlers
|
|
9448
|
+
var scrollLeftBtn = tabsWrapper.querySelector('.tab-scroll-btn.left');
|
|
9449
|
+
var scrollRightBtn = tabsWrapper.querySelector('.tab-scroll-btn.right');
|
|
9450
|
+
var scrollAmount = 200; // pixels to scroll per click
|
|
9451
|
+
|
|
9452
|
+
if (scrollLeftBtn) {
|
|
9453
|
+
scrollLeftBtn.addEventListener('click', function() {
|
|
9454
|
+
tabsContainer.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
|
|
9455
|
+
});
|
|
9456
|
+
}
|
|
9457
|
+
|
|
9458
|
+
if (scrollRightBtn) {
|
|
9459
|
+
scrollRightBtn.addEventListener('click', function() {
|
|
9460
|
+
tabsContainer.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
9461
|
+
});
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
|
|
9252
9465
|
// Initialize
|
|
9253
9466
|
updateTabStates();
|
|
9467
|
+
updateTabScrollIndicators();
|
|
9254
9468
|
showInitialPreview();
|
|
9255
9469
|
|
|
9256
9470
|
if (prefersReducedMotion) {
|
|
@@ -9271,9 +9485,17 @@ function generateTabsHtml(demo) {
|
|
|
9271
9485
|
}).join(`
|
|
9272
9486
|
`);
|
|
9273
9487
|
return `
|
|
9274
|
-
<
|
|
9275
|
-
|
|
9276
|
-
|
|
9488
|
+
<div class="tabs-wrapper">
|
|
9489
|
+
<button class="tab-scroll-btn left" aria-label="Scroll tabs left">
|
|
9490
|
+
<svg viewBox="0 0 24 24"><path d="M18.41 7.41L17 6l-6 6 6 6 1.41-1.41L13.83 12z"/><path d="M12.41 7.41L11 6l-6 6 6 6 1.41-1.41L7.83 12z"/></svg>
|
|
9491
|
+
</button>
|
|
9492
|
+
<nav class="tabs" role="tablist">
|
|
9493
|
+
${tabButtons}
|
|
9494
|
+
</nav>
|
|
9495
|
+
<button class="tab-scroll-btn right" aria-label="Scroll tabs right">
|
|
9496
|
+
<svg viewBox="0 0 24 24"><path d="M5.59 7.41L7 6l6 6-6 6-1.41-1.41L10.17 12z"/><path d="M11.59 7.41L13 6l6 6-6 6-1.41-1.41L16.17 12z"/></svg>
|
|
9497
|
+
</button>
|
|
9498
|
+
</div>
|
|
9277
9499
|
`;
|
|
9278
9500
|
}
|
|
9279
9501
|
function generateHtml(demo, options = { outputPath: "" }) {
|
|
@@ -9391,6 +9613,394 @@ async function buildDemo(demo, outputPath, options = {}) {
|
|
|
9391
9613
|
await writeFile(outputPath, html, "utf-8");
|
|
9392
9614
|
}
|
|
9393
9615
|
|
|
9616
|
+
// src/schema.ts
|
|
9617
|
+
function generateTemplate(theme) {
|
|
9618
|
+
const selectedTheme = theme || "chat";
|
|
9619
|
+
return `# Conversation Replay - Demo Configuration
|
|
9620
|
+
# Documentation: https://github.com/lennyzeltser/conversation-replay
|
|
9621
|
+
#
|
|
9622
|
+
# Run: conversation-replay build this-file.yaml -o output.html
|
|
9623
|
+
# Validate: conversation-replay validate this-file.yaml
|
|
9624
|
+
|
|
9625
|
+
meta:
|
|
9626
|
+
title: "My Demo Title" # Required: shown in header
|
|
9627
|
+
description: "A brief description" # Optional: shown below title
|
|
9628
|
+
theme: ${selectedTheme} # chat | email | slack | terminal | generic
|
|
9629
|
+
# articleUrl: "/related-article" # Optional: "View Article" link
|
|
9630
|
+
# annotationLabel: "Behind the Scenes" # Label for annotation steps
|
|
9631
|
+
# autoAdvance: true # Auto-play next scenario when current ends
|
|
9632
|
+
|
|
9633
|
+
# Timer and visual style
|
|
9634
|
+
# timerStyle: circle # circle | bar
|
|
9635
|
+
# cornerStyle: rounded # rounded | straight
|
|
9636
|
+
# initialBlur: 1 # Blur amount for play overlay (px)
|
|
9637
|
+
|
|
9638
|
+
# Custom colors (all optional)
|
|
9639
|
+
# colors:
|
|
9640
|
+
# accent: "#4f46e5" # Buttons, links (Indigo 600)
|
|
9641
|
+
# pageBg: "#f8fafc" # Page background (Slate 50)
|
|
9642
|
+
# canvasBg: "#ffffff" # Chat container background
|
|
9643
|
+
# leftBg: "#eef2ff" # Left bubble background (Indigo 50)
|
|
9644
|
+
# leftBorder: "transparent" # Left bubble border
|
|
9645
|
+
# rightBg: "#f1f5f9" # Right bubble background (Slate 100)
|
|
9646
|
+
# rightBorder: "transparent" # Right bubble border
|
|
9647
|
+
# annotationText: "#475569" # Annotation text (Slate 600)
|
|
9648
|
+
# annotationBorder: "#cbd5e1" # Annotation accent bar (Slate 300)
|
|
9649
|
+
|
|
9650
|
+
# Timing configuration (all in milliseconds)
|
|
9651
|
+
# speed:
|
|
9652
|
+
# minDelay: 3000 # Minimum delay between steps
|
|
9653
|
+
# maxDelay: 8000 # Maximum delay between steps
|
|
9654
|
+
# msPerWord: 200 # Reading time per word
|
|
9655
|
+
# annotationMultiplier: 1.15 # Extra time for annotations
|
|
9656
|
+
# upNextDelay: 2500 # "Up Next" display time
|
|
9657
|
+
|
|
9658
|
+
scenarios:
|
|
9659
|
+
- id: main
|
|
9660
|
+
title: "Scenario Title" # Shown in tab (if multiple scenarios)
|
|
9661
|
+
|
|
9662
|
+
participants:
|
|
9663
|
+
- id: user1
|
|
9664
|
+
label: "Person A"
|
|
9665
|
+
role: left # Messages appear on left
|
|
9666
|
+
- id: user2
|
|
9667
|
+
label: "Person B"
|
|
9668
|
+
role: right # Messages appear on right
|
|
9669
|
+
|
|
9670
|
+
steps:
|
|
9671
|
+
# Message from a participant
|
|
9672
|
+
- type: message
|
|
9673
|
+
from: user1 # Must match a participant id
|
|
9674
|
+
content: "Hello! This is a sample message."
|
|
9675
|
+
|
|
9676
|
+
- type: message
|
|
9677
|
+
from: user2
|
|
9678
|
+
content: "Hi there! Here's a response."
|
|
9679
|
+
# footnote: "Optional italic footnote below the message"
|
|
9680
|
+
|
|
9681
|
+
# Message with code block
|
|
9682
|
+
# - type: message
|
|
9683
|
+
# from: user2
|
|
9684
|
+
# content: "Here's some code:"
|
|
9685
|
+
# codeBlock: |
|
|
9686
|
+
# function example() {
|
|
9687
|
+
# return "hello";
|
|
9688
|
+
# }
|
|
9689
|
+
|
|
9690
|
+
# Annotation - educational callout with accent bar
|
|
9691
|
+
- type: annotation
|
|
9692
|
+
content: "This annotation explains something to the viewer."
|
|
9693
|
+
|
|
9694
|
+
# Transition - centered scene break
|
|
9695
|
+
# - type: transition
|
|
9696
|
+
# content: "Later that day..."
|
|
9697
|
+
|
|
9698
|
+
# Additional scenarios appear as tabs
|
|
9699
|
+
# - id: part2
|
|
9700
|
+
# title: "Part Two"
|
|
9701
|
+
# participants:
|
|
9702
|
+
# - id: analyst
|
|
9703
|
+
# label: "Analyst"
|
|
9704
|
+
# role: left
|
|
9705
|
+
# steps:
|
|
9706
|
+
# - type: message
|
|
9707
|
+
# from: analyst
|
|
9708
|
+
# content: "This is another scenario."
|
|
9709
|
+
`;
|
|
9710
|
+
}
|
|
9711
|
+
var jsonSchema = {
|
|
9712
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
9713
|
+
$id: "https://github.com/lennyzeltser/conversation-replay/schema.json",
|
|
9714
|
+
title: "Conversation Replay Demo",
|
|
9715
|
+
description: "Schema for conversation-replay YAML configuration files",
|
|
9716
|
+
type: "object",
|
|
9717
|
+
required: ["meta", "scenarios"],
|
|
9718
|
+
properties: {
|
|
9719
|
+
meta: {
|
|
9720
|
+
type: "object",
|
|
9721
|
+
description: "Demo metadata and configuration",
|
|
9722
|
+
required: ["title"],
|
|
9723
|
+
properties: {
|
|
9724
|
+
title: {
|
|
9725
|
+
type: "string",
|
|
9726
|
+
description: "Demo title shown in header",
|
|
9727
|
+
minLength: 1
|
|
9728
|
+
},
|
|
9729
|
+
description: {
|
|
9730
|
+
type: "string",
|
|
9731
|
+
description: "Brief description shown below title"
|
|
9732
|
+
},
|
|
9733
|
+
theme: {
|
|
9734
|
+
type: "string",
|
|
9735
|
+
enum: ["chat", "email", "slack", "terminal", "generic"],
|
|
9736
|
+
description: "Visual theme for message styling",
|
|
9737
|
+
default: "chat"
|
|
9738
|
+
},
|
|
9739
|
+
articleUrl: {
|
|
9740
|
+
type: "string",
|
|
9741
|
+
description: "URL for 'View Article' link in header"
|
|
9742
|
+
},
|
|
9743
|
+
hideHeaderInIframe: {
|
|
9744
|
+
type: "boolean",
|
|
9745
|
+
description: "Auto-hide header when embedded in iframe",
|
|
9746
|
+
default: true
|
|
9747
|
+
},
|
|
9748
|
+
autoAdvance: {
|
|
9749
|
+
type: "boolean",
|
|
9750
|
+
description: "Auto-play next scenario when current one completes"
|
|
9751
|
+
},
|
|
9752
|
+
annotationLabel: {
|
|
9753
|
+
type: "string",
|
|
9754
|
+
description: "Label shown above annotation content",
|
|
9755
|
+
default: "Behind the Scenes"
|
|
9756
|
+
},
|
|
9757
|
+
timerStyle: {
|
|
9758
|
+
type: "string",
|
|
9759
|
+
enum: ["bar", "circle"],
|
|
9760
|
+
description: "Countdown timer display style",
|
|
9761
|
+
default: "circle"
|
|
9762
|
+
},
|
|
9763
|
+
cornerStyle: {
|
|
9764
|
+
type: "string",
|
|
9765
|
+
enum: ["rounded", "straight"],
|
|
9766
|
+
description: "Border radius style for containers and bubbles",
|
|
9767
|
+
default: "rounded"
|
|
9768
|
+
},
|
|
9769
|
+
initialBlur: {
|
|
9770
|
+
type: "number",
|
|
9771
|
+
description: "Blur amount in pixels for play overlay",
|
|
9772
|
+
default: 1,
|
|
9773
|
+
minimum: 0
|
|
9774
|
+
},
|
|
9775
|
+
colors: {
|
|
9776
|
+
type: "object",
|
|
9777
|
+
description: "Custom color overrides",
|
|
9778
|
+
properties: {
|
|
9779
|
+
accent: { type: "string", description: "Primary accent color (buttons, links)" },
|
|
9780
|
+
pageBg: { type: "string", description: "Page background color" },
|
|
9781
|
+
canvasBg: { type: "string", description: "Chat container background" },
|
|
9782
|
+
leftBg: { type: "string", description: "Left participant bubble background" },
|
|
9783
|
+
leftBorder: { type: "string", description: "Left participant bubble border" },
|
|
9784
|
+
rightBg: { type: "string", description: "Right participant bubble background" },
|
|
9785
|
+
rightBorder: { type: "string", description: "Right participant bubble border" },
|
|
9786
|
+
tabInactiveColor: { type: "string", description: "Inactive tab text color" },
|
|
9787
|
+
annotationText: { type: "string", description: "Annotation text color" },
|
|
9788
|
+
annotationBorder: { type: "string", description: "Annotation accent bar color" }
|
|
9789
|
+
}
|
|
9790
|
+
},
|
|
9791
|
+
speed: {
|
|
9792
|
+
type: "object",
|
|
9793
|
+
description: "Timing configuration for playback",
|
|
9794
|
+
properties: {
|
|
9795
|
+
minDelay: { type: "number", description: "Minimum delay between steps (ms)", default: 3000 },
|
|
9796
|
+
maxDelay: { type: "number", description: "Maximum delay between steps (ms)", default: 8000 },
|
|
9797
|
+
msPerWord: { type: "number", description: "Milliseconds per word for reading time", default: 200 },
|
|
9798
|
+
annotationMultiplier: { type: "number", description: "Time multiplier for annotations", default: 1.15 },
|
|
9799
|
+
upNextDelay: { type: "number", description: "'Up Next' display duration (ms)", default: 2500 }
|
|
9800
|
+
}
|
|
9801
|
+
}
|
|
9802
|
+
}
|
|
9803
|
+
},
|
|
9804
|
+
scenarios: {
|
|
9805
|
+
type: "array",
|
|
9806
|
+
description: "One or more conversation scenarios (shown as tabs if multiple)",
|
|
9807
|
+
minItems: 1,
|
|
9808
|
+
items: {
|
|
9809
|
+
type: "object",
|
|
9810
|
+
required: ["id", "title", "participants", "steps"],
|
|
9811
|
+
properties: {
|
|
9812
|
+
id: {
|
|
9813
|
+
type: "string",
|
|
9814
|
+
description: "Unique identifier for this scenario",
|
|
9815
|
+
minLength: 1
|
|
9816
|
+
},
|
|
9817
|
+
title: {
|
|
9818
|
+
type: "string",
|
|
9819
|
+
description: "Display title (shown in tab)",
|
|
9820
|
+
minLength: 1
|
|
9821
|
+
},
|
|
9822
|
+
participants: {
|
|
9823
|
+
type: "array",
|
|
9824
|
+
description: "People/entities in this conversation",
|
|
9825
|
+
minItems: 1,
|
|
9826
|
+
items: {
|
|
9827
|
+
type: "object",
|
|
9828
|
+
required: ["id", "label"],
|
|
9829
|
+
properties: {
|
|
9830
|
+
id: {
|
|
9831
|
+
type: "string",
|
|
9832
|
+
description: "Unique identifier (referenced in message 'from' field)"
|
|
9833
|
+
},
|
|
9834
|
+
label: {
|
|
9835
|
+
type: "string",
|
|
9836
|
+
description: "Display name shown above messages"
|
|
9837
|
+
},
|
|
9838
|
+
role: {
|
|
9839
|
+
type: "string",
|
|
9840
|
+
enum: ["left", "right"],
|
|
9841
|
+
description: "Which side messages appear on",
|
|
9842
|
+
default: "left"
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
}
|
|
9846
|
+
},
|
|
9847
|
+
steps: {
|
|
9848
|
+
type: "array",
|
|
9849
|
+
description: "Sequence of messages, annotations, and transitions",
|
|
9850
|
+
minItems: 1,
|
|
9851
|
+
items: {
|
|
9852
|
+
oneOf: [
|
|
9853
|
+
{
|
|
9854
|
+
type: "object",
|
|
9855
|
+
description: "A message from a participant",
|
|
9856
|
+
required: ["type", "from", "content"],
|
|
9857
|
+
properties: {
|
|
9858
|
+
type: { const: "message" },
|
|
9859
|
+
from: { type: "string", description: "Participant ID" },
|
|
9860
|
+
content: { type: "string", description: "Message text" },
|
|
9861
|
+
codeBlock: { type: "string", description: "Optional code/preformatted text" },
|
|
9862
|
+
footnote: { type: "string", description: "Optional italic footnote" }
|
|
9863
|
+
}
|
|
9864
|
+
},
|
|
9865
|
+
{
|
|
9866
|
+
type: "object",
|
|
9867
|
+
description: "An educational annotation/callout",
|
|
9868
|
+
required: ["type", "content"],
|
|
9869
|
+
properties: {
|
|
9870
|
+
type: { const: "annotation" },
|
|
9871
|
+
content: { type: "string", description: "Annotation text" }
|
|
9872
|
+
}
|
|
9873
|
+
},
|
|
9874
|
+
{
|
|
9875
|
+
type: "object",
|
|
9876
|
+
description: "A scene break/transition",
|
|
9877
|
+
required: ["type", "content"],
|
|
9878
|
+
properties: {
|
|
9879
|
+
type: { const: "transition" },
|
|
9880
|
+
content: { type: "string", description: "Transition text (e.g., 'Later that day...')" }
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
]
|
|
9884
|
+
}
|
|
9885
|
+
}
|
|
9886
|
+
}
|
|
9887
|
+
}
|
|
9888
|
+
}
|
|
9889
|
+
}
|
|
9890
|
+
};
|
|
9891
|
+
function getSchemaReference(section) {
|
|
9892
|
+
if (section === "meta") {
|
|
9893
|
+
return `
|
|
9894
|
+
META OPTIONS
|
|
9895
|
+
============
|
|
9896
|
+
|
|
9897
|
+
Required:
|
|
9898
|
+
title: string Demo title shown in header
|
|
9899
|
+
|
|
9900
|
+
Optional:
|
|
9901
|
+
description: string Brief description below title
|
|
9902
|
+
theme: string chat | email | slack | terminal | generic (default: chat)
|
|
9903
|
+
articleUrl: string URL for "View Article" link
|
|
9904
|
+
annotationLabel: string Label for annotations (default: "Behind the Scenes")
|
|
9905
|
+
autoAdvance: boolean Auto-play next scenario when current ends
|
|
9906
|
+
hideHeaderInIframe: bool Auto-hide header when embedded (default: true)
|
|
9907
|
+
timerStyle: string circle | bar (default: circle)
|
|
9908
|
+
cornerStyle: string rounded | straight (default: rounded)
|
|
9909
|
+
initialBlur: number Play overlay blur in px (default: 1)
|
|
9910
|
+
colors: object Custom color overrides (see: schema colors)
|
|
9911
|
+
speed: object Timing configuration (see: schema speed)
|
|
9912
|
+
`;
|
|
9913
|
+
}
|
|
9914
|
+
if (section === "colors") {
|
|
9915
|
+
return `
|
|
9916
|
+
COLOR OPTIONS (meta.colors)
|
|
9917
|
+
===========================
|
|
9918
|
+
|
|
9919
|
+
All colors are optional CSS color values (hex, rgb, named colors, etc.)
|
|
9920
|
+
|
|
9921
|
+
accent: string Buttons, links (default: #4f46e5)
|
|
9922
|
+
pageBg: string Page background (default: #f8fafc)
|
|
9923
|
+
canvasBg: string Chat container background (default: #ffffff)
|
|
9924
|
+
leftBg: string Left bubble background (default: #eef2ff)
|
|
9925
|
+
leftBorder: string Left bubble border (default: transparent)
|
|
9926
|
+
rightBg: string Right bubble background (default: #f1f5f9)
|
|
9927
|
+
rightBorder: string Right bubble border (default: transparent)
|
|
9928
|
+
tabInactiveColor: string Inactive tab text (default: #94a3b8)
|
|
9929
|
+
annotationText: string Annotation text (default: #475569)
|
|
9930
|
+
annotationBorder: string Annotation accent bar (default: #cbd5e1)
|
|
9931
|
+
`;
|
|
9932
|
+
}
|
|
9933
|
+
if (section === "speed") {
|
|
9934
|
+
return `
|
|
9935
|
+
SPEED OPTIONS (meta.speed)
|
|
9936
|
+
==========================
|
|
9937
|
+
|
|
9938
|
+
All values are in milliseconds.
|
|
9939
|
+
|
|
9940
|
+
minDelay: number Minimum delay between steps (default: 3000)
|
|
9941
|
+
maxDelay: number Maximum delay between steps (default: 8000)
|
|
9942
|
+
msPerWord: number Reading time per word (default: 200)
|
|
9943
|
+
annotationMultiplier: num Extra time multiplier for annotations (default: 1.15)
|
|
9944
|
+
upNextDelay: number "Up Next" indicator duration (default: 2500)
|
|
9945
|
+
`;
|
|
9946
|
+
}
|
|
9947
|
+
if (section === "steps") {
|
|
9948
|
+
return `
|
|
9949
|
+
STEP TYPES
|
|
9950
|
+
==========
|
|
9951
|
+
|
|
9952
|
+
MESSAGE - A chat bubble from a participant
|
|
9953
|
+
type: message (required)
|
|
9954
|
+
from: string Participant ID (required)
|
|
9955
|
+
content: string Message text (required)
|
|
9956
|
+
codeBlock: string Optional preformatted code block
|
|
9957
|
+
footnote: string Optional italic footnote
|
|
9958
|
+
|
|
9959
|
+
ANNOTATION - Educational callout with accent bar
|
|
9960
|
+
type: annotation (required)
|
|
9961
|
+
content: string Annotation text (required)
|
|
9962
|
+
|
|
9963
|
+
TRANSITION - Centered scene break
|
|
9964
|
+
type: transition (required)
|
|
9965
|
+
content: string Transition text (required)
|
|
9966
|
+
`;
|
|
9967
|
+
}
|
|
9968
|
+
return `
|
|
9969
|
+
CONVERSATION REPLAY SCHEMA
|
|
9970
|
+
==========================
|
|
9971
|
+
|
|
9972
|
+
STRUCTURE
|
|
9973
|
+
---------
|
|
9974
|
+
meta: Demo configuration (see: schema meta)
|
|
9975
|
+
scenarios: Array of conversation scenarios
|
|
9976
|
+
|
|
9977
|
+
SCENARIO
|
|
9978
|
+
--------
|
|
9979
|
+
id: string Unique identifier (required)
|
|
9980
|
+
title: string Display title for tab (required)
|
|
9981
|
+
participants: array People in conversation (required)
|
|
9982
|
+
steps: array Message/annotation sequence (required)
|
|
9983
|
+
|
|
9984
|
+
PARTICIPANT
|
|
9985
|
+
-----------
|
|
9986
|
+
id: string Unique ID, used in message 'from' field (required)
|
|
9987
|
+
label: string Display name shown above messages (required)
|
|
9988
|
+
role: string left | right (default: left)
|
|
9989
|
+
|
|
9990
|
+
STEP TYPES
|
|
9991
|
+
----------
|
|
9992
|
+
message Chat bubble (requires: from, content)
|
|
9993
|
+
annotation Educational callout (requires: content)
|
|
9994
|
+
transition Scene break (requires: content)
|
|
9995
|
+
|
|
9996
|
+
Use 'schema <section>' for details:
|
|
9997
|
+
schema meta Meta/configuration options
|
|
9998
|
+
schema colors Color customization
|
|
9999
|
+
schema speed Timing configuration
|
|
10000
|
+
schema steps Step type details
|
|
10001
|
+
`;
|
|
10002
|
+
}
|
|
10003
|
+
|
|
9394
10004
|
// src/cli.ts
|
|
9395
10005
|
var HELP = `
|
|
9396
10006
|
conversation-replay - Create annotated replays of text conversations from YAML
|
|
@@ -9398,22 +10008,31 @@ conversation-replay - Create annotated replays of text conversations from YAML
|
|
|
9398
10008
|
Usage:
|
|
9399
10009
|
conversation-replay build <scenario.yaml> -o <output.html> [options]
|
|
9400
10010
|
conversation-replay validate <scenario.yaml>
|
|
9401
|
-
conversation-replay --
|
|
10011
|
+
conversation-replay init <output.yaml> [--theme <theme>]
|
|
10012
|
+
conversation-replay schema [section] [--json]
|
|
10013
|
+
conversation-replay info
|
|
10014
|
+
conversation-replay --help | --version
|
|
9402
10015
|
|
|
9403
10016
|
Commands:
|
|
9404
10017
|
build Generate HTML from a scenario file
|
|
9405
10018
|
validate Check a scenario file for errors
|
|
10019
|
+
init Create a starter YAML template with documentation
|
|
10020
|
+
schema Show schema reference (sections: meta, colors, speed, steps)
|
|
10021
|
+
info Show tool name, version, author, and links
|
|
9406
10022
|
|
|
9407
10023
|
Options:
|
|
9408
10024
|
-o, --output <path> Output HTML file path (required for build)
|
|
9409
10025
|
--theme <theme> Override theme (chat, email, slack, terminal, generic)
|
|
9410
10026
|
--no-header Exclude the demo header
|
|
10027
|
+
--json Output JSON schema (for schema command)
|
|
9411
10028
|
-h, --help Show this help message
|
|
10029
|
+
-v, --version Show version number
|
|
9412
10030
|
|
|
9413
10031
|
Examples:
|
|
9414
10032
|
conversation-replay build demo.yaml -o demo.html
|
|
9415
|
-
conversation-replay
|
|
9416
|
-
conversation-replay
|
|
10033
|
+
conversation-replay init my-demo.yaml
|
|
10034
|
+
conversation-replay schema meta
|
|
10035
|
+
conversation-replay info
|
|
9417
10036
|
`;
|
|
9418
10037
|
var VALID_THEMES2 = ["chat", "email", "slack", "terminal", "generic"];
|
|
9419
10038
|
async function main() {
|
|
@@ -9423,18 +10042,32 @@ async function main() {
|
|
|
9423
10042
|
output: { type: "string", short: "o" },
|
|
9424
10043
|
theme: { type: "string" },
|
|
9425
10044
|
"no-header": { type: "boolean" },
|
|
9426
|
-
|
|
10045
|
+
json: { type: "boolean" },
|
|
10046
|
+
help: { type: "boolean", short: "h" },
|
|
10047
|
+
version: { type: "boolean", short: "v" }
|
|
9427
10048
|
},
|
|
9428
10049
|
allowPositionals: true
|
|
9429
10050
|
});
|
|
10051
|
+
if (values.version) {
|
|
10052
|
+
console.log(`conversation-replay v${package_default.version}`);
|
|
10053
|
+
process.exit(0);
|
|
10054
|
+
}
|
|
9430
10055
|
if (values.help || positionals.length === 0) {
|
|
9431
10056
|
console.log(HELP);
|
|
9432
10057
|
process.exit(0);
|
|
9433
10058
|
}
|
|
9434
10059
|
const command = positionals[0];
|
|
9435
|
-
const
|
|
9436
|
-
if (
|
|
9437
|
-
|
|
10060
|
+
const arg1 = positionals[1];
|
|
10061
|
+
if (command === "schema") {
|
|
10062
|
+
handleSchema(arg1, values.json);
|
|
10063
|
+
process.exit(0);
|
|
10064
|
+
}
|
|
10065
|
+
if (command === "info") {
|
|
10066
|
+
handleInfo();
|
|
10067
|
+
process.exit(0);
|
|
10068
|
+
}
|
|
10069
|
+
if (!arg1) {
|
|
10070
|
+
console.error(`Error: No file specified
|
|
9438
10071
|
`);
|
|
9439
10072
|
console.log(HELP);
|
|
9440
10073
|
process.exit(1);
|
|
@@ -9447,10 +10080,13 @@ async function main() {
|
|
|
9447
10080
|
try {
|
|
9448
10081
|
switch (command) {
|
|
9449
10082
|
case "build":
|
|
9450
|
-
await handleBuild(
|
|
10083
|
+
await handleBuild(arg1, values);
|
|
9451
10084
|
break;
|
|
9452
10085
|
case "validate":
|
|
9453
|
-
await handleValidate(
|
|
10086
|
+
await handleValidate(arg1);
|
|
10087
|
+
break;
|
|
10088
|
+
case "init":
|
|
10089
|
+
await handleInit(arg1, values.theme);
|
|
9454
10090
|
break;
|
|
9455
10091
|
default:
|
|
9456
10092
|
console.error(`Error: Unknown command "${command}"
|
|
@@ -9523,6 +10159,41 @@ async function handleValidate(inputFile) {
|
|
|
9523
10159
|
console.log(` - Transitions: ${counts.transition}`);
|
|
9524
10160
|
}
|
|
9525
10161
|
}
|
|
10162
|
+
async function handleInit(outputFile, theme) {
|
|
10163
|
+
const fs = await import("fs/promises");
|
|
10164
|
+
try {
|
|
10165
|
+
await fs.access(outputFile);
|
|
10166
|
+
console.error(`Error: File already exists: ${outputFile}`);
|
|
10167
|
+
console.error("Use a different filename or delete the existing file.");
|
|
10168
|
+
process.exit(1);
|
|
10169
|
+
} catch {}
|
|
10170
|
+
const template = generateTemplate(theme);
|
|
10171
|
+
await fs.writeFile(outputFile, template, "utf-8");
|
|
10172
|
+
console.log(`Created ${outputFile}`);
|
|
10173
|
+
console.log("");
|
|
10174
|
+
console.log("Next steps:");
|
|
10175
|
+
console.log(` 1. Edit ${outputFile} to define your conversation`);
|
|
10176
|
+
console.log(` 2. Validate: conversation-replay validate ${outputFile}`);
|
|
10177
|
+
console.log(` 3. Build: conversation-replay build ${outputFile} -o output.html`);
|
|
10178
|
+
console.log("");
|
|
10179
|
+
console.log("For schema reference: conversation-replay schema");
|
|
10180
|
+
}
|
|
10181
|
+
function handleInfo() {
|
|
10182
|
+
console.log(`
|
|
10183
|
+
Conversation Replay v${package_default.version}
|
|
10184
|
+
https://github.com/lennyzeltser/conversation-replay
|
|
10185
|
+
|
|
10186
|
+
Created by Lenny Zeltser
|
|
10187
|
+
https://zeltser.com
|
|
10188
|
+
`.trim());
|
|
10189
|
+
}
|
|
10190
|
+
function handleSchema(section, outputJson) {
|
|
10191
|
+
if (outputJson) {
|
|
10192
|
+
console.log(JSON.stringify(jsonSchema, null, 2));
|
|
10193
|
+
return;
|
|
10194
|
+
}
|
|
10195
|
+
console.log(getSchemaReference(section));
|
|
10196
|
+
}
|
|
9526
10197
|
main().catch((error) => {
|
|
9527
10198
|
console.error("Unexpected error:", error);
|
|
9528
10199
|
process.exit(1);
|