playwright-director 0.2.0
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 +860 -0
- package/dist/Tutorial.d.ts +108 -0
- package/dist/Tutorial.d.ts.map +1 -0
- package/dist/Tutorial.js +767 -0
- package/dist/Tutorial.js.map +1 -0
- package/dist/bin/export-transcript.d.ts +3 -0
- package/dist/bin/export-transcript.d.ts.map +1 -0
- package/dist/bin/export-transcript.js +111 -0
- package/dist/bin/export-transcript.js.map +1 -0
- package/dist/cursor.d.ts +24 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +96 -0
- package/dist/cursor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +183 -0
- package/dist/init.js.map +1 -0
- package/dist/merge.d.ts +26 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +65 -0
- package/dist/merge.js.map +1 -0
- package/dist/music.d.ts +29 -0
- package/dist/music.d.ts.map +1 -0
- package/dist/music.js +107 -0
- package/dist/music.js.map +1 -0
- package/dist/overlay-html.d.ts +44 -0
- package/dist/overlay-html.d.ts.map +1 -0
- package/dist/overlay-html.js +150 -0
- package/dist/overlay-html.js.map +1 -0
- package/dist/overlay.d.ts +39 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +134 -0
- package/dist/overlay.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +38 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/reporter.d.ts +22 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +166 -0
- package/dist/reporter.js.map +1 -0
- package/dist/site/build-site.d.ts +2 -0
- package/dist/site/build-site.d.ts.map +1 -0
- package/dist/site/build-site.js +82 -0
- package/dist/site/build-site.js.map +1 -0
- package/dist/site/embed.d.ts +48 -0
- package/dist/site/embed.d.ts.map +1 -0
- package/dist/site/embed.js +45 -0
- package/dist/site/embed.js.map +1 -0
- package/dist/site/generate-manifest.d.ts +15 -0
- package/dist/site/generate-manifest.d.ts.map +1 -0
- package/dist/site/generate-manifest.js +106 -0
- package/dist/site/generate-manifest.js.map +1 -0
- package/dist/site/scaffold.d.ts +3 -0
- package/dist/site/scaffold.d.ts.map +1 -0
- package/dist/site/scaffold.js +70 -0
- package/dist/site/scaffold.js.map +1 -0
- package/dist/site/scan-tutorials.d.ts +12 -0
- package/dist/site/scan-tutorials.d.ts.map +1 -0
- package/dist/site/scan-tutorials.js +54 -0
- package/dist/site/scan-tutorials.js.map +1 -0
- package/dist/site/types.d.ts +75 -0
- package/dist/site/types.d.ts.map +1 -0
- package/dist/site/types.js +2 -0
- package/dist/site/types.js.map +1 -0
- package/dist/skill-stamp.d.ts +21 -0
- package/dist/skill-stamp.d.ts.map +1 -0
- package/dist/skill-stamp.js +39 -0
- package/dist/skill-stamp.js.map +1 -0
- package/dist/slugify.d.ts +19 -0
- package/dist/slugify.d.ts.map +1 -0
- package/dist/slugify.js +29 -0
- package/dist/slugify.js.map +1 -0
- package/dist/stage-presets.d.ts +51 -0
- package/dist/stage-presets.d.ts.map +1 -0
- package/dist/stage-presets.js +45 -0
- package/dist/stage-presets.js.map +1 -0
- package/dist/styles.css +637 -0
- package/dist/timeline.d.ts +87 -0
- package/dist/timeline.d.ts.map +1 -0
- package/dist/timeline.js +108 -0
- package/dist/timeline.js.map +1 -0
- package/dist/transcript.d.ts +87 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +291 -0
- package/dist/transcript.js.map +1 -0
- package/dist/tts-provider.d.ts +69 -0
- package/dist/tts-provider.d.ts.map +1 -0
- package/dist/tts-provider.js +182 -0
- package/dist/tts-provider.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/voice-prerender.d.ts +41 -0
- package/dist/voice-prerender.d.ts.map +1 -0
- package/dist/voice-prerender.js +122 -0
- package/dist/voice-prerender.js.map +1 -0
- package/dist/voice.d.ts +76 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +244 -0
- package/dist/voice.js.map +1 -0
- package/package.json +87 -0
- package/skills/tutorialize/SKILL.md +54 -0
- package/skills/tutorialize/agent.md +58 -0
- package/skills/tutorialize/references/api.md +489 -0
- package/skills/tutorialize/references/storytelling.md +193 -0
- package/src/styles.css +637 -0
- package/templates/site/public/.gitkeep +0 -0
- package/templates/site/public/widget.js +273 -0
- package/templates/site/src/components/TutorialsHome.astro +141 -0
- package/templates/site/src/components/VideoCard.astro +192 -0
- package/templates/site/src/components/VideoModal.astro +170 -0
- package/templates/site/src/data/.gitkeep +0 -0
- package/templates/site/src/layouts/Base.astro +56 -0
- package/templates/site/src/pages/[slug].astro +745 -0
- package/templates/site/src/pages/index.astro +5 -0
- package/templates/site/src/styles/tutorials.css +303 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB;8DAC0D;IAC1D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;2FACuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB;;sEAEkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;sEAEkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,SAAS,CAAc;gBAG9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAW,EACrB,WAAW,GAAE,MAAW,EACxB,IAAI,GAAE,MAAa,EACnB,SAAS,GAAE,MAAW,EACtB,OAAO,GAAE,MAAW,EACpB,YAAY,GAAE,oBAAyB,EACvC,KAAK,GAAE,MAAW,EAClB,OAAO,GAAE,MAAW;IAarB;;OAEG;IACH,KAAK,CAAC,WAAW,GAAE,MAAU,EAAE,UAAU,GAAE,OAAe,GAAG,IAAI;IAOjE;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;IAe5J;;OAEG;IACH,OAAO,IAAI,YAAY;IA2BvB;;OAEG;IACH,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAY9B"}
|
package/dist/timeline.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { buildMergeCommand } from './merge.js';
|
|
4
|
+
import { buildTranscriptMarkdown } from './transcript.js';
|
|
5
|
+
/**
|
|
6
|
+
* Tracks timing of tutorial steps for post-processing audio merge.
|
|
7
|
+
*/
|
|
8
|
+
export class TutorialTimeline {
|
|
9
|
+
testName;
|
|
10
|
+
testTitle;
|
|
11
|
+
title = '';
|
|
12
|
+
testFile;
|
|
13
|
+
projectName;
|
|
14
|
+
lang;
|
|
15
|
+
feature = '';
|
|
16
|
+
variant = '';
|
|
17
|
+
musicOptions;
|
|
18
|
+
startTime = 0;
|
|
19
|
+
videoTrimMs = 0;
|
|
20
|
+
syncMarker = false;
|
|
21
|
+
steps = [];
|
|
22
|
+
videoPath = '';
|
|
23
|
+
constructor(testName, testFile = '', projectName = '', lang = 'fr', testTitle = '', feature = '', musicOptions = {}, title = '', variant = '') {
|
|
24
|
+
this.variant = variant;
|
|
25
|
+
this.testName = testName;
|
|
26
|
+
this.testTitle = testTitle;
|
|
27
|
+
this.title = title;
|
|
28
|
+
this.testFile = testFile;
|
|
29
|
+
this.projectName = projectName;
|
|
30
|
+
this.lang = lang;
|
|
31
|
+
this.feature = feature;
|
|
32
|
+
this.musicOptions = musicOptions;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mark the start of the tutorial (second 0 for video)
|
|
36
|
+
*/
|
|
37
|
+
start(videoTrimMs = 0, syncMarker = false) {
|
|
38
|
+
this.startTime = Date.now();
|
|
39
|
+
this.videoTrimMs = videoTrimMs;
|
|
40
|
+
this.syncMarker = syncMarker;
|
|
41
|
+
console.log(`[Timeline] Started at ${new Date().toISOString()} (trim ${videoTrimMs}ms from video start)`);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Set the video path (from Playwright's page.video()?.path())
|
|
45
|
+
*/
|
|
46
|
+
setVideoPath(path) {
|
|
47
|
+
this.videoPath = path;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Record a step at a specific timestamp (for accurate voice timing)
|
|
51
|
+
*/
|
|
52
|
+
addStep(step, title, audioFile, durationMs, timestamp, text, key, scene) {
|
|
53
|
+
const startMs = timestamp - this.startTime;
|
|
54
|
+
this.steps.push({
|
|
55
|
+
step,
|
|
56
|
+
title,
|
|
57
|
+
...(text ? { text } : {}),
|
|
58
|
+
...(key ? { key } : {}),
|
|
59
|
+
...(scene ? { scene } : {}),
|
|
60
|
+
audioFile,
|
|
61
|
+
startMs,
|
|
62
|
+
durationMs
|
|
63
|
+
});
|
|
64
|
+
console.log(`[Timeline] Step ${step}: "${title}" at ${startMs}ms (${durationMs}ms audio)`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the timeline data with merge command
|
|
68
|
+
*/
|
|
69
|
+
getData() {
|
|
70
|
+
const data = {
|
|
71
|
+
testName: this.testName,
|
|
72
|
+
testTitle: this.testTitle,
|
|
73
|
+
title: this.title || undefined,
|
|
74
|
+
testFile: this.testFile,
|
|
75
|
+
projectName: this.projectName,
|
|
76
|
+
lang: this.lang,
|
|
77
|
+
feature: this.feature || undefined,
|
|
78
|
+
variant: this.variant || undefined,
|
|
79
|
+
totalDurationMs: Date.now() - this.startTime,
|
|
80
|
+
videoTrimMs: this.videoTrimMs,
|
|
81
|
+
syncMarker: this.syncMarker || undefined,
|
|
82
|
+
steps: this.steps,
|
|
83
|
+
videoPath: this.videoPath
|
|
84
|
+
};
|
|
85
|
+
// Output filename is already language-specific (testName = slugified translated title).
|
|
86
|
+
// No `-en` / `-ar` suffix needed — each locale produces its own SEO-friendly slug.
|
|
87
|
+
const { command } = buildMergeCommand(data, this.videoPath, `tutorials/videos/${this.testName}.webm`, this.musicOptions);
|
|
88
|
+
return {
|
|
89
|
+
...data,
|
|
90
|
+
mergeCommand: command
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Save timeline to JSON file and auto-generate transcript markdown.
|
|
95
|
+
*/
|
|
96
|
+
save(outputPath) {
|
|
97
|
+
const data = this.getData();
|
|
98
|
+
writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
|
99
|
+
console.log(`[Timeline] Saved to ${outputPath}`);
|
|
100
|
+
const transcriptDir = join(process.cwd(), 'tutorials/transcripts');
|
|
101
|
+
if (!existsSync(transcriptDir))
|
|
102
|
+
mkdirSync(transcriptDir, { recursive: true });
|
|
103
|
+
const transcriptPath = join(transcriptDir, `${data.testName}.md`);
|
|
104
|
+
writeFileSync(transcriptPath, buildTranscriptMarkdown(data), 'utf-8');
|
|
105
|
+
console.log(`[Timeline] Transcript: tutorials/transcripts/${data.testName}.md`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { TimelineData } from './timeline.js';
|
|
2
|
+
/** One narrated entry of a transcript markdown file. */
|
|
3
|
+
export interface TranscriptEntry {
|
|
4
|
+
kind: 'context' | 'step' | 'complete';
|
|
5
|
+
/** i18n key of the step/context (from the `**key:**` line, when present) */
|
|
6
|
+
key?: string;
|
|
7
|
+
/** Step title (from the `### Step N: <title>` heading) */
|
|
8
|
+
title?: string;
|
|
9
|
+
/** Narration text */
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Build the transcript markdown for a timeline. Shared by the automatic
|
|
14
|
+
* generation in `TutorialTimeline.save()` and the `tutorial-transcript` CLI,
|
|
15
|
+
* so both always produce the same format `parseTranscript` reads back.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildTranscriptMarkdown(data: TimelineData): string;
|
|
18
|
+
/**
|
|
19
|
+
* Parse a transcript markdown file back into its narrated entries.
|
|
20
|
+
* Tolerant of surrounding noise (header, comments, separators): only the
|
|
21
|
+
* `**[Context]**`, `**[Complete]**`, `### Step N:` and `**key:**` markers
|
|
22
|
+
* plus step body text are significant.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseTranscript(markdown: string): TranscriptEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Narration overrides loaded from an edited transcript
|
|
27
|
+
* (`tutorials/transcripts/<testName>.corrections.md`).
|
|
28
|
+
*
|
|
29
|
+
* Entries are matched by kind + key, consumed in file order so a key used
|
|
30
|
+
* twice corrects each occurrence in turn. Entries without a key (older
|
|
31
|
+
* transcript files) fall back to kind-order matching.
|
|
32
|
+
*/
|
|
33
|
+
export declare class TranscriptCorrections {
|
|
34
|
+
private queues;
|
|
35
|
+
constructor(entries: TranscriptEntry[]);
|
|
36
|
+
/** Load corrections from a file; null when the file does not exist. */
|
|
37
|
+
static load(path: string): TranscriptCorrections | null;
|
|
38
|
+
/**
|
|
39
|
+
* Consume the next correction for this kind (+ key). Exact key match
|
|
40
|
+
* first, then keyless entries of the same kind as an ordered fallback.
|
|
41
|
+
*/
|
|
42
|
+
next(kind: TranscriptEntry['kind'], key?: string): string | undefined;
|
|
43
|
+
/** Corrections that never matched a narrated step — likely stale keys. */
|
|
44
|
+
get remaining(): number;
|
|
45
|
+
describeRemaining(): string[];
|
|
46
|
+
}
|
|
47
|
+
export interface ApplyChange {
|
|
48
|
+
kind: TranscriptEntry['kind'];
|
|
49
|
+
key?: string;
|
|
50
|
+
file: string;
|
|
51
|
+
from: string;
|
|
52
|
+
to: string;
|
|
53
|
+
}
|
|
54
|
+
export interface ApplyProblem extends ApplyChange {
|
|
55
|
+
reason: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ApplyReport {
|
|
58
|
+
/** Replacements written into source files */
|
|
59
|
+
applied: ApplyChange[];
|
|
60
|
+
/** Corrections that could not be applied automatically */
|
|
61
|
+
manual: ApplyProblem[];
|
|
62
|
+
/** Narrated steps whose text was not edited */
|
|
63
|
+
unchanged: number;
|
|
64
|
+
/** Files that were modified */
|
|
65
|
+
files: string[];
|
|
66
|
+
/** True when step texts changed in the timeline data (caller should re-save the JSON) */
|
|
67
|
+
timelineUpdated: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Filesystem boundary, injectable for deterministic tests. */
|
|
70
|
+
export interface ApplyIO {
|
|
71
|
+
read(path: string): string | null;
|
|
72
|
+
write(path: string, content: string): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Write edited transcript texts back into the test source file.
|
|
76
|
+
*
|
|
77
|
+
* Each narrated timeline step is paired with its transcript entry (kind + key,
|
|
78
|
+
* in order). When the edited text differs, the original text is located in
|
|
79
|
+
* `data.testFile` as a quoted string literal and replaced. A narration composed
|
|
80
|
+
* as `` `${do}. ${explain}` `` (or title + description) is handled by replacing
|
|
81
|
+
* both halves, splitting old and new at the first sentence boundary. Texts that
|
|
82
|
+
* cannot be found (e.g. coming from an i18n catalog) are reported for manual
|
|
83
|
+
* correction with their key. Applied texts are also written back into the
|
|
84
|
+
* timeline data, keeping a second `apply` run idempotent.
|
|
85
|
+
*/
|
|
86
|
+
export declare function applyCorrections(data: TimelineData, entries: TranscriptEntry[], io: ApplyIO): ApplyReport;
|
|
87
|
+
//# sourceMappingURL=transcript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../src/transcript.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;IACtC,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAqClE;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAqDnE;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAqB;IACjC,OAAO,CAAC,MAAM,CAA+B;gBAEjC,OAAO,EAAE,eAAe,EAAE;IAStC,uEAAuE;IACvE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI;IAMvD;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUrE,0EAA0E;IAC1E,IAAI,SAAS,IAAI,MAAM,CAItB;IAED,iBAAiB,IAAI,MAAM,EAAE;CAS7B;AAID,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAChD,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC3B,6CAA6C;IAC7C,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,0DAA0D;IAC1D,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yFAAyF;IACzF,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,+DAA+D;AAC/D,MAAM,WAAW,OAAO;IACvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAmCD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,eAAe,EAAE,EAC1B,EAAE,EAAE,OAAO,GACT,WAAW,CAgGb"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Build the transcript markdown for a timeline. Shared by the automatic
|
|
4
|
+
* generation in `TutorialTimeline.save()` and the `tutorial-transcript` CLI,
|
|
5
|
+
* so both always produce the same format `parseTranscript` reads back.
|
|
6
|
+
*/
|
|
7
|
+
export function buildTranscriptMarkdown(data) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`# ${data.testName}`);
|
|
10
|
+
lines.push('');
|
|
11
|
+
lines.push(`- **Test:** \`${data.testTitle}\``);
|
|
12
|
+
lines.push(`- **Language:** ${data.lang}`);
|
|
13
|
+
lines.push(`- **Duration:** ${(data.totalDurationMs / 1000).toFixed(1)}s`);
|
|
14
|
+
lines.push('');
|
|
15
|
+
lines.push('<!-- Edit the narration texts below, then run `npx tutorial-transcript apply`');
|
|
16
|
+
lines.push(' to write the corrections back into the test source. -->');
|
|
17
|
+
lines.push('');
|
|
18
|
+
lines.push('---');
|
|
19
|
+
lines.push('');
|
|
20
|
+
let stepNum = 0;
|
|
21
|
+
for (const step of data.steps) {
|
|
22
|
+
// Unvoiced steps (empty audioFile) have no narration to correct.
|
|
23
|
+
if (!step.text || !step.audioFile)
|
|
24
|
+
continue;
|
|
25
|
+
if (step.step === 0 && step.title === 'Context') {
|
|
26
|
+
lines.push(`**[Context]** ${step.text}`);
|
|
27
|
+
if (step.key)
|
|
28
|
+
lines.push(`**key:** \`${step.key}\``);
|
|
29
|
+
lines.push('');
|
|
30
|
+
}
|
|
31
|
+
else if (step.title === 'Complete') {
|
|
32
|
+
lines.push(`**[Complete]** ${step.text}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
stepNum++;
|
|
37
|
+
lines.push(`### Step ${stepNum}: ${step.title}`);
|
|
38
|
+
if (step.key)
|
|
39
|
+
lines.push(`**key:** \`${step.key}\``);
|
|
40
|
+
lines.push('');
|
|
41
|
+
lines.push(step.text);
|
|
42
|
+
lines.push('');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
}
|
|
47
|
+
const KEY_LINE = /^\*\*key:\*\* `(.+)`\s*$/;
|
|
48
|
+
const STEP_HEADING = /^### Step \d+: (.*)$/;
|
|
49
|
+
const CONTEXT_LINE = /^\*\*\[Context\]\*\* (.*)$/;
|
|
50
|
+
const COMPLETE_LINE = /^\*\*\[Complete\]\*\* (.*)$/;
|
|
51
|
+
/**
|
|
52
|
+
* Parse a transcript markdown file back into its narrated entries.
|
|
53
|
+
* Tolerant of surrounding noise (header, comments, separators): only the
|
|
54
|
+
* `**[Context]**`, `**[Complete]**`, `### Step N:` and `**key:**` markers
|
|
55
|
+
* plus step body text are significant.
|
|
56
|
+
*/
|
|
57
|
+
export function parseTranscript(markdown) {
|
|
58
|
+
const entries = [];
|
|
59
|
+
let current = null;
|
|
60
|
+
let bodyLines = [];
|
|
61
|
+
const flush = () => {
|
|
62
|
+
if (current && current.kind === 'step') {
|
|
63
|
+
current.text = bodyLines.join(' ').replace(/\s+/g, ' ').trim();
|
|
64
|
+
}
|
|
65
|
+
if (current && current.text)
|
|
66
|
+
entries.push(current);
|
|
67
|
+
current = null;
|
|
68
|
+
bodyLines = [];
|
|
69
|
+
};
|
|
70
|
+
for (const rawLine of markdown.split('\n')) {
|
|
71
|
+
const line = rawLine.trimEnd();
|
|
72
|
+
const context = line.match(CONTEXT_LINE);
|
|
73
|
+
if (context) {
|
|
74
|
+
flush();
|
|
75
|
+
current = { kind: 'context', text: context[1].trim() };
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const complete = line.match(COMPLETE_LINE);
|
|
79
|
+
if (complete) {
|
|
80
|
+
flush();
|
|
81
|
+
current = { kind: 'complete', text: complete[1].trim() };
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const heading = line.match(STEP_HEADING);
|
|
85
|
+
if (heading) {
|
|
86
|
+
flush();
|
|
87
|
+
current = { kind: 'step', title: heading[1].trim(), text: '' };
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const keyLine = line.match(KEY_LINE);
|
|
91
|
+
if (keyLine) {
|
|
92
|
+
if (current)
|
|
93
|
+
current.key = keyLine[1];
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Everything else is either step body text or ignorable scaffolding.
|
|
97
|
+
if (current?.kind === 'step') {
|
|
98
|
+
if (line.startsWith('#') || line.startsWith('---') || line.startsWith('<!--'))
|
|
99
|
+
continue;
|
|
100
|
+
if (line.trim())
|
|
101
|
+
bodyLines.push(line.trim());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
flush();
|
|
105
|
+
return entries;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Narration overrides loaded from an edited transcript
|
|
109
|
+
* (`tutorials/transcripts/<testName>.corrections.md`).
|
|
110
|
+
*
|
|
111
|
+
* Entries are matched by kind + key, consumed in file order so a key used
|
|
112
|
+
* twice corrects each occurrence in turn. Entries without a key (older
|
|
113
|
+
* transcript files) fall back to kind-order matching.
|
|
114
|
+
*/
|
|
115
|
+
export class TranscriptCorrections {
|
|
116
|
+
queues = new Map();
|
|
117
|
+
constructor(entries) {
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
const bucket = `${entry.kind}:${entry.key ?? ''}`;
|
|
120
|
+
const queue = this.queues.get(bucket) ?? [];
|
|
121
|
+
queue.push(entry.text);
|
|
122
|
+
this.queues.set(bucket, queue);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Load corrections from a file; null when the file does not exist. */
|
|
126
|
+
static load(path) {
|
|
127
|
+
if (!existsSync(path))
|
|
128
|
+
return null;
|
|
129
|
+
const entries = parseTranscript(readFileSync(path, 'utf-8'));
|
|
130
|
+
return entries.length > 0 ? new TranscriptCorrections(entries) : null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Consume the next correction for this kind (+ key). Exact key match
|
|
134
|
+
* first, then keyless entries of the same kind as an ordered fallback.
|
|
135
|
+
*/
|
|
136
|
+
next(kind, key) {
|
|
137
|
+
const exact = this.queues.get(`${kind}:${key ?? ''}`);
|
|
138
|
+
if (exact && exact.length > 0)
|
|
139
|
+
return exact.shift();
|
|
140
|
+
if (key) {
|
|
141
|
+
const keyless = this.queues.get(`${kind}:`);
|
|
142
|
+
if (keyless && keyless.length > 0)
|
|
143
|
+
return keyless.shift();
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/** Corrections that never matched a narrated step — likely stale keys. */
|
|
148
|
+
get remaining() {
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const queue of this.queues.values())
|
|
151
|
+
count += queue.length;
|
|
152
|
+
return count;
|
|
153
|
+
}
|
|
154
|
+
describeRemaining() {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const [bucket, queue] of this.queues) {
|
|
157
|
+
for (const text of queue) {
|
|
158
|
+
out.push(`[${bucket}] ${text.slice(0, 60)}${text.length > 60 ? '…' : ''}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const QUOTES = ["'", '"', '`'];
|
|
165
|
+
function escapeLiteral(text, quote) {
|
|
166
|
+
return text.split('\\').join('\\\\').split(quote).join('\\' + quote);
|
|
167
|
+
}
|
|
168
|
+
/** Find `oldText` as a complete quoted string literal and swap it for `newText`.
|
|
169
|
+
* Searches from `cursor` first (source order follows step order), then from 0. */
|
|
170
|
+
function replaceQuoted(content, cursor, oldText, newText) {
|
|
171
|
+
for (const start of cursor > 0 ? [cursor, 0] : [0]) {
|
|
172
|
+
let best = null;
|
|
173
|
+
for (const quote of QUOTES) {
|
|
174
|
+
const needle = quote + escapeLiteral(oldText, quote) + quote;
|
|
175
|
+
const at = content.indexOf(needle, start);
|
|
176
|
+
if (at !== -1 && (!best || at < best.at))
|
|
177
|
+
best = { at, quote };
|
|
178
|
+
}
|
|
179
|
+
if (best) {
|
|
180
|
+
const needle = best.quote + escapeLiteral(oldText, best.quote) + best.quote;
|
|
181
|
+
const replacement = best.quote + escapeLiteral(newText, best.quote) + best.quote;
|
|
182
|
+
return {
|
|
183
|
+
content: content.slice(0, best.at) + replacement + content.slice(best.at + needle.length),
|
|
184
|
+
at: best.at
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write edited transcript texts back into the test source file.
|
|
192
|
+
*
|
|
193
|
+
* Each narrated timeline step is paired with its transcript entry (kind + key,
|
|
194
|
+
* in order). When the edited text differs, the original text is located in
|
|
195
|
+
* `data.testFile` as a quoted string literal and replaced. A narration composed
|
|
196
|
+
* as `` `${do}. ${explain}` `` (or title + description) is handled by replacing
|
|
197
|
+
* both halves, splitting old and new at the first sentence boundary. Texts that
|
|
198
|
+
* cannot be found (e.g. coming from an i18n catalog) are reported for manual
|
|
199
|
+
* correction with their key. Applied texts are also written back into the
|
|
200
|
+
* timeline data, keeping a second `apply` run idempotent.
|
|
201
|
+
*/
|
|
202
|
+
export function applyCorrections(data, entries, io) {
|
|
203
|
+
const corrections = new TranscriptCorrections(entries);
|
|
204
|
+
const report = { applied: [], manual: [], unchanged: 0, files: [], timelineUpdated: false };
|
|
205
|
+
let content = data.testFile ? io.read(data.testFile) : null;
|
|
206
|
+
let dirty = false;
|
|
207
|
+
let cursor = 0;
|
|
208
|
+
for (const step of data.steps) {
|
|
209
|
+
if (!step.text || !step.audioFile)
|
|
210
|
+
continue;
|
|
211
|
+
const kind = step.step === 0 && step.title === 'Context' ? 'context'
|
|
212
|
+
: step.title === 'Complete' ? 'complete'
|
|
213
|
+
: 'step';
|
|
214
|
+
const corrected = corrections.next(kind, step.key);
|
|
215
|
+
if (!corrected || corrected === step.text) {
|
|
216
|
+
report.unchanged++;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const change = { kind, key: step.key, file: data.testFile, from: step.text, to: corrected };
|
|
220
|
+
if (content === null) {
|
|
221
|
+
report.manual.push({ ...change, reason: `test file not found: ${data.testFile || '(unknown)'}` });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// 1. The whole narration as one literal (voiceText, do-only, plain title…)
|
|
225
|
+
let result = replaceQuoted(content, cursor, step.text, corrected);
|
|
226
|
+
// 2. Two-part narration: `${do}. ${explain}` / `${title}. ${description}` —
|
|
227
|
+
// replace each half, splitting old and new at the first sentence boundary.
|
|
228
|
+
let keyEditBlocked = false;
|
|
229
|
+
if (!result) {
|
|
230
|
+
const oldSplit = step.text.indexOf('. ');
|
|
231
|
+
const newSplit = corrected.indexOf('. ');
|
|
232
|
+
if (oldSplit > 0 && newSplit > 0) {
|
|
233
|
+
const oldFirst = step.text.slice(0, oldSplit);
|
|
234
|
+
const newFirst = corrected.slice(0, newSplit);
|
|
235
|
+
const oldRest = step.text.slice(oldSplit + 2);
|
|
236
|
+
const newRest = corrected.slice(newSplit + 2);
|
|
237
|
+
if (oldFirst === step.key) {
|
|
238
|
+
// The first sentence IS the i18n key rendered verbatim (identity
|
|
239
|
+
// translate). Rewriting it would break the key — only the rest
|
|
240
|
+
// may be edited from the transcript.
|
|
241
|
+
if (newFirst === oldFirst) {
|
|
242
|
+
result = replaceQuoted(content, cursor, oldRest, newRest);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
keyEditBlocked = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const first = replaceQuoted(content, cursor, oldFirst, newFirst);
|
|
250
|
+
if (first) {
|
|
251
|
+
const second = replaceQuoted(first.content, cursor, oldRest, newRest);
|
|
252
|
+
if (second)
|
|
253
|
+
result = { content: second.content, at: Math.max(first.at, second.at) };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (result) {
|
|
259
|
+
content = result.content;
|
|
260
|
+
cursor = result.at;
|
|
261
|
+
dirty = true;
|
|
262
|
+
step.text = corrected;
|
|
263
|
+
report.timelineUpdated = true;
|
|
264
|
+
report.applied.push(change);
|
|
265
|
+
}
|
|
266
|
+
else if (keyEditBlocked) {
|
|
267
|
+
report.manual.push({
|
|
268
|
+
...change,
|
|
269
|
+
reason: `the first sentence is the step key \`${step.key}\` — rename the key in the test (or fix its translation) instead of editing it here`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const hint = step.key
|
|
274
|
+
? ` — probably an i18n text (key \`${step.key}\`), fix it in the translation catalog`
|
|
275
|
+
: '';
|
|
276
|
+
report.manual.push({ ...change, reason: `text not found as a string literal in ${data.testFile}${hint}` });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const desc of corrections.describeRemaining()) {
|
|
280
|
+
report.manual.push({
|
|
281
|
+
kind: 'step', file: data.testFile, from: '', to: desc,
|
|
282
|
+
reason: 'transcript entry matches no narrated step (stale key after a test change?)'
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (dirty && content !== null) {
|
|
286
|
+
io.write(data.testFile, content);
|
|
287
|
+
report.files.push(data.testFile);
|
|
288
|
+
}
|
|
289
|
+
return report;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=transcript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../src/transcript.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface TTSResult {
|
|
2
|
+
filePath: string;
|
|
3
|
+
duration: number;
|
|
4
|
+
}
|
|
5
|
+
export interface TTSProvider {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
synthesize(text: string, outputPath: string): Promise<TTSResult>;
|
|
8
|
+
isAvailable(): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export interface TTSProviderOptions {
|
|
11
|
+
lang: string;
|
|
12
|
+
voice?: string;
|
|
13
|
+
rate?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Command-based TTS provider. Defaults to macOS `say`, can be overridden
|
|
17
|
+
* via TUTORIAL_TTS_CMD env var.
|
|
18
|
+
* Supports placeholders: {lang}, {text}, {output}.
|
|
19
|
+
* If {text}/{output} are present they are substituted in-place; otherwise
|
|
20
|
+
* the legacy "-o <output> <text>" suffix is appended automatically.
|
|
21
|
+
* Examples:
|
|
22
|
+
* TUTORIAL_TTS_CMD='qsay --my-voice jadou -l {lang} {text} -o {output}'
|
|
23
|
+
* TUTORIAL_TTS_CMD='say -v Thomas' (legacy: appends -o <out> "<text>")
|
|
24
|
+
*/
|
|
25
|
+
export declare class CommandTTSProvider implements TTSProvider {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
private command;
|
|
28
|
+
private options;
|
|
29
|
+
private static VOICES;
|
|
30
|
+
private static LANG_NAMES;
|
|
31
|
+
private static _logged;
|
|
32
|
+
constructor(options: TTSProviderOptions);
|
|
33
|
+
isAvailable(): Promise<boolean>;
|
|
34
|
+
synthesize(text: string, outputPath: string): Promise<TTSResult>;
|
|
35
|
+
}
|
|
36
|
+
/** Edge TTS provider using Microsoft's free TTS API */
|
|
37
|
+
export declare class EdgeTTSProvider implements TTSProvider {
|
|
38
|
+
readonly name = "edge-tts";
|
|
39
|
+
private options;
|
|
40
|
+
private static VOICES;
|
|
41
|
+
constructor(options: TTSProviderOptions);
|
|
42
|
+
isAvailable(): Promise<boolean>;
|
|
43
|
+
synthesize(text: string, outputPath: string): Promise<TTSResult>;
|
|
44
|
+
}
|
|
45
|
+
/** Auto-detect and create the best available TTS provider */
|
|
46
|
+
export declare function createTTSProvider(options: TTSProviderOptions): Promise<TTSProvider>;
|
|
47
|
+
/** Audio segment info for later merging */
|
|
48
|
+
export interface AudioSegment {
|
|
49
|
+
filePath: string;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
duration: number;
|
|
52
|
+
text: string;
|
|
53
|
+
}
|
|
54
|
+
/** Manages TTS audio generation and tracking for tutorials */
|
|
55
|
+
export declare class TTSManager {
|
|
56
|
+
private provider;
|
|
57
|
+
private options;
|
|
58
|
+
private outputDir;
|
|
59
|
+
private segments;
|
|
60
|
+
private recordingStartTime;
|
|
61
|
+
constructor(options: TTSProviderOptions, outputDir: string);
|
|
62
|
+
initialize(): Promise<void>;
|
|
63
|
+
startRecording(): void;
|
|
64
|
+
speak(text: string): Promise<number>;
|
|
65
|
+
getSegments(): AudioSegment[];
|
|
66
|
+
/** Generate a manifest file for post-processing */
|
|
67
|
+
writeManifest(videoPath: string): Promise<string>;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=tts-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts-provider.d.ts","sourceRoot":"","sources":["../src/tts-provider.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,SAAS;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACjE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAeD;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAqB;IAEpC,OAAO,CAAC,MAAM,CAAC,MAAM,CAInB;IAEF,OAAO,CAAC,MAAM,CAAC,UAAU,CAIvB;IAEF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAS;gBAEnB,OAAO,EAAE,kBAAkB;IAmBjC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAU/B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAWtE;AAED,uDAAuD;AACvD,qBAAa,eAAgB,YAAW,WAAW;IAClD,QAAQ,CAAC,IAAI,cAAc;IAC3B,OAAO,CAAC,OAAO,CAAqB;IAEpC,OAAO,CAAC,MAAM,CAAC,MAAM,CAInB;gBAEU,OAAO,EAAE,kBAAkB;IAIjC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAS/B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAYtE;AAED,6DAA6D;AAC7D,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAczF;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,8DAA8D;AAC9D,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,kBAAkB,CAAa;gBAE3B,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM;IAKpD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC,cAAc,IAAI,IAAI;IAKhB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB1C,WAAW,IAAI,YAAY,EAAE;IAI7B,mDAAmD;IAC7C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAavD"}
|