egaki 0.6.0 → 0.7.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 (271) hide show
  1. package/CHANGELOG.md +145 -9
  2. package/dist/cli/cache-utils.d.ts +33 -0
  3. package/dist/cli/cache-utils.d.ts.map +1 -0
  4. package/dist/cli/cache-utils.js +157 -0
  5. package/dist/cli/cached-generate.d.ts +105 -0
  6. package/dist/cli/cached-generate.d.ts.map +1 -0
  7. package/dist/cli/cached-generate.js +208 -0
  8. package/dist/cli/cached-generate.test.d.ts +11 -0
  9. package/dist/cli/cached-generate.test.d.ts.map +1 -0
  10. package/dist/cli/cached-generate.test.js +431 -0
  11. package/dist/cli/cartesia-provider.d.ts +16 -0
  12. package/dist/cli/cartesia-provider.d.ts.map +1 -0
  13. package/dist/cli/cartesia-provider.js +184 -0
  14. package/dist/cli/chatgpt-auth.d.ts.map +1 -0
  15. package/dist/cli/cli.d.ts +4 -0
  16. package/dist/cli/cli.d.ts.map +1 -0
  17. package/dist/{cli.js → cli/cli.js} +626 -600
  18. package/dist/cli/clone-generate.d.ts +26 -0
  19. package/dist/cli/clone-generate.d.ts.map +1 -0
  20. package/dist/cli/clone-generate.js +148 -0
  21. package/dist/cli/credentials.d.ts.map +1 -0
  22. package/dist/{credentials.js → cli/credentials.js} +20 -0
  23. package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
  24. package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
  25. package/dist/cli/elevenlabs-speech-provider.js +199 -0
  26. package/dist/cli/generate.d.ts +140 -0
  27. package/dist/cli/generate.d.ts.map +1 -0
  28. package/dist/cli/generate.js +604 -0
  29. package/dist/cli/generate.test.d.ts +2 -0
  30. package/dist/cli/generate.test.d.ts.map +1 -0
  31. package/dist/cli/generate.test.js +131 -0
  32. package/dist/cli/login.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts +3 -0
  34. package/dist/cli/main.d.ts.map +1 -0
  35. package/dist/cli/main.js +7 -0
  36. package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
  37. package/dist/cli/model-catalog.d.ts.map +1 -0
  38. package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
  39. package/dist/{models.d.ts → cli/models.d.ts} +12 -5
  40. package/dist/cli/models.d.ts.map +1 -0
  41. package/dist/{models.js → cli/models.js} +58 -57
  42. package/dist/cli/open-browser.d.ts.map +1 -0
  43. package/dist/cli/openai-speech-provider.d.ts +3 -0
  44. package/dist/cli/openai-speech-provider.d.ts.map +1 -0
  45. package/dist/cli/openai-speech-provider.js +28 -0
  46. package/dist/cli/separation-generate.d.ts +34 -0
  47. package/dist/cli/separation-generate.d.ts.map +1 -0
  48. package/dist/cli/separation-generate.js +117 -0
  49. package/dist/cli/speech-catalog.d.ts +35 -0
  50. package/dist/cli/speech-catalog.d.ts.map +1 -0
  51. package/dist/cli/speech-catalog.js +195 -0
  52. package/dist/cli/speech-generate.d.ts +86 -0
  53. package/dist/cli/speech-generate.d.ts.map +1 -0
  54. package/dist/cli/speech-generate.js +156 -0
  55. package/dist/cli/speech-models.d.ts +8 -0
  56. package/dist/cli/speech-models.d.ts.map +1 -0
  57. package/dist/cli/speech-models.js +109 -0
  58. package/dist/cli/speech-provider.test.d.ts +2 -0
  59. package/dist/cli/speech-provider.test.d.ts.map +1 -0
  60. package/dist/cli/speech-provider.test.js +197 -0
  61. package/dist/cli/subscription.d.ts.map +1 -0
  62. package/dist/cli/transcription-catalog.d.ts +35 -0
  63. package/dist/cli/transcription-catalog.d.ts.map +1 -0
  64. package/dist/cli/transcription-catalog.js +192 -0
  65. package/dist/cli/transcription-generate.d.ts +62 -0
  66. package/dist/cli/transcription-generate.d.ts.map +1 -0
  67. package/dist/cli/transcription-generate.js +144 -0
  68. package/dist/cli/transcription-models.d.ts +9 -0
  69. package/dist/cli/transcription-models.d.ts.map +1 -0
  70. package/dist/cli/transcription-models.js +100 -0
  71. package/dist/cli/transcription-providers.d.ts +22 -0
  72. package/dist/cli/transcription-providers.d.ts.map +1 -0
  73. package/dist/cli/transcription-providers.js +290 -0
  74. package/dist/cli/video-model-catalog.d.ts.map +1 -0
  75. package/dist/cli/xai-auth.d.ts.map +1 -0
  76. package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
  77. package/dist/vite/angled-screen.d.ts +57 -0
  78. package/dist/vite/angled-screen.d.ts.map +1 -0
  79. package/dist/vite/angled-screen.js +115 -0
  80. package/dist/vite/app.d.ts +51 -0
  81. package/dist/vite/app.d.ts.map +1 -0
  82. package/dist/vite/app.js +150 -0
  83. package/dist/vite/bands-shader.d.ts +47 -0
  84. package/dist/vite/bands-shader.d.ts.map +1 -0
  85. package/dist/vite/bands-shader.js +267 -0
  86. package/dist/vite/code-block.d.ts +107 -0
  87. package/dist/vite/code-block.d.ts.map +1 -0
  88. package/dist/vite/code-block.js +641 -0
  89. package/dist/vite/components.d.ts +36 -0
  90. package/dist/vite/components.d.ts.map +1 -0
  91. package/dist/vite/components.js +113 -0
  92. package/dist/vite/dispersion-rings-shader.d.ts +44 -0
  93. package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
  94. package/dist/vite/dispersion-rings-shader.js +282 -0
  95. package/dist/vite/easing-curves.d.ts +129 -0
  96. package/dist/vite/easing-curves.d.ts.map +1 -0
  97. package/dist/vite/easing-curves.js +584 -0
  98. package/dist/vite/easing-curves.test.d.ts +2 -0
  99. package/dist/vite/easing-curves.test.d.ts.map +1 -0
  100. package/dist/vite/easing-curves.test.js +200 -0
  101. package/dist/vite/generated-media-client.d.ts +35 -0
  102. package/dist/vite/generated-media-client.d.ts.map +1 -0
  103. package/dist/vite/generated-media-client.js +76 -0
  104. package/dist/vite/keyframes.d.ts +129 -0
  105. package/dist/vite/keyframes.d.ts.map +1 -0
  106. package/dist/vite/keyframes.js +194 -0
  107. package/dist/vite/layout-editor.d.ts +45 -0
  108. package/dist/vite/layout-editor.d.ts.map +1 -0
  109. package/dist/vite/layout-editor.js +587 -0
  110. package/dist/vite/layout-transition.d.ts +136 -0
  111. package/dist/vite/layout-transition.d.ts.map +1 -0
  112. package/dist/vite/layout-transition.js +581 -0
  113. package/dist/vite/liquid-gradient-shader.d.ts +62 -0
  114. package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
  115. package/dist/vite/liquid-gradient-shader.js +413 -0
  116. package/dist/vite/mdx-client.d.ts +15 -0
  117. package/dist/vite/mdx-client.d.ts.map +1 -0
  118. package/dist/vite/mdx-client.js +265 -0
  119. package/dist/vite/mdx-parse.d.ts +102 -0
  120. package/dist/vite/mdx-parse.d.ts.map +1 -0
  121. package/dist/vite/mdx-parse.js +251 -0
  122. package/dist/vite/mdx-provided-components.d.ts +18 -0
  123. package/dist/vite/mdx-provided-components.d.ts.map +1 -0
  124. package/dist/vite/mdx-provided-components.js +14 -0
  125. package/dist/vite/mdx-video.d.ts +327 -0
  126. package/dist/vite/mdx-video.d.ts.map +1 -0
  127. package/dist/vite/mdx-video.js +596 -0
  128. package/dist/vite/mdx-video.test.d.ts +9 -0
  129. package/dist/vite/mdx-video.test.d.ts.map +1 -0
  130. package/dist/vite/mdx-video.test.js +1945 -0
  131. package/dist/vite/media-components.d.ts +45 -0
  132. package/dist/vite/media-components.d.ts.map +1 -0
  133. package/dist/vite/media-components.js +355 -0
  134. package/dist/vite/media-duration-store.d.ts +66 -0
  135. package/dist/vite/media-duration-store.d.ts.map +1 -0
  136. package/dist/vite/media-duration-store.js +204 -0
  137. package/dist/vite/player-page.d.ts +35 -0
  138. package/dist/vite/player-page.d.ts.map +1 -0
  139. package/dist/vite/player-page.js +720 -0
  140. package/dist/vite/render-client.d.ts +18 -0
  141. package/dist/vite/render-client.d.ts.map +1 -0
  142. package/dist/vite/render-client.js +67 -0
  143. package/dist/vite/sdk.d.ts +221 -0
  144. package/dist/vite/sdk.d.ts.map +1 -0
  145. package/dist/vite/sdk.js +355 -0
  146. package/dist/vite/server-components.d.ts +55 -0
  147. package/dist/vite/server-components.d.ts.map +1 -0
  148. package/dist/vite/server-components.js +156 -0
  149. package/dist/vite/server-mdx.d.ts +74 -0
  150. package/dist/vite/server-mdx.d.ts.map +1 -0
  151. package/dist/vite/server-mdx.js +259 -0
  152. package/dist/vite/shader-renderer.d.ts +48 -0
  153. package/dist/vite/shader-renderer.d.ts.map +1 -0
  154. package/dist/vite/shader-renderer.js +411 -0
  155. package/dist/vite/store-hooks.d.ts +12 -0
  156. package/dist/vite/store-hooks.d.ts.map +1 -0
  157. package/dist/vite/store-hooks.js +93 -0
  158. package/dist/vite/store.d.ts +75 -0
  159. package/dist/vite/store.d.ts.map +1 -0
  160. package/dist/vite/store.js +52 -0
  161. package/dist/vite/store.test.d.ts +6 -0
  162. package/dist/vite/store.test.d.ts.map +1 -0
  163. package/dist/vite/store.test.js +172 -0
  164. package/dist/vite/tweakpane-hook.d.ts +94 -0
  165. package/dist/vite/tweakpane-hook.d.ts.map +1 -0
  166. package/dist/vite/tweakpane-hook.js +450 -0
  167. package/dist/vite/vite-plugin.d.ts +28 -0
  168. package/dist/vite/vite-plugin.d.ts.map +1 -0
  169. package/dist/vite/vite-plugin.js +539 -0
  170. package/dist/vite/wave-gradient-shader.d.ts +41 -0
  171. package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
  172. package/dist/vite/wave-gradient-shader.js +207 -0
  173. package/package.json +101 -24
  174. package/skills/egaki/SKILL.md +162 -1
  175. package/src/cli/cache-utils.ts +169 -0
  176. package/src/cli/cached-generate.test.ts +531 -0
  177. package/src/cli/cached-generate.ts +348 -0
  178. package/src/cli/cartesia-provider.ts +249 -0
  179. package/src/{cli.ts → cli/cli.ts} +836 -884
  180. package/src/cli/clone-generate.ts +202 -0
  181. package/src/{credentials.ts → cli/credentials.ts} +20 -0
  182. package/src/cli/elevenlabs-speech-provider.ts +243 -0
  183. package/src/cli/generate.test.ts +166 -0
  184. package/src/cli/generate.ts +912 -0
  185. package/src/cli/main.ts +8 -0
  186. package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
  187. package/src/{models.ts → cli/models.ts} +65 -86
  188. package/src/cli/openai-speech-provider.ts +34 -0
  189. package/src/cli/separation-generate.ts +187 -0
  190. package/src/cli/speech-catalog.ts +253 -0
  191. package/src/cli/speech-generate.ts +275 -0
  192. package/src/cli/speech-models.ts +146 -0
  193. package/src/cli/speech-provider.test.ts +226 -0
  194. package/src/cli/transcription-catalog.ts +250 -0
  195. package/src/cli/transcription-generate.ts +214 -0
  196. package/src/cli/transcription-models.ts +136 -0
  197. package/src/cli/transcription-providers.ts +427 -0
  198. package/src/{xai-auth.ts → cli/xai-auth.ts} +1 -1
  199. package/src/vite/angled-screen.tsx +214 -0
  200. package/src/vite/app.tsx +197 -0
  201. package/src/vite/bands-shader.tsx +319 -0
  202. package/src/vite/code-block.tsx +939 -0
  203. package/src/vite/components.tsx +278 -0
  204. package/src/vite/dispersion-rings-shader.tsx +331 -0
  205. package/src/vite/easing-curves.test.ts +232 -0
  206. package/src/vite/easing-curves.ts +743 -0
  207. package/src/vite/generated-media-client.tsx +121 -0
  208. package/src/vite/keyframes.tsx +347 -0
  209. package/src/vite/layout-editor.tsx +709 -0
  210. package/src/vite/layout-transition.tsx +706 -0
  211. package/src/vite/liquid-gradient-shader.tsx +474 -0
  212. package/src/vite/mdx-client.tsx +420 -0
  213. package/src/vite/mdx-parse.ts +343 -0
  214. package/src/vite/mdx-provided-components.ts +19 -0
  215. package/src/vite/mdx-video.test.tsx +2219 -0
  216. package/src/vite/mdx-video.tsx +976 -0
  217. package/src/vite/media-components.tsx +473 -0
  218. package/src/vite/media-duration-store.ts +225 -0
  219. package/src/vite/motion-timing.ts +194 -0
  220. package/src/vite/player-page.tsx +1107 -0
  221. package/src/vite/render-client.ts +84 -0
  222. package/src/vite/sdk.ts +598 -0
  223. package/src/vite/server-components.tsx +241 -0
  224. package/src/vite/server-mdx.ts +285 -0
  225. package/src/vite/shader-renderer.tsx +535 -0
  226. package/src/vite/store-hooks.ts +117 -0
  227. package/src/vite/store.test.ts +202 -0
  228. package/src/vite/store.ts +115 -0
  229. package/src/vite/styles.css +21 -0
  230. package/src/vite/tweakpane-hook.tsx +593 -0
  231. package/src/vite/virtual-modules.d.ts +30 -0
  232. package/src/vite/vite-plugin.ts +585 -0
  233. package/src/vite/wave-gradient-shader.tsx +253 -0
  234. package/README.md +0 -370
  235. package/dist/antigravity-auth.d.ts +0 -15
  236. package/dist/antigravity-auth.d.ts.map +0 -1
  237. package/dist/antigravity-auth.js +0 -544
  238. package/dist/antigravity-image.d.ts +0 -11
  239. package/dist/antigravity-image.d.ts.map +0 -1
  240. package/dist/antigravity-image.js +0 -285
  241. package/dist/chatgpt-auth.d.ts.map +0 -1
  242. package/dist/cli.d.ts +0 -3
  243. package/dist/cli.d.ts.map +0 -1
  244. package/dist/credentials.d.ts.map +0 -1
  245. package/dist/gateway-model-candidates.generated.d.ts +0 -11
  246. package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
  247. package/dist/gateway-model-candidates.generated.js +0 -64
  248. package/dist/login.d.ts.map +0 -1
  249. package/dist/model-catalog.d.ts.map +0 -1
  250. package/dist/models.d.ts.map +0 -1
  251. package/dist/open-browser.d.ts.map +0 -1
  252. package/dist/subscription.d.ts.map +0 -1
  253. package/dist/video-model-catalog.d.ts.map +0 -1
  254. package/dist/xai-auth.d.ts.map +0 -1
  255. /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
  256. /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
  257. /package/dist/{credentials.d.ts → cli/credentials.d.ts} +0 -0
  258. /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
  259. /package/dist/{login.js → cli/login.js} +0 -0
  260. /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
  261. /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
  262. /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
  263. /package/dist/{subscription.js → cli/subscription.js} +0 -0
  264. /package/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
  265. /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
  266. /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
  267. /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
  268. /package/src/{login.ts → cli/login.ts} +0 -0
  269. /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
  270. /package/src/{subscription.ts → cli/subscription.ts} +0 -0
  271. /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Server components for AI media generation in MDX <Server> blocks.
3
+ *
4
+ * No 'use client' directive: these execute in the RSC environment (async,
5
+ * filesystem access, API calls). They are auto-wrapped in <Server> by
6
+ * wrapGenerateNodes() in server-mdx.ts, so users write them bare in MDX.
7
+ *
8
+ * Each component delegates to the cached generate functions from egaki/generate
9
+ * (which handle caching, deduplication, stale management, and progress tracking
10
+ * via cachedGenerate) and passes the resolved URL to the client wrapper component.
11
+ *
12
+ * The server components add a thin layer on top for:
13
+ * - Composition-aware defaults (aspect ratio from dimensions)
14
+ * - Asset path resolution (string paths → Uint8Array)
15
+ * - Fallback lookup for RSC streaming (show stale while generating)
16
+ * - RSC flight streaming (pass promise to client wrapper)
17
+ */
18
+
19
+ import fs from 'node:fs'
20
+ import path from 'node:path'
21
+ import type { ComponentProps } from 'react'
22
+ import type { GenerateImageOptions } from '../cli/generate.js'
23
+ import type { GenerateVideoOptions } from '../cli/generate.js'
24
+ import type { GenerateSpeechOptions } from '../cli/speech-generate.js'
25
+ import { CATALOG, VIDEO_CATALOG } from '../cli/model-catalog.js'
26
+ import { DEFAULT_MODEL, DEFAULT_VIDEO_MODEL } from '../cli/models.js'
27
+ import { aspectRatioFromDimensions } from './mdx-parse.ts'
28
+ import {
29
+ GeneratedImageClient,
30
+ GeneratedVideoClient,
31
+ GeneratedSpeechClient,
32
+ } from './generated-media-client.tsx'
33
+ import type { Img, Audio, Video } from './mdx-video.tsx'
34
+ import { setProjectRoot } from '../cli/cache-utils.js'
35
+
36
+ // Re-export progress tracking from the centralized module
37
+ export {
38
+ getGenerationProgress,
39
+ onProgressChange,
40
+ type GenerationProgressEvent,
41
+ type GenerationProgressSummary,
42
+ type GenerationProgressEntry,
43
+ type GenerationError,
44
+ type GenerationEntry,
45
+ } from '../cli/cached-generate.js'
46
+
47
+ // Re-export caching utilities so existing imports from 'egaki/generate-media' still work
48
+ export { stableJsonKey, hashKey, promptPrefix, findCachedFile, findFallbackFile } from '../cli/cache-utils.js'
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Project root and composition dimensions from Vite virtual module.
52
+ // setProjectRoot() is called here so the cached generate functions
53
+ // in egaki/generate know where public/generated/ is.
54
+ // ---------------------------------------------------------------------------
55
+
56
+ let _initialized = false
57
+ async function ensureInit() {
58
+ if (_initialized) return
59
+ const mod = await import('virtual:egaki-mdx')
60
+ setProjectRoot(mod.projectRoot)
61
+ _initialized = true
62
+ }
63
+
64
+ async function getCompositionDimensions(): Promise<{ width: number; height: number }> {
65
+ const mod = await import('virtual:egaki-mdx')
66
+ return { width: mod.compositionWidth, height: mod.compositionHeight }
67
+ }
68
+
69
+ /** Look up a model's supported aspect ratios from the catalog. */
70
+ function getModelAspectRatios(modelId: string, type: 'image' | 'video'): string[] | undefined {
71
+ if (type === 'image') {
72
+ const entry = CATALOG.find((m) => m.id === modelId)
73
+ return entry?.features.aspectRatios
74
+ }
75
+ const entry = VIDEO_CATALOG.find((m) => m.id === modelId)
76
+ return entry?.features.aspectRatios ?? undefined
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Asset path resolution — paths starting with `/` resolve to the project's
81
+ // public/ folder (Vite convention), then fall back to absolute/relative paths.
82
+ // ---------------------------------------------------------------------------
83
+
84
+ /** Resolve a path prop to an absolute filesystem path.
85
+ * `/photo.png` → `{projectRoot}/public/photo.png` if the file exists.
86
+ * Relative paths resolve against projectRoot.
87
+ * Absolute paths and URLs pass through unchanged. */
88
+ export async function resolveAssetPath(p: string): Promise<string> {
89
+ await ensureInit()
90
+ const { getProjectRoot } = await import('../cli/cache-utils.js')
91
+ const root = getProjectRoot()
92
+ if (p.startsWith('http://') || p.startsWith('https://')) return p
93
+ if (p.startsWith('/')) {
94
+ const publicDir = path.join(root, 'public')
95
+ const publicPath = path.resolve(publicDir, '.' + p)
96
+ if (publicPath.startsWith(publicDir + path.sep) && fs.existsSync(publicPath)) {
97
+ return publicPath
98
+ }
99
+ }
100
+ if (path.isAbsolute(p)) return p
101
+ return path.resolve(root, p)
102
+ }
103
+
104
+ /** Resolve a path and read it as bytes. */
105
+ export async function readAssetBytes(p: string): Promise<Uint8Array> {
106
+ const resolved = await resolveAssetPath(p)
107
+ if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
108
+ const res = await fetch(resolved)
109
+ if (!res.ok) throw new Error(`Failed to fetch ${resolved}: ${res.status}`)
110
+ return new Uint8Array(await res.arrayBuffer())
111
+ }
112
+ return new Uint8Array(fs.readFileSync(resolved))
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // GeneratedImage
117
+ // ---------------------------------------------------------------------------
118
+
119
+ type GeneratedImageGenProps = Omit<GenerateImageOptions, 'count' | 'inputImages' | 'maskImage'> & {
120
+ inputImages?: string[]
121
+ maskImage?: string
122
+ }
123
+
124
+ export type GeneratedImageProps = GeneratedImageGenProps & Omit<ComponentProps<typeof Img>, 'src'>
125
+
126
+ export async function GeneratedImage({ inputImages: inputImagePaths, maskImage: maskImagePath, ...props }: GeneratedImageProps) {
127
+ await ensureInit()
128
+ const { prompt, model, seed, aspectRatio: aspectRatioProp, quality, resolution, outputFormat, negativePrompt, allowPeople, imageSize, ...passthrough } = props
129
+
130
+ const { width, height } = await getCompositionDimensions()
131
+ const allowedRatios = getModelAspectRatios(model ?? DEFAULT_MODEL, 'image')
132
+ const aspectRatio = aspectRatioProp ?? (
133
+ allowedRatios?.length ? aspectRatioFromDimensions(width, height, allowedRatios) : undefined
134
+ )
135
+ const inputImages = inputImagePaths ? await Promise.all(inputImagePaths.map(readAssetBytes)) : undefined
136
+ const maskImage = maskImagePath ? await readAssetBytes(maskImagePath) : undefined
137
+
138
+ const genParams = { prompt, model, seed, aspectRatio, quality, resolution, outputFormat, negativePrompt, allowPeople, imageSize, inputImages, maskImage }
139
+
140
+ // Sync cache check + fallback lookup for RSC streaming.
141
+ // Uses the wrapper's getCacheInfo() so the same cacheKey config is applied.
142
+ const { generateImage } = await import('../cli/generate.js')
143
+ const cacheInfo = generateImage.getCacheInfo(genParams)
144
+ if (cacheInfo.cached) {
145
+ return <GeneratedImageClient srcPromise={Promise.resolve(`/generated/image/${cacheInfo.cached}`)} {...passthrough} />
146
+ }
147
+
148
+ const srcPromise = generateImage(genParams)
149
+ .then((result) => {
150
+ if (result instanceof Error) throw result
151
+ return result.src
152
+ })
153
+
154
+ return <GeneratedImageClient srcPromise={srcPromise} fallbackSrc={cacheInfo.fallbackSrc} {...passthrough} />
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // GeneratedVideo
159
+ // ---------------------------------------------------------------------------
160
+
161
+ type GeneratedVideoGenProps = Omit<GenerateVideoOptions, 'count' | 'inputImage'> & {
162
+ inputImage?: string
163
+ }
164
+
165
+ export type GeneratedVideoProps = GeneratedVideoGenProps & Omit<ComponentProps<typeof Video>, 'src'>
166
+
167
+ export async function GeneratedVideo({ inputImage: inputImagePath, ...props }: GeneratedVideoProps) {
168
+ await ensureInit()
169
+ const { prompt, model, seed, aspectRatio: aspectRatioProp, resolution, duration, fps, negativePrompt, mode, videoUrl, referenceImages, ...passthrough } = props
170
+
171
+ const inputImage = inputImagePath ? await readAssetBytes(inputImagePath) : undefined
172
+ const { width, height } = await getCompositionDimensions()
173
+ const allowedRatios = getModelAspectRatios(model ?? DEFAULT_VIDEO_MODEL, 'video')
174
+ const aspectRatio = aspectRatioProp ?? (
175
+ allowedRatios?.length ? aspectRatioFromDimensions(width, height, allowedRatios) : undefined
176
+ )
177
+
178
+ const genParams = { prompt, model, seed, aspectRatio, resolution, duration, fps, negativePrompt, inputImage, mode, videoUrl, referenceImages }
179
+
180
+ const { generateVideo } = await import('../cli/generate.js')
181
+ const cacheInfo = generateVideo.getCacheInfo(genParams)
182
+ if (cacheInfo.cached) {
183
+ return <GeneratedVideoClient srcPromise={Promise.resolve(`/generated/video/${cacheInfo.cached}`)} {...passthrough} />
184
+ }
185
+
186
+ const srcPromise = generateVideo(genParams)
187
+ .then((result) => {
188
+ if (result instanceof Error) throw result
189
+ return result.src
190
+ })
191
+
192
+ return <GeneratedVideoClient srcPromise={srcPromise} fallbackSrc={cacheInfo.fallbackSrc} {...passthrough} />
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // GeneratedSpeech
197
+ // ---------------------------------------------------------------------------
198
+
199
+ type GeneratedSpeechGenProps = GenerateSpeechOptions & {
200
+ seed?: number
201
+ }
202
+
203
+ export type GeneratedSpeechProps = GeneratedSpeechGenProps & Omit<ComponentProps<typeof Audio>, 'src'>
204
+
205
+ export async function GeneratedSpeech(props: GeneratedSpeechProps) {
206
+ await ensureInit()
207
+ const { text, model, voice, outputFormat, instructions, speed, language, seed, ...passthrough } = props
208
+
209
+ const genParams = { text, model, voice, outputFormat, instructions, speed, language, seed }
210
+
211
+ const { generateSpeech } = await import('../cli/speech-generate.js')
212
+ const cacheInfo = generateSpeech.getCacheInfo(genParams)
213
+ if (cacheInfo.cached) {
214
+ return <GeneratedSpeechClient srcPromise={Promise.resolve(`/generated/audio/${cacheInfo.cached}`)} {...passthrough} />
215
+ }
216
+
217
+ const srcPromise = generateSpeech(genParams)
218
+ .then((result) => {
219
+ if (result instanceof Error) throw result
220
+ return result.src
221
+ })
222
+
223
+ return <GeneratedSpeechClient srcPromise={srcPromise} fallbackSrc={cacheInfo.fallbackSrc} {...passthrough} />
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // TextToSpeech (legacy alias for GeneratedSpeech, kept for backwards compat)
228
+ // ---------------------------------------------------------------------------
229
+
230
+ interface TextToSpeechProps {
231
+ /** Text to synthesize. */
232
+ text: string
233
+ /** Voice preset. */
234
+ voice?: string
235
+ }
236
+
237
+ export async function TextToSpeech({ text, voice = 'alloy' }: TextToSpeechProps) {
238
+ return GeneratedSpeech({ text, voice })
239
+ }
240
+
241
+
@@ -0,0 +1,285 @@
1
+ /**
2
+ * <Server> MDX parsing utilities.
3
+ *
4
+ * <Server> is a reserved MDX element marking a subtree as React Server
5
+ * Components. This module contains the pure parsing logic shared by the
6
+ * server (app.tsx), the client (mdx-client.tsx), and the vite plugin:
7
+ *
8
+ * - findServerNodes: locate <Server> elements, keyed by start line
9
+ * - blankServerContents: strip server content from the client-facing MDX
10
+ * string while preserving line positions
11
+ * - collectServerImportSources: detect which MDX imports <Server> blocks
12
+ * need (app.tsx dynamically imports them at request time)
13
+ * - filterImportNodesToModules: per-statement import filtering against an
14
+ * environment's modules map
15
+ *
16
+ * Environment-neutral: no remotion, react, or node-only imports.
17
+ */
18
+
19
+ import { resolveModulePath } from 'safe-mdx/parse'
20
+
21
+ // Inline mdast type to avoid requiring @types/mdast as a dependency
22
+ type RootContent = any
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // <Server> slot extraction
26
+ //
27
+ // The server (app.tsx) renders each Server node's children in the RSC
28
+ // environment and passes the resulting JSX to the client keyed by the
29
+ // node's start line. The client (mdx-client.tsx) has a real `Server`
30
+ // component in its components map that reads ServerSlotsContext and
31
+ // matches its slot via the data-markdown-line prop safe-mdx injects.
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export interface ServerNode {
35
+ /** Slot key: the node's start LINE as a string. Stable across server
36
+ * and client because both parse the same source string
37
+ * (blankServerContents preserves all line positions). Line-only (no
38
+ * column) because the client matches slots via the data-markdown-line
39
+ * prop that safe-mdx injects, which only carries the line. Two <Server>
40
+ * elements on the same line collide — app.tsx warns about duplicates. */
41
+ key: string
42
+ node: RootContent
43
+ }
44
+
45
+ function serverNodeKey(node: RootContent): string {
46
+ return String(node.position?.start?.line)
47
+ }
48
+
49
+ /** Recursively find <Server> elements in the mdast. Server nodes nested
50
+ * inside another <Server> are skipped — the outer slot covers them. */
51
+ export function findServerNodes(mdast: { children?: RootContent[] }): ServerNode[] {
52
+ const found: ServerNode[] = []
53
+ const visit = (node: RootContent) => {
54
+ const isServer =
55
+ (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement')
56
+ && node.name === 'Server'
57
+ if (isServer) {
58
+ found.push({ key: serverNodeKey(node), node })
59
+ return // don't descend — nested Server is part of the outer slot
60
+ }
61
+ for (const child of node.children || []) visit(child)
62
+ }
63
+ for (const child of mdast.children || []) visit(child)
64
+ return found
65
+ }
66
+
67
+ /** Replace each <Server> element's source range with a self-closing
68
+ * `<Server />` marker, padded with newlines so the result has the EXACT
69
+ * same line count and all subsequent positions stay valid. This keeps
70
+ * slot keys, data-markdown-line attributes, and sourcemaps aligned with
71
+ * the original file, and prevents the client from parsing (and trying to
72
+ * resolve imports for) server-only content. */
73
+ export function blankServerContents(source: string, serverNodes: ServerNode[]): string {
74
+ // Process back-to-front so earlier offsets stay valid after each splice.
75
+ const sorted = [...serverNodes].sort(
76
+ (a, b) => (b.node.position?.start?.offset ?? 0) - (a.node.position?.start?.offset ?? 0),
77
+ )
78
+ let result = source
79
+ for (const { node } of sorted) {
80
+ const start = node.position?.start?.offset
81
+ const end = node.position?.end?.offset
82
+ if (typeof start !== 'number' || typeof end !== 'number') continue
83
+ const span = result.slice(start, end)
84
+ const newlines = span.split('\n').length - 1
85
+ const replacement = '<Server />' + '\n'.repeat(newlines)
86
+ result = result.slice(0, start) + replacement + result.slice(end)
87
+ }
88
+ return result
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Server import detection
93
+ //
94
+ // Detects which MDX imports are needed by <Server> slot rendering by
95
+ // scanning identifier usage inside <Server> subtrees. app.tsx dynamically
96
+ // imports exactly these sources at request time — no filename convention
97
+ // required (though *.server.* remains supported as a hard "never bundle
98
+ // to the browser" override in the vite plugin).
99
+ //
100
+ // Detection is name-based and intentionally COARSE: it collects JSX
101
+ // element names (root identifier of <ns.Comp/>) plus every estree
102
+ // Identifier in expressions and attribute values. Over-collection just
103
+ // imports an extra file server-side; under-collection would break slot
104
+ // rendering, so coarse is the safe direction.
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /** Collect every estree Identifier name reachable from a value. */
108
+ function collectEstreeIdentifiers(value: any, out: Set<string>) {
109
+ if (!value || typeof value !== 'object') return
110
+ if (Array.isArray(value)) {
111
+ for (const item of value) collectEstreeIdentifiers(item, out)
112
+ return
113
+ }
114
+ if (value.type === 'Identifier' && typeof value.name === 'string') {
115
+ out.add(value.name)
116
+ }
117
+ for (const key of Object.keys(value)) {
118
+ if (key === 'position' || key === 'loc' || key === 'range' || key === 'start' || key === 'end') continue
119
+ collectEstreeIdentifiers(value[key], out)
120
+ }
121
+ }
122
+
123
+ /** Collect identifier usage from a single mdast node into a bucket:
124
+ * JSX element names plus identifiers in expressions/attribute values. */
125
+ function collectNodeIdentifiers(node: any, out: Set<string>) {
126
+ if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
127
+ if (typeof node.name === 'string' && node.name) {
128
+ out.add(node.name.split('.')[0]!)
129
+ }
130
+ for (const attr of node.attributes || []) {
131
+ if (attr.type === 'mdxJsxExpressionAttribute') {
132
+ collectEstreeIdentifiers(attr.data?.estree, out)
133
+ } else if (attr.value && typeof attr.value === 'object') {
134
+ collectEstreeIdentifiers(attr.value.data?.estree, out)
135
+ }
136
+ }
137
+ }
138
+ if (node.type === 'mdxFlowExpression' || node.type === 'mdxTextExpression') {
139
+ collectEstreeIdentifiers(node.data?.estree, out)
140
+ }
141
+ }
142
+
143
+ /** Collect MDX import sources whose names are used inside <Server>
144
+ * subtrees. Includes bare specifiers (e.g. 'egaki/text-to-speech') —
145
+ * app.tsx imports them through vite's resolver, and the resolved file's
146
+ * 'use client' directive (or lack of it) decides whether the exports are
147
+ * client refs or server components. */
148
+ export function collectServerImportSources(mdast: { children?: RootContent[] }): string[] {
149
+ const insideNames = new Set<string>()
150
+
151
+ const visit = (node: RootContent, insideServer: boolean) => {
152
+ const isServer =
153
+ (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement')
154
+ && node.name === 'Server'
155
+ const nowInside = insideServer || isServer
156
+ if (nowInside && !isServer) {
157
+ // The <Server> element itself contributes no identifiers.
158
+ collectNodeIdentifiers(node, insideNames)
159
+ }
160
+ for (const child of node.children || []) visit(child, nowInside)
161
+ }
162
+ for (const child of mdast.children || []) visit(child, false)
163
+
164
+ const sources = new Set<string>()
165
+ for (const node of mdast.children || []) {
166
+ if (node.type !== 'mdxjsEsm') continue
167
+ const body = node.data?.estree?.body || []
168
+ for (const stmt of body) {
169
+ if (stmt.type !== 'ImportDeclaration') continue
170
+ const source = stmt.source?.value
171
+ if (typeof source !== 'string') continue
172
+ const locals: string[] = (stmt.specifiers || [])
173
+ .map((s: any) => s.local?.name)
174
+ .filter(Boolean)
175
+ if (locals.some((name) => insideNames.has(name))) sources.add(source)
176
+ }
177
+ }
178
+ return [...sources].sort()
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Auto-wrap generated media components in <Server>
183
+ //
184
+ // <GeneratedImage>, <GeneratedVideo>, <GeneratedSpeech> are server components
185
+ // that call egaki's generation APIs. When found bare in the mdast (not inside
186
+ // a <Server> block), this transform wraps them in a synthetic <Server> node
187
+ // so findServerNodes() picks them up. The wrapper reuses the original node's
188
+ // position so line numbers stay stable and no sourcemap fixup is needed.
189
+ // ---------------------------------------------------------------------------
190
+
191
+ /** Component names that should be auto-wrapped in <Server>. */
192
+ export const GENERATED_COMPONENT_NAMES = new Set([
193
+ 'GeneratedImage',
194
+ 'GeneratedVideo',
195
+ 'GeneratedSpeech',
196
+ ])
197
+
198
+ /** Collect local names imported from *.server.* sources in the MDX.
199
+ * These components should be auto-wrapped in <Server> because the
200
+ * .server postfix signals they run in the RSC environment. */
201
+ export function collectServerFileImportNames(mdast: { children?: RootContent[] }): Set<string> {
202
+ const names = new Set<string>()
203
+ for (const node of mdast.children || []) {
204
+ if (node.type !== 'mdxjsEsm') continue
205
+ for (const stmt of node.data?.estree?.body || []) {
206
+ if (stmt.type !== 'ImportDeclaration') continue
207
+ const source = stmt.source?.value
208
+ if (typeof source !== 'string') continue
209
+ if (!/\.server(\.[jt]sx?)?$/.test(source)) continue
210
+ for (const spec of stmt.specifiers || []) {
211
+ if (spec.local?.name) names.add(spec.local.name)
212
+ }
213
+ }
214
+ }
215
+ return names
216
+ }
217
+
218
+ /** Walk the mdast and wrap bare server components in <Server>.
219
+ * Targets two categories:
220
+ * 1. Built-in generated media components (GeneratedImage, etc.)
221
+ * 2. Components imported from *.server.* files
222
+ * Mutates the tree in place. Wrapping reuses the original node's position
223
+ * so slot keys and line numbers are unchanged. */
224
+ export function wrapGenerateNodes(mdast: { children?: RootContent[] }): void {
225
+ const serverImportNames = collectServerFileImportNames(mdast)
226
+ const shouldWrap = (name: string) =>
227
+ GENERATED_COMPONENT_NAMES.has(name) || serverImportNames.has(name)
228
+
229
+ const wrapInParent = (parent: { children?: RootContent[] }) => {
230
+ if (!parent.children) return
231
+ for (let i = 0; i < parent.children.length; i++) {
232
+ const node = parent.children[i]
233
+ // Use root identifier for dotted names like <Scenes.Hero /> → "Scenes"
234
+ const jsxRootName = typeof node.name === 'string' ? node.name.split('.')[0]! : ''
235
+ if (
236
+ (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement')
237
+ && shouldWrap(jsxRootName)
238
+ ) {
239
+ // Wrap in a synthetic <Server> with the same position so slot
240
+ // keying (by start line) stays identical and blankServerContents
241
+ // replaces the right source span.
242
+ parent.children[i] = {
243
+ type: node.type,
244
+ name: 'Server',
245
+ attributes: [],
246
+ children: [node],
247
+ position: node.position ? { ...node.position } : undefined,
248
+ }
249
+ continue
250
+ }
251
+ // Don't descend into <Server> — already server-side
252
+ const isServer =
253
+ (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement')
254
+ && node.name === 'Server'
255
+ if (isServer) continue
256
+ // Recurse into other elements
257
+ if (node.children) wrapInParent(node)
258
+ }
259
+ }
260
+ wrapInParent(mdast)
261
+ }
262
+
263
+ /** Filter mdxjsEsm import nodes to statements whose source is resolvable
264
+ * in the given modules map. Each environment's map only contains the
265
+ * modules that belong there, so this keeps exactly the imports that
266
+ * environment can satisfy — no naming rules. Filtering is per-STATEMENT:
267
+ * contiguous import lines parse as a single mdxjsEsm node, so dropping
268
+ * whole nodes would remove unrelated imports. */
269
+ export function filterImportNodesToModules(
270
+ importNodes: RootContent[],
271
+ moduleKeys: string[],
272
+ ): RootContent[] {
273
+ return importNodes.map((node: any) => {
274
+ const estree = node.data?.estree
275
+ if (!estree?.body) return node
276
+ const body = estree.body.filter((stmt: any) => {
277
+ if (stmt.type !== 'ImportDeclaration') return true
278
+ const source = stmt.source?.value
279
+ if (typeof source !== 'string') return true
280
+ return resolveModulePath(source, './', moduleKeys) !== undefined
281
+ })
282
+ if (body.length === estree.body.length) return node
283
+ return { ...node, data: { ...node.data, estree: { ...estree, body } } }
284
+ })
285
+ }