egaki 0.6.0 → 0.7.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.
Files changed (271) hide show
  1. package/CHANGELOG.md +140 -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 +538 -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 +584 -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,100 @@
1
+ // Transcription model registry for egaki.
2
+ // Maps transcription model IDs to their provider and direct HTTP provider factory.
3
+ // Follows the same pattern as speech-models.ts: provider factory map with lazy
4
+ // imports, auth source detection, and key validation.
5
+ //
6
+ // No AI SDK dependency — all providers use direct HTTP calls via
7
+ // TranscriptionProvider implementations in transcription-providers.ts.
8
+ import pc from 'picocolors';
9
+ import { PROVIDERS, injectCredentialsToEnv, } from './credentials.js';
10
+ import { ValidationError } from './models.js';
11
+ import { TRANSCRIPTION_CATALOG, findTranscriptionModel, } from './transcription-catalog.js';
12
+ export {};
13
+ export const TRANSCRIPTION_MODELS = TRANSCRIPTION_CATALOG.map((m) => m.id);
14
+ export const DEFAULT_TRANSCRIPTION_MODEL = 'whisper-1';
15
+ const TRANSCRIPTION_PROVIDER_FACTORIES = {
16
+ openai: async () => {
17
+ const { createOpenAITranscriptionProvider } = await import('./transcription-providers.js');
18
+ return createOpenAITranscriptionProvider();
19
+ },
20
+ elevenlabs: async () => {
21
+ const { createElevenLabsTranscriptionProvider } = await import('./transcription-providers.js');
22
+ return createElevenLabsTranscriptionProvider();
23
+ },
24
+ deepgram: async () => {
25
+ const { createDeepgramTranscriptionProvider } = await import('./transcription-providers.js');
26
+ return createDeepgramTranscriptionProvider();
27
+ },
28
+ groq: async () => {
29
+ const { createGroqTranscriptionProvider } = await import('./transcription-providers.js');
30
+ return createGroqTranscriptionProvider();
31
+ },
32
+ cartesia: async () => {
33
+ const { createCartesiaTranscriptionProvider } = await import('./transcription-providers.js');
34
+ return createCartesiaTranscriptionProvider();
35
+ },
36
+ };
37
+ // ─── key checking ────────────────────────────────────────────────────────────
38
+ function hasDirectProviderKey(providerName) {
39
+ const info = PROVIDERS[providerName];
40
+ if (!info)
41
+ return false;
42
+ return Boolean(process.env[info.envVar]);
43
+ }
44
+ // ─── config lookup ───────────────────────────────────────────────────────────
45
+ export function getTranscriptionModelConfig(modelId) {
46
+ const entry = findTranscriptionModel(modelId);
47
+ if (!entry) {
48
+ return new ValidationError(`Unknown transcription model: ${modelId}`);
49
+ }
50
+ return entry;
51
+ }
52
+ function resolveAuthSource(providerName) {
53
+ const info = PROVIDERS[providerName];
54
+ if (info && process.env[info.envVar]) {
55
+ return { type: 'api-key', label: info.envVar };
56
+ }
57
+ return { type: 'api-key', label: 'unknown' };
58
+ }
59
+ function logAuthSource(source) {
60
+ console.error(pc.dim(`Auth: ${source.label}`));
61
+ }
62
+ // ─── ensure key ──────────────────────────────────────────────────────────────
63
+ export function ensureTranscriptionProviderKey(providerName) {
64
+ if (hasDirectProviderKey(providerName))
65
+ return undefined;
66
+ const info = PROVIDERS[providerName];
67
+ const label = info?.label || providerName;
68
+ return new ValidationError(`Missing API key for ${label}. ` +
69
+ `Run: egaki login --provider ${providerName} --key <key>. ` +
70
+ (info ? info.hint : ''));
71
+ }
72
+ // ─── provider cache ──────────────────────────────────────────────────────────
73
+ /** Global cache of provider instances, keyed by provider name. */
74
+ const providerCache = globalThis.__egakiTranscriptionProviderCache ??= new Map();
75
+ // ─── public factory ──────────────────────────────────────────────────────────
76
+ export function getTranscriptionProvider(providerName) {
77
+ injectCredentialsToEnv();
78
+ const keyError = ensureTranscriptionProviderKey(providerName);
79
+ if (keyError)
80
+ return keyError;
81
+ const authSource = resolveAuthSource(providerName);
82
+ logAuthSource(authSource);
83
+ const factory = TRANSCRIPTION_PROVIDER_FACTORIES[providerName];
84
+ if (!factory) {
85
+ return new Error(`Transcription is not supported for provider: ${providerName}`);
86
+ }
87
+ // Return a lazy provider: the factory is async but we want getTranscriptionProvider
88
+ // to be sync for ergonomic use in transcribeAudioUncached. The provider promise
89
+ // is cached globally per provider name so the factory runs once across all calls.
90
+ return {
91
+ async transcribe(options) {
92
+ const cached = providerCache.get(providerName);
93
+ if (cached)
94
+ return (await cached).transcribe(options);
95
+ const promise = factory();
96
+ providerCache.set(providerName, promise);
97
+ return (await promise).transcribe(options);
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,22 @@
1
+ import type { TranscriptionSegment } from './transcription-generate.js';
2
+ export interface TranscriptionProviderOptions {
3
+ audio: Uint8Array;
4
+ modelId: string;
5
+ language?: string;
6
+ abortSignal?: AbortSignal;
7
+ }
8
+ export interface TranscriptionProviderResult {
9
+ text: string;
10
+ segments: TranscriptionSegment[];
11
+ language?: string;
12
+ durationInSeconds?: number;
13
+ }
14
+ export interface TranscriptionProvider {
15
+ transcribe(options: TranscriptionProviderOptions): Promise<TranscriptionProviderResult>;
16
+ }
17
+ export declare function createOpenAITranscriptionProvider(): TranscriptionProvider;
18
+ export declare function createGroqTranscriptionProvider(): TranscriptionProvider;
19
+ export declare function createElevenLabsTranscriptionProvider(): TranscriptionProvider;
20
+ export declare function createDeepgramTranscriptionProvider(): TranscriptionProvider;
21
+ export declare function createCartesiaTranscriptionProvider(): TranscriptionProvider;
22
+ //# sourceMappingURL=transcription-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcription-providers.d.ts","sourceRoot":"","sources":["../../src/cli/transcription-providers.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAIvE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,UAAU,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,oBAAoB,EAAE,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAA;CACxF;AAiED,wBAAgB,iCAAiC,IAAI,qBAAqB,CAkDzE;AAgBD,wBAAgB,+BAA+B,IAAI,qBAAqB,CA6CvE;AAmBD,wBAAgB,qCAAqC,IAAI,qBAAqB,CAqD7E;AAsBD,wBAAgB,mCAAmC,IAAI,qBAAqB,CAmD3E;AAmBD,wBAAgB,mCAAmC,IAAI,qBAAqB,CA+C3E"}
@@ -0,0 +1,290 @@
1
+ // Direct HTTP transcription providers for egaki.
2
+ // Each provider sends multipart form or raw audio to the provider API
3
+ // and parses the response, including word-level timestamps.
4
+ //
5
+ // This replaces the AI SDK's experimental_transcribe + @ai-sdk/* provider
6
+ // packages, giving us full control over request/response handling.
7
+ // In particular, we can:
8
+ // - Send verbose_json + word timestamp granularities for whisper-1
9
+ // - Avoid verbose_json for gpt-4o-transcribe models, which only support json/text
10
+ // - Parse the top-level `words` array from Groq responses (the AI SDK
11
+ // Groq provider only parses `segments`)
12
+ //
13
+ // API reference / OpenAPI specs:
14
+ // OpenAI: https://platform.openai.com/docs/api-reference/audio/createTranscription
15
+ // Groq: https://console.groq.com/docs/api-reference#audio-transcription
16
+ // (OpenAI-compatible endpoint at /openai/v1/audio/transcriptions)
17
+ // ElevenLabs: https://elevenlabs.io/docs/api-reference/speech-to-text/convert
18
+ // Deepgram: https://developers.deepgram.com/reference/listen-file
19
+ // ─── shared helpers ──────────────────────────────────────────────────────────
20
+ function mediaTypeFromAudioBytes(audio) {
21
+ if (audio.length >= 3 && audio[0] === 0x49 && audio[1] === 0x44 && audio[2] === 0x33)
22
+ return 'audio/mpeg'; // ID3 header
23
+ if (audio.length >= 2 && audio[0] === 0xff && (audio[1] & 0xe0) === 0xe0)
24
+ return 'audio/mpeg'; // MP3 sync word
25
+ // MP4/M4A: ftyp box at byte offset 4 (box size in first 4 bytes, then 'ftyp')
26
+ if (audio.length >= 12 && audio[4] === 0x66 && audio[5] === 0x74 && audio[6] === 0x79 && audio[7] === 0x70)
27
+ return 'audio/mp4';
28
+ // WebM: EBML header magic bytes
29
+ if (audio.length >= 4 && audio[0] === 0x1a && audio[1] === 0x45 && audio[2] === 0xdf && audio[3] === 0xa3)
30
+ return 'audio/webm';
31
+ if (audio.length >= 4 && audio[0] === 0x52 && audio[1] === 0x49 && audio[2] === 0x46 && audio[3] === 0x46)
32
+ return 'audio/wav'; // RIFF
33
+ if (audio.length >= 4 && audio[0] === 0x4f && audio[1] === 0x67 && audio[2] === 0x67 && audio[3] === 0x53)
34
+ return 'audio/ogg'; // OggS
35
+ if (audio.length >= 4 && audio[0] === 0x66 && audio[1] === 0x4c && audio[2] === 0x61 && audio[3] === 0x43)
36
+ return 'audio/flac'; // fLaC
37
+ return 'audio/mpeg';
38
+ }
39
+ function extensionFromMediaType(mediaType) {
40
+ const map = {
41
+ 'audio/mpeg': 'mp3',
42
+ 'audio/mp3': 'mp3',
43
+ 'audio/wav': 'wav',
44
+ 'audio/x-wav': 'wav',
45
+ 'audio/ogg': 'ogg',
46
+ 'audio/flac': 'flac',
47
+ 'audio/webm': 'webm',
48
+ 'audio/mp4': 'm4a',
49
+ 'audio/m4a': 'm4a',
50
+ 'audio/x-m4a': 'm4a',
51
+ };
52
+ return map[mediaType] || 'mp3';
53
+ }
54
+ function buildAudioFormData(audio, modelId) {
55
+ const mediaType = mediaTypeFromAudioBytes(audio);
56
+ const ext = extensionFromMediaType(mediaType);
57
+ const formData = new FormData();
58
+ // Copy into a fresh ArrayBuffer to satisfy TypeScript's BlobPart constraint
59
+ // (Uint8Array backed by SharedArrayBuffer is not assignable to BlobPart).
60
+ const buf = new Uint8Array(audio);
61
+ const blob = new Blob([buf], { type: mediaType });
62
+ formData.append('file', new File([blob], `audio.${ext}`, { type: mediaType }), `audio.${ext}`);
63
+ formData.append('model', modelId);
64
+ return { formData, mediaType };
65
+ }
66
+ // ─── OpenAI ──────────────────────────────────────────────────────────────────
67
+ // POST https://api.openai.com/v1/audio/transcriptions
68
+ // Multipart form: file, model, response_format, timestamp_granularities[], language
69
+ //
70
+ // gpt-4o-transcribe and gpt-4o-mini-transcribe only support response_format=json
71
+ // (NOT verbose_json). Word timestamps require verbose_json, so these models
72
+ // cannot return word-level timestamps. This is an OpenAI API limitation, not ours.
73
+ // whisper-1 supports verbose_json and returns word timestamps.
74
+ const GPT4O_TRANSCRIBE_MODELS = ['gpt-4o-transcribe', 'gpt-4o-mini-transcribe'];
75
+ export function createOpenAITranscriptionProvider() {
76
+ return {
77
+ async transcribe(options) {
78
+ const apiKey = process.env['OPENAI_API_KEY'];
79
+ if (!apiKey) {
80
+ throw new Error('Missing OPENAI_API_KEY. Run: egaki login --provider openai --key <key>');
81
+ }
82
+ const { formData } = buildAudioFormData(options.audio, options.modelId);
83
+ // gpt-4o-transcribe models only support "json" format;
84
+ // whisper-1 supports "verbose_json" which enables word timestamps.
85
+ const isGpt4oTranscribe = GPT4O_TRANSCRIBE_MODELS.includes(options.modelId);
86
+ formData.append('response_format', isGpt4oTranscribe ? 'json' : 'verbose_json');
87
+ if (!isGpt4oTranscribe) {
88
+ formData.append('timestamp_granularities[]', 'word');
89
+ }
90
+ if (options.language) {
91
+ formData.append('language', options.language);
92
+ }
93
+ const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
94
+ method: 'POST',
95
+ headers: { 'Authorization': `Bearer ${apiKey}` },
96
+ body: formData,
97
+ signal: options.abortSignal,
98
+ });
99
+ if (!response.ok) {
100
+ const errorText = await response.text().catch(() => '');
101
+ throw new Error(`OpenAI transcription API error ${response.status}: ${errorText || response.statusText}`);
102
+ }
103
+ const json = await response.json();
104
+ // Prefer word-level segments from the `words` array; fall back to
105
+ // sentence-level `segments` if words are not present.
106
+ const segments = json.words?.map((w) => ({ text: w.word, startSecond: w.start, endSecond: w.end }))
107
+ ?? json.segments?.map((s) => ({ text: s.text, startSecond: s.start, endSecond: s.end }))
108
+ ?? [];
109
+ return {
110
+ text: json.text,
111
+ segments,
112
+ language: json.language,
113
+ durationInSeconds: json.duration,
114
+ };
115
+ },
116
+ };
117
+ }
118
+ export function createGroqTranscriptionProvider() {
119
+ return {
120
+ async transcribe(options) {
121
+ const apiKey = process.env['GROQ_API_KEY'];
122
+ if (!apiKey) {
123
+ throw new Error('Missing GROQ_API_KEY. Run: egaki login --provider groq --key <key>');
124
+ }
125
+ const { formData } = buildAudioFormData(options.audio, options.modelId);
126
+ formData.append('response_format', 'verbose_json');
127
+ formData.append('timestamp_granularities[]', 'word');
128
+ formData.append('timestamp_granularities[]', 'segment');
129
+ if (options.language) {
130
+ formData.append('language', options.language);
131
+ }
132
+ const response = await fetch('https://api.groq.com/openai/v1/audio/transcriptions', {
133
+ method: 'POST',
134
+ headers: { 'Authorization': `Bearer ${apiKey}` },
135
+ body: formData,
136
+ signal: options.abortSignal,
137
+ });
138
+ if (!response.ok) {
139
+ const errorText = await response.text().catch(() => '');
140
+ throw new Error(`Groq transcription API error ${response.status}: ${errorText || response.statusText}`);
141
+ }
142
+ const json = await response.json();
143
+ // Prefer word-level from the `words` array (now parsed correctly);
144
+ // fall back to sentence-level `segments`.
145
+ const segments = json.words?.map((w) => ({ text: w.word, startSecond: w.start, endSecond: w.end }))
146
+ ?? json.segments?.map((s) => ({ text: s.text, startSecond: s.start, endSecond: s.end }))
147
+ ?? [];
148
+ return {
149
+ text: json.text,
150
+ segments,
151
+ language: json.language,
152
+ durationInSeconds: json.duration,
153
+ };
154
+ },
155
+ };
156
+ }
157
+ export function createElevenLabsTranscriptionProvider() {
158
+ return {
159
+ async transcribe(options) {
160
+ const apiKey = process.env['ELEVENLABS_API_KEY'];
161
+ if (!apiKey) {
162
+ throw new Error('Missing ELEVENLABS_API_KEY. Run: egaki login --provider elevenlabs --key <key>');
163
+ }
164
+ const mediaType = mediaTypeFromAudioBytes(options.audio);
165
+ const ext = extensionFromMediaType(mediaType);
166
+ const formData = new FormData();
167
+ const buf = new Uint8Array(options.audio);
168
+ const blob = new Blob([buf], { type: mediaType });
169
+ formData.append('file', new File([blob], `audio.${ext}`, { type: mediaType }), `audio.${ext}`);
170
+ formData.append('model_id', options.modelId);
171
+ formData.append('timestamps_granularity', 'word');
172
+ formData.append('tag_audio_events', 'true');
173
+ if (options.language) {
174
+ formData.append('language_code', options.language);
175
+ }
176
+ const response = await fetch('https://api.elevenlabs.io/v1/speech-to-text', {
177
+ method: 'POST',
178
+ headers: { 'xi-api-key': apiKey },
179
+ body: formData,
180
+ signal: options.abortSignal,
181
+ });
182
+ if (!response.ok) {
183
+ const errorText = await response.text().catch(() => '');
184
+ throw new Error(`ElevenLabs transcription API error ${response.status}: ${errorText || response.statusText}`);
185
+ }
186
+ const json = await response.json();
187
+ // Filter to only 'word' type entries (skip 'spacing' and 'audio_event')
188
+ const segments = json.words
189
+ ?.filter((w) => w.type === 'word' && w.text.trim())
190
+ .map((w) => ({ text: w.text, startSecond: w.start ?? 0, endSecond: w.end ?? 0 }))
191
+ ?? [];
192
+ // Duration is the end time of the last word
193
+ const lastWord = json.words?.findLast((w) => w.type === 'word' && w.end != null);
194
+ const durationInSeconds = lastWord?.end ?? undefined;
195
+ return {
196
+ text: json.text,
197
+ segments,
198
+ language: json.language_code,
199
+ durationInSeconds,
200
+ };
201
+ },
202
+ };
203
+ }
204
+ export function createDeepgramTranscriptionProvider() {
205
+ return {
206
+ async transcribe(options) {
207
+ const apiKey = process.env['DEEPGRAM_API_KEY'];
208
+ if (!apiKey) {
209
+ throw new Error('Missing DEEPGRAM_API_KEY. Run: egaki login --provider deepgram --key <key>');
210
+ }
211
+ const mediaType = mediaTypeFromAudioBytes(options.audio);
212
+ const params = new URLSearchParams();
213
+ params.append('model', options.modelId);
214
+ params.append('smart_format', 'true');
215
+ if (options.language) {
216
+ params.append('language', options.language);
217
+ }
218
+ else {
219
+ params.append('detect_language', 'true');
220
+ }
221
+ const response = await fetch(`https://api.deepgram.com/v1/listen?${params}`, {
222
+ method: 'POST',
223
+ headers: {
224
+ 'Authorization': `Token ${apiKey}`,
225
+ 'Content-Type': mediaType,
226
+ },
227
+ body: new Uint8Array(options.audio),
228
+ signal: options.abortSignal,
229
+ });
230
+ if (!response.ok) {
231
+ const errorText = await response.text().catch(() => '');
232
+ throw new Error(`Deepgram transcription API error ${response.status}: ${errorText || response.statusText}`);
233
+ }
234
+ const json = await response.json();
235
+ const channel = json.results?.channels?.[0];
236
+ const alt = channel?.alternatives?.[0];
237
+ const segments = alt?.words?.map((w) => ({ text: w.word, startSecond: w.start, endSecond: w.end }))
238
+ ?? [];
239
+ return {
240
+ text: alt?.transcript ?? '',
241
+ segments,
242
+ language: channel?.detected_language,
243
+ durationInSeconds: json.metadata?.duration,
244
+ };
245
+ },
246
+ };
247
+ }
248
+ const CARTESIA_API_BASE = 'https://api.cartesia.ai';
249
+ const CARTESIA_API_VERSION = '2026-03-01';
250
+ export function createCartesiaTranscriptionProvider() {
251
+ return {
252
+ async transcribe(options) {
253
+ const apiKey = process.env['CARTESIA_API_KEY'];
254
+ if (!apiKey) {
255
+ throw new Error('Missing CARTESIA_API_KEY. Run: egaki login --provider cartesia --key <key>');
256
+ }
257
+ const mediaType = mediaTypeFromAudioBytes(options.audio);
258
+ const ext = extensionFromMediaType(mediaType);
259
+ const formData = new FormData();
260
+ const buf = new Uint8Array(options.audio);
261
+ const blob = new Blob([buf], { type: mediaType });
262
+ formData.append('file', new File([blob], `audio.${ext}`, { type: mediaType }), `audio.${ext}`);
263
+ formData.append('model', options.modelId);
264
+ formData.append('language', options.language || 'en');
265
+ formData.append('timestamp_granularities[]', 'word');
266
+ const response = await fetch(`${CARTESIA_API_BASE}/stt`, {
267
+ method: 'POST',
268
+ headers: {
269
+ 'Cartesia-Version': CARTESIA_API_VERSION,
270
+ 'Authorization': `Bearer ${apiKey}`,
271
+ },
272
+ body: formData,
273
+ signal: options.abortSignal,
274
+ });
275
+ if (!response.ok) {
276
+ const errorText = await response.text().catch(() => '');
277
+ throw new Error(`Cartesia STT API error ${response.status}: ${errorText || response.statusText}`);
278
+ }
279
+ const json = await response.json();
280
+ const segments = json.words?.map((w) => ({ text: w.word, startSecond: w.start, endSecond: w.end }))
281
+ ?? [];
282
+ return {
283
+ text: json.text,
284
+ segments,
285
+ language: json.language,
286
+ durationInSeconds: json.duration,
287
+ };
288
+ },
289
+ };
290
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-model-catalog.d.ts","sourceRoot":"","sources":["../../src/cli/video-model-catalog.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,aAAa,EACb,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,WAAW,IAAI,gBAAgB,EACpC,KAAK,wBAAwB,GAC9B,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xai-auth.d.ts","sourceRoot":"","sources":["../../src/cli/xai-auth.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAwBvB,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,QAAA,MAAM,mBAAmB;;;;;iBAKvB,CAAA;AAEF,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAA;AA+DxD,8EAA8E;AAC9E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,MAAM,GAAE,MAAqC,GAC5C,OAAO,CAKT;AA6BD,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAelF;AAwHD;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAoEtD;AAID;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAC5B,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,CAuB1B"}
@@ -15,7 +15,7 @@ import { spinner, log, note } from '@clack/prompts';
15
15
  import pc from 'picocolors';
16
16
  import { z } from 'zod';
17
17
  import { openUrlInBrowser } from './open-browser.js';
18
- import pkg from '../package.json' with { type: 'json' };
18
+ import pkg from '../../package.json' with { type: 'json' };
19
19
  // Public Grok-CLI OAuth client. xAI's auth server rejects loopback OAuth from
20
20
  // non-allowlisted clients, so we reuse the Grok-CLI client_id that xAI ships
21
21
  // for desktop OAuth flows.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * AngledScreen — CSS 3D perspective wrapper with fake bokeh depth-of-field.
3
+ *
4
+ * Wraps any children (images, videos, divs) in a 3D-transformed plane using
5
+ * CSS `perspective` + `transform: rotateX/Y/Z`. Simulates depth-of-field
6
+ * via `backdrop-filter: blur()` masked with `mask-image: linear-gradient()`
7
+ * so only the far edge of the content gets blurred.
8
+ *
9
+ * The blur direction is auto-detected from the rotation angles: whichever
10
+ * edge is furthest from the camera gets blurred. Override with bokehDirection.
11
+ *
12
+ * IMPORTANT: the blur overlay lives OUTSIDE the 3D transform as a sibling
13
+ * in the outer container. backdrop-filter does not work inside CSS 3D
14
+ * transformed elements (the transform compositing layer breaks backdrop
15
+ * sampling and blurs everything regardless of mask). Placing it outside
16
+ * means it blurs the already-composited 3D scene in normal 2D space.
17
+ *
18
+ * Set `debug` to visualize the mask gradient as green (sharp) / red (blur).
19
+ */
20
+ import { type CSSProperties, type ReactNode } from 'react';
21
+ export interface AngledScreenProps {
22
+ children: ReactNode;
23
+ /** CSS perspective distance in px. Smaller = more dramatic. Default 1200. */
24
+ perspective?: number;
25
+ /** X-axis rotation in degrees (tilts top/bottom). Default 8. */
26
+ rotateX?: number;
27
+ /** Y-axis rotation in degrees (angles left/right). Default -12. */
28
+ rotateY?: number;
29
+ /** Z-axis rotation in degrees. Default 0. */
30
+ rotateZ?: number;
31
+ /** Push the plane forward/back in px. Positive = closer/larger,
32
+ * negative = further/smaller (same visual effect as scale). Default 0. */
33
+ translateZ?: number;
34
+ /** Transform origin. Default '50% 50%'. */
35
+ transformOrigin?: string;
36
+ /** Enable bokeh blur overlay. Default true. */
37
+ bokeh?: boolean;
38
+ /** Max blur radius in px at the far edge. Default 12. */
39
+ bokehBlur?: number;
40
+ /** Override which edge gets blurred. Auto-detected from rotation if omitted. */
41
+ bokehDirection?: 'top' | 'bottom' | 'left' | 'right';
42
+ /** Where blur starts (0-1). 0 = blur from the very near edge, 1 = no blur.
43
+ * The transition ramp is a fixed 25% after this point. Default 0.3. */
44
+ bokehOffset?: number;
45
+ /** Background color behind the tilted screen. Default '#000000'. */
46
+ backgroundColor?: string;
47
+ /** Width of the screen content. Default '80%'. */
48
+ width?: string | number;
49
+ /** Height of the screen content. Default 'auto'. */
50
+ height?: string | number;
51
+ /** Show the blur mask as green (sharp) / red (blur) overlay for debugging. */
52
+ debug?: boolean;
53
+ /** Optional inline style on the outer container. */
54
+ style?: CSSProperties;
55
+ }
56
+ export declare function AngledScreen(props: AngledScreenProps): import("react").JSX.Element;
57
+ //# sourceMappingURL=angled-screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"angled-screen.d.ts","sourceRoot":"","sources":["../../src/vite/angled-screen.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAG1D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAA;IAGnB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;+EAC2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,CAAA;IAGxB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gFAAgF;IAChF,cAAc,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;IACpD;4EACwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAA;IAGpB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAA;IAGxB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAExB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,OAAO,CAAA;IAEf,oDAAoD;IACpD,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAwBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAwHpD"}
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * AngledScreen — CSS 3D perspective wrapper with fake bokeh depth-of-field.
5
+ *
6
+ * Wraps any children (images, videos, divs) in a 3D-transformed plane using
7
+ * CSS `perspective` + `transform: rotateX/Y/Z`. Simulates depth-of-field
8
+ * via `backdrop-filter: blur()` masked with `mask-image: linear-gradient()`
9
+ * so only the far edge of the content gets blurred.
10
+ *
11
+ * The blur direction is auto-detected from the rotation angles: whichever
12
+ * edge is furthest from the camera gets blurred. Override with bokehDirection.
13
+ *
14
+ * IMPORTANT: the blur overlay lives OUTSIDE the 3D transform as a sibling
15
+ * in the outer container. backdrop-filter does not work inside CSS 3D
16
+ * transformed elements (the transform compositing layer breaks backdrop
17
+ * sampling and blurs everything regardless of mask). Placing it outside
18
+ * means it blurs the already-composited 3D scene in normal 2D space.
19
+ *
20
+ * Set `debug` to visualize the mask gradient as green (sharp) / red (blur).
21
+ */
22
+ import {} from 'react';
23
+ import { useTweakpane } from "./tweakpane-hook.js";
24
+ const GRADIENT_DIRECTIONS = {
25
+ top: 'to top',
26
+ bottom: 'to bottom',
27
+ left: 'to left',
28
+ right: 'to right',
29
+ };
30
+ /**
31
+ * Auto-detect which edge is furthest from camera based on rotation.
32
+ * CSS rotateY(-20deg): right edge comes toward viewer, LEFT is far → blur left.
33
+ * CSS rotateY(+20deg): left edge comes toward viewer, RIGHT is far → blur right.
34
+ * CSS rotateX(+10deg): top edge comes toward viewer, BOTTOM is far → blur bottom.
35
+ * CSS rotateX(-10deg): bottom edge comes toward viewer, TOP is far → blur top.
36
+ * Picks the axis with the larger rotation angle.
37
+ */
38
+ function autoDirection(rotateX, rotateY) {
39
+ if (Math.abs(rotateY) >= Math.abs(rotateX)) {
40
+ return rotateY < 0 ? 'left' : 'right';
41
+ }
42
+ return rotateX > 0 ? 'bottom' : 'top';
43
+ }
44
+ export function AngledScreen(props) {
45
+ const { children, transformOrigin = '50% 50%', bokehDirection, backgroundColor = '#000000', width = '80%', height = 'auto', style, } = props;
46
+ const tp = useTweakpane('AngledScreen', {
47
+ perspective: { value: props.perspective ?? 1200, min: 100, max: 3000, step: 10 },
48
+ rotateX: { value: props.rotateX ?? 8, min: -90, max: 90, step: 0.5 },
49
+ rotateY: { value: props.rotateY ?? -12, min: -90, max: 90, step: 0.5 },
50
+ rotateZ: { value: props.rotateZ ?? 0, min: -180, max: 180, step: 0.5 },
51
+ translateZ: { value: props.translateZ ?? 0, min: -500, max: 500, step: 1 },
52
+ bokeh: props.bokeh ?? true,
53
+ bokehBlur: { value: props.bokehBlur ?? 12, min: 0, max: 50, step: 0.5 },
54
+ bokehOffset: { value: props.bokehOffset ?? 0.3, min: 0, max: 1, step: 0.01 },
55
+ debug: props.debug ?? false,
56
+ });
57
+ const { perspective, rotateX, rotateY, rotateZ, translateZ } = tp;
58
+ const { bokeh, bokehBlur, bokehOffset } = tp;
59
+ const showDebug = tp.debug;
60
+ const direction = bokehDirection ?? autoDirection(rotateX, rotateY);
61
+ const gradientDir = GRADIENT_DIRECTIONS[direction] || 'to right';
62
+ const startPct = `${bokehOffset * 100}%`;
63
+ const endPct = `${Math.min((bokehOffset + 0.25) * 100, 100)}%`;
64
+ return (_jsxs("div", { style: {
65
+ position: 'relative',
66
+ width: '100%',
67
+ height: '100%',
68
+ overflow: 'hidden',
69
+ }, children: [_jsx("div", { style: {
70
+ position: 'absolute',
71
+ inset: 0,
72
+ backgroundColor,
73
+ display: 'flex',
74
+ alignItems: 'center',
75
+ justifyContent: 'center',
76
+ ...style,
77
+ }, children: _jsx("div", { style: {
78
+ perspective: `${perspective}px`,
79
+ perspectiveOrigin: '50% 50%',
80
+ display: 'flex',
81
+ alignItems: 'center',
82
+ justifyContent: 'center',
83
+ width: '100%',
84
+ height: '100%',
85
+ }, children: _jsx("div", { style: {
86
+ transform: [
87
+ `rotateX(${rotateX}deg)`,
88
+ `rotateY(${rotateY}deg)`,
89
+ `rotateZ(${rotateZ}deg)`,
90
+ translateZ !== 0 ? `translateZ(${translateZ}px)` : '',
91
+ ]
92
+ .filter(Boolean)
93
+ .join(' '),
94
+ transformOrigin,
95
+ width: typeof width === 'number' ? `${width}px` : width,
96
+ height: typeof height === 'number' ? `${height}px` : height,
97
+ position: 'relative',
98
+ borderRadius: '12px',
99
+ overflow: 'hidden',
100
+ }, children: children }) }) }), bokeh && bokehBlur > 0 && !showDebug && (_jsx("div", { style: {
101
+ position: 'absolute',
102
+ inset: 0,
103
+ backdropFilter: `blur(${bokehBlur}px)`,
104
+ WebkitBackdropFilter: `blur(${bokehBlur}px)`,
105
+ maskImage: `linear-gradient(${gradientDir}, transparent ${startPct}, black ${endPct})`,
106
+ WebkitMaskImage: `linear-gradient(${gradientDir}, transparent ${startPct}, black ${endPct})`,
107
+ pointerEvents: 'none',
108
+ } })), showDebug && (_jsx("div", { style: {
109
+ position: 'absolute',
110
+ inset: 0,
111
+ background: `linear-gradient(${gradientDir}, rgba(0,255,0,0.4) ${startPct}, rgba(255,0,0,0.4) ${endPct})`,
112
+ pointerEvents: 'none',
113
+ zIndex: 999,
114
+ } }))] }));
115
+ }