@lincyaw/kernel 1.0.0-rc.0 → 1.0.0-rc.1

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 (119) hide show
  1. package/README.md +7 -7
  2. package/package.json +6 -2
  3. package/src/access.ts +18 -0
  4. package/src/anchors.ts +5 -1
  5. package/src/authoring.ts +56 -0
  6. package/src/blocks.ts +0 -8
  7. package/src/component-code.ts +6 -0
  8. package/src/contracts/agent.ts +5 -82
  9. package/src/contracts/anchors.ts +46 -0
  10. package/src/contracts/authoring-actions.ts +60 -0
  11. package/src/contracts/authoring-clients.ts +213 -0
  12. package/src/contracts/authoring.ts +180 -0
  13. package/src/contracts/checks.ts +29 -0
  14. package/src/contracts/classroom.ts +49 -0
  15. package/src/contracts/diagnostics.ts +37 -0
  16. package/src/contracts/document-audio.ts +150 -0
  17. package/src/contracts/document-comments.ts +31 -0
  18. package/src/contracts/document-history.ts +63 -0
  19. package/src/contracts/document-image.ts +32 -0
  20. package/src/contracts/document-reads.ts +57 -0
  21. package/src/contracts/document-voice.ts +44 -0
  22. package/src/contracts/document.ts +240 -0
  23. package/src/contracts/execution.ts +4 -5
  24. package/src/contracts/locale.ts +3 -0
  25. package/src/contracts/offline.ts +2 -0
  26. package/src/contracts/plugin.ts +183 -0
  27. package/src/contracts/presenter-panels.ts +17 -0
  28. package/src/contracts/preview.ts +305 -310
  29. package/src/contracts/production-assets.ts +61 -0
  30. package/src/contracts/production.ts +109 -0
  31. package/src/contracts/projects.ts +59 -0
  32. package/src/contracts/server.ts +1 -51
  33. package/src/contracts/speech-settings.ts +16 -0
  34. package/src/contracts/speech.ts +82 -70
  35. package/src/contracts/steps.ts +15 -0
  36. package/src/contracts/transcription-adoption.ts +79 -0
  37. package/src/contracts/transcription.ts +112 -0
  38. package/src/contracts/voice.ts +97 -0
  39. package/src/contracts/voiceover-execution.ts +68 -0
  40. package/src/contracts/workspace.ts +458 -0
  41. package/src/contracts.ts +28 -278
  42. package/src/diagnostics.ts +7 -0
  43. package/src/document-authoring-actions.ts +47 -0
  44. package/src/document-caption-timings.ts +64 -0
  45. package/src/document-colors.ts +25 -0
  46. package/src/document-comments.ts +118 -0
  47. package/src/document-components.ts +251 -0
  48. package/src/document-operations.ts +132 -0
  49. package/src/document-pages.ts +215 -0
  50. package/src/document-reads.ts +81 -0
  51. package/src/document-schema.ts +85 -0
  52. package/src/document-voices.ts +153 -0
  53. package/src/document.ts +252 -0
  54. package/src/durations.ts +7 -0
  55. package/src/errors.ts +8 -0
  56. package/src/execution.ts +5 -1
  57. package/src/fingerprint.ts +1 -1
  58. package/src/follow.ts +26 -30
  59. package/src/git.ts +1 -1
  60. package/src/index.ts +66 -13
  61. package/src/ir.ts +54 -5
  62. package/src/jupyter-kernel.ts +9 -0
  63. package/src/kernel.ts +38 -25
  64. package/src/keyboard.ts +43 -0
  65. package/src/locale.ts +13 -0
  66. package/src/lock.ts +4 -8
  67. package/src/logging.ts +1 -1
  68. package/src/{markdown.ts → lower-myst.ts} +2 -1
  69. package/src/node/assets.ts +23 -0
  70. package/src/node/authoring-actions.ts +50 -0
  71. package/src/node/authoring-clients.ts +14 -0
  72. package/src/node/document-audio.ts +22 -0
  73. package/src/node/document-history.ts +11 -0
  74. package/src/node/document-image.ts +8 -0
  75. package/src/node/document-reads.ts +10 -0
  76. package/src/node/document-voice.ts +7 -0
  77. package/src/node/ffmpeg.ts +29 -0
  78. package/src/node/file-version.ts +6 -0
  79. package/src/node/fonts.ts +22 -0
  80. package/src/node/index.ts +29 -133
  81. package/src/node/installed-packages.ts +11 -0
  82. package/src/node/lines.ts +24 -0
  83. package/src/node/locale.ts +7 -0
  84. package/src/node/network.ts +11 -0
  85. package/src/node/operator-speech.ts +91 -0
  86. package/src/node/packages.ts +74 -0
  87. package/src/node/paths.ts +7 -0
  88. package/src/node/projects.ts +7 -0
  89. package/src/node/resources.ts +86 -0
  90. package/src/node/revision.ts +6 -0
  91. package/src/node/run-directory.ts +70 -0
  92. package/src/node/transcription-adoption.ts +9 -0
  93. package/src/node/transcription.ts +10 -0
  94. package/src/node/voiceover-routes.ts +73 -0
  95. package/src/node/voiceover.ts +199 -0
  96. package/src/page-source.ts +359 -0
  97. package/src/parameters.ts +81 -0
  98. package/src/preview-edit.ts +18 -0
  99. package/src/preview-events.ts +57 -0
  100. package/src/preview-frame.ts +1220 -0
  101. package/src/props.ts +17 -0
  102. package/src/push-stream.ts +67 -0
  103. package/src/reading.ts +12 -7
  104. package/src/recording.ts +38 -0
  105. package/src/renderer-components.ts +30 -0
  106. package/src/responses.ts +49 -0
  107. package/src/run-outputs.ts +15 -0
  108. package/src/runner.py +3 -3
  109. package/src/services.ts +6 -6
  110. package/src/slides.ts +36 -0
  111. package/src/speech-words.ts +76 -0
  112. package/src/speech.ts +13 -0
  113. package/src/step-keys.ts +38 -0
  114. package/src/steps.ts +7 -0
  115. package/src/transcription-adoption.ts +105 -0
  116. package/src/uri-decoding.ts +8 -0
  117. package/src/urls.ts +13 -0
  118. package/src/views.ts +47 -0
  119. package/src/voice.ts +86 -0
package/src/views.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { irBlocks } from './blocks.ts'
2
+ import type { IRBlock, IRDocument, IRSection } from './ir.ts'
3
+
4
+ /**
5
+ * Closes the gaps that removed blocks leave in a section's steps. With `stepMap`, the section records
6
+ * the new number of each old step, so positions in the whole document can be mapped to it.
7
+ */
8
+ export function compactSteps(section: IRSection, options: { stepMap?: boolean } = {}): IRSection {
9
+ // Explicit authoring steps can carry narration and production timing even
10
+ // when this renderer has no visual change. Keep their identities and states.
11
+ if (section.stepIds) return options.stepMap ? { ...section, stepMap: section.stepIds.map((_, index) => index) } : section
12
+ const used = new Set([0])
13
+ for (const block of irBlocks(section.blocks)) {
14
+ if (block.role !== 'visual') continue
15
+ for (let step = block.at; step <= block.at + (block.span ?? 0); step++) used.add(step)
16
+ if (block.until !== undefined) used.add(block.until)
17
+ }
18
+ if (used.size > section.steps) return section
19
+ const stepMap: number[] = []
20
+ for (let step = 0, n = -1; step <= section.steps; step++) stepMap.push(used.has(step) ? ++n : n)
21
+ const map = (step: number) => stepMap[Math.min(step, section.steps)]!
22
+ const renumber = (blocks: IRBlock[]): IRBlock[] => blocks.map(block => ({
23
+ ...block,
24
+ at: map(block.at),
25
+ ...block.until === undefined ? {} : { until: map(block.until) },
26
+ ...block.node.type === 'component' && block.node.children ? { node: { ...block.node, children: renumber(block.node.children) } } : {},
27
+ }))
28
+ return { ...section, steps: map(section.steps), ...options.stepMap ? { stepMap } : {}, blocks: renumber(section.blocks) }
29
+ }
30
+
31
+ /** The document as one renderer sees it: blocks limited to other renderers are gone, the steps closed up. */
32
+ export function viewFor(doc: IRDocument, renderer: string): IRDocument {
33
+ let changed = false
34
+ const keep = (blocks: IRBlock[]): IRBlock[] => blocks.flatMap((block) => {
35
+ if (block.only && !block.only.includes(renderer)) {
36
+ changed = true
37
+ return []
38
+ }
39
+ return block.node.type === 'component' && block.node.children ? [{ ...block, node: { ...block.node, children: keep(block.node.children) } }] : [block]
40
+ })
41
+ const sections = doc.sections.map((section) => {
42
+ changed = false
43
+ const blocks = keep(section.blocks)
44
+ return changed ? compactSteps({ ...section, blocks }, { stepMap: true }) : section
45
+ })
46
+ return { ...doc, sections }
47
+ }
package/src/voice.ts ADDED
@@ -0,0 +1,86 @@
1
+ import { irBlocks } from './blocks.ts'
2
+ import { isRecord } from './errors.ts'
3
+ import { fnv1a64 } from './fingerprint.ts'
4
+ import type { IRDocument, IRSection, MdastNode } from './ir.ts'
5
+ import { stepKeys } from './step-keys.ts'
6
+ import type { SpeechVoice } from './contracts/speech.ts'
7
+ import type { ResolvedVoice, VoiceOverrides, VoiceTarget } from './contracts/voice.ts'
8
+
9
+ export function speechVoice(value: unknown): SpeechVoice | undefined {
10
+ if (!isRecord(value) || typeof value.voice !== 'string' || !value.voice.trim()) return undefined
11
+ if (value.engine !== undefined && (typeof value.engine !== 'string' || !value.engine.trim())) return undefined
12
+ if (value.rate !== undefined && (typeof value.rate !== 'number' || !Number.isFinite(value.rate) || value.rate <= 0)) return undefined
13
+ if (value.style !== undefined && typeof value.style !== 'string') return undefined
14
+ return { voice: value.voice, ...typeof value.engine === 'string' ? { engine: value.engine } : {}, ...typeof value.rate === 'number' ? { rate: value.rate } : {}, ...typeof value.style === 'string' ? { style: value.style } : {} }
15
+ }
16
+
17
+ export function voiceTarget(section: Pick<IRSection, 'id' | 'steps' | 'stepIds' | 'blocks'>, step: number): VoiceTarget {
18
+ if (!Number.isSafeInteger(step) || step < 0 || step > section.steps) throw new Error('voice-target')
19
+ return { section: section.id, step, binding: fnv1a64(JSON.stringify([section.id, stepKeys(section)])) }
20
+ }
21
+
22
+ export function documentVoice(doc: IRDocument): SpeechVoice | undefined {
23
+ if (doc.production) return doc.production.defaults.voiceSource?.type === 'tts' ? doc.production.defaults.voiceSource.voice : undefined
24
+ return speechVoice(doc.config.renderers.lecture?.voice)
25
+ }
26
+
27
+ export function voiceOverrides(doc: IRDocument): VoiceOverrides {
28
+ if (doc.production) {
29
+ const production = doc.production
30
+ const pages = Object.fromEntries(Object.entries(production.sectionOverrides ?? {}).flatMap(([id, value]) => value.voiceSource?.type === 'tts' ? [[id, value.voiceSource.voice]] : []))
31
+ const steps = doc.sections.flatMap(section => (section.stepIds ?? []).flatMap((id, step) => {
32
+ const source = production.stepOverrides[id]?.voiceSource
33
+ return source?.type === 'tts' ? [{ ...voiceTarget(section, step), voice: source.voice }] : []
34
+ }))
35
+ return { pages, steps }
36
+ }
37
+ const raw = doc.config.renderers.lecture?.voiceOverrides
38
+ if (raw === undefined) return { pages: {}, steps: [] }
39
+ if (!isRecord(raw) || raw.pages !== undefined && !isRecord(raw.pages) || raw.steps !== undefined && !Array.isArray(raw.steps)) throw new Error('voice-settings-invalid')
40
+ const pages: Record<string, SpeechVoice> = Object.create(null)
41
+ for (const [section, value] of Object.entries(raw.pages ?? {})) {
42
+ const voice = speechVoice(value)
43
+ if (!voice) throw new Error('voice-settings-invalid')
44
+ pages[section] = voice
45
+ }
46
+ const steps: VoiceOverrides['steps'] = []
47
+ const used = new Set<string>()
48
+ for (const value of raw.steps ?? []) {
49
+ if (!isRecord(value) || typeof value.section !== 'string' || typeof value.step !== 'number' || !Number.isSafeInteger(value.step) || value.step < 0 || typeof value.binding !== 'string') throw new Error('voice-settings-invalid')
50
+ const voice = speechVoice(value.voice), key = JSON.stringify([value.section, value.step])
51
+ if (!voice || used.has(key)) throw new Error('voice-settings-invalid')
52
+ used.add(key)
53
+ steps.push({ section: value.section, step: value.step, binding: value.binding, voice })
54
+ }
55
+ return { pages, steps }
56
+ }
57
+
58
+ function advances(node: MdastNode): boolean {
59
+ if (node.type === 'presmiumRole' && node.plugin === 'presmium.lecture' && node.name === 'cue' && isRecord(node.options) && typeof node.options.value === 'string' && /^next(?:\s|$)/.test(node.options.value.trim())) return true
60
+ return node.children?.some(advances) ?? false
61
+ }
62
+
63
+ /** A cue can change the visual step inside one audio clip. That clip cannot yet take independent step voices. */
64
+ export function stepVoiceReason(doc: IRDocument, section: IRSection): ResolvedVoice['reason'] | undefined {
65
+ if ([...irBlocks(section.blocks)].some(block => block.role === 'narration' && block.node.type === 'markdown' && advances(block.node.mdast))) return 'cue-step'
66
+ const expected = voiceTarget(section, 0).binding
67
+ if (voiceOverrides(doc).steps.some(entry => entry.section === section.id && (entry.binding !== expected || entry.step > section.steps))) return 'step-binding-stale'
68
+ return undefined
69
+ }
70
+
71
+ export function resolvedVoice(doc: IRDocument, section: IRSection, step: number): ResolvedVoice {
72
+ if (doc.production) {
73
+ const target = voiceTarget(section, step), production = doc.production
74
+ const stepId = section.stepIds?.[step]
75
+ const own = stepId ? production.stepOverrides[stepId]?.voiceSource : undefined
76
+ const page = production.sectionOverrides?.[section.id]?.voiceSource
77
+ const source = own ?? page ?? production.defaults.voiceSource
78
+ const reason = source?.type === 'recording' ? 'recording-unavailable' as const : stepVoiceReason(doc, section)
79
+ return { target, ...source?.type === 'tts' ? { voice: source.voice, ...own ? { override: source.voice } : {} } : {}, inheritedFrom: own ? 'step' : page ? 'page' : source ? 'document' : 'unset', editable: !reason, ...reason ? { reason } : {} }
80
+ }
81
+ const target = voiceTarget(section, step), overrides = voiceOverrides(doc)
82
+ const reason = stepVoiceReason(doc, section)
83
+ const own = !reason && overrides.steps.find(entry => entry.section === section.id && entry.step === step && entry.binding === target.binding)?.voice
84
+ const page = overrides.pages[section.id], fallback = documentVoice(doc)
85
+ return { target, ...own || page || fallback ? { voice: own || page || fallback } : {}, inheritedFrom: own ? 'step' : page ? 'page' : fallback ? 'document' : 'unset', ...own ? { override: own } : {}, editable: !reason, ...reason ? { reason } : {} }
86
+ }