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,208 @@
1
+ // General-purpose persisted memoization for expensive async functions.
2
+ //
3
+ // cachedGenerate() wraps any async function with:
4
+ // - Deterministic filesystem caching (params → stable hash → file)
5
+ // - Concurrent call deduplication (same key → same promise)
6
+ // - Stale fallback management (old results moved to stale/ subfolder)
7
+ // - Progress tracking (namespace-based, drives the player toolbar UI)
8
+ //
9
+ // Binary params (Uint8Array) are auto-hashed in the cache key. The caller
10
+ // only provides a generate function, a serializer, and a namespace.
11
+ //
12
+ // Usage:
13
+ // const generateImage = cachedGenerate({
14
+ // namespace: 'image',
15
+ // prefixFrom: (p) => p.prompt,
16
+ // generate: (p) => generateImageUncached(p).then(r => r.images[0]!),
17
+ // serialize: (file) => ({ bytes: file.uint8Array, extension: '.png' }),
18
+ // })
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { stableJsonKey, hashKey, promptPrefix, generatedDir, findCachedFile, findFallbackFile, moveFileToStale, generationQueue, formatKeyParams, } from './cache-utils.js';
22
+ const generationRegistry = globalThis.__egakiGenerationRegistry ??= new Map();
23
+ const progressListeners = globalThis.__egakiProgressListeners ??= new Set();
24
+ function notifyProgressListeners() {
25
+ for (const listener of progressListeners) {
26
+ try {
27
+ listener();
28
+ }
29
+ catch { /* listener errors are non-fatal */ }
30
+ }
31
+ }
32
+ /** Subscribe to generation progress changes. Returns an unsubscribe function. */
33
+ export function onProgressChange(listener) {
34
+ progressListeners.add(listener);
35
+ return () => { progressListeners.delete(listener); };
36
+ }
37
+ /** Recent generation errors, drained on each getGenerationProgress() call
38
+ * so each SSE event includes errors that happened since the last yield. */
39
+ const recentErrors = globalThis.__egakiRecentErrors ??= [];
40
+ /** Get a snapshot of all active generations with elapsed time computed.
41
+ * Drains the error queue so each call returns only new errors since last read. */
42
+ export function getGenerationProgress() {
43
+ const now = Date.now();
44
+ const generations = [];
45
+ const counts = {};
46
+ for (const [key, entry] of generationRegistry) {
47
+ generations.push({ ...entry, key, elapsedMs: now - entry.startedAt });
48
+ counts[entry.namespace] = (counts[entry.namespace] ?? 0) + 1;
49
+ }
50
+ const total = generations.length;
51
+ const errors = recentErrors.splice(0);
52
+ return {
53
+ generations,
54
+ errors,
55
+ summary: { counts, total },
56
+ done: total === 0 && errors.length === 0,
57
+ };
58
+ }
59
+ function registerGeneration(key, entry) {
60
+ generationRegistry.set(key, entry);
61
+ notifyProgressListeners();
62
+ }
63
+ function unregisterGeneration(key, error) {
64
+ const entry = generationRegistry.get(key);
65
+ if (entry && error) {
66
+ recentErrors.push({
67
+ key,
68
+ namespace: entry.namespace,
69
+ label: entry.label,
70
+ model: entry.model,
71
+ error,
72
+ durationMs: Date.now() - entry.startedAt,
73
+ });
74
+ }
75
+ generationRegistry.delete(key);
76
+ notifyProgressListeners();
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Auto cache key — recursively walks params, hashes Uint8Array values,
80
+ // strips undefined. Handles nested objects and arrays of any depth.
81
+ // ---------------------------------------------------------------------------
82
+ function normalizeCacheValue(value) {
83
+ if (value === undefined)
84
+ return undefined;
85
+ if (value instanceof Uint8Array)
86
+ return hashKey(value);
87
+ if (Array.isArray(value))
88
+ return value.map(normalizeCacheValue);
89
+ if (value && typeof value === 'object') {
90
+ return Object.fromEntries(Object.entries(value)
91
+ .filter(([, v]) => v !== undefined)
92
+ .map(([k, v]) => [k, normalizeCacheValue(v)]));
93
+ }
94
+ return value;
95
+ }
96
+ function autoCacheKey(params) {
97
+ return normalizeCacheValue(params);
98
+ }
99
+ /** Compute cache info for a given namespace and params without starting generation.
100
+ * Used by server components to check cache and find fallback synchronously. */
101
+ export function getCacheInfo(namespace, params, prefixText) {
102
+ const keyParams = autoCacheKey(params);
103
+ // Note: getCacheInfo uses the raw params. If a cachedGenerate wrapper uses
104
+ // cacheKey to exclude fields, the caller should pass the same subset here.
105
+ const key = stableJsonKey(keyParams);
106
+ const hash = hashKey(key);
107
+ const dir = generatedDir(namespace);
108
+ const prefix = promptPrefix(prefixText);
109
+ const cached = findCachedFile(dir, hash);
110
+ const fallback = cached ? undefined : findFallbackFile(dir, prefix, hash);
111
+ const fallbackSrc = fallback
112
+ ? `/generated/${namespace}/${fallback.inStale ? 'stale/' : ''}${fallback.filename}`
113
+ : undefined;
114
+ return { hash, prefix, dir, key, cached, fallback, fallbackSrc };
115
+ }
116
+ export function cachedGenerate(config) {
117
+ const { namespace, prefixFrom, cacheKey, generate, serialize, deserialize, modelFrom, postWrite } = config;
118
+ function cachedFn(params) {
119
+ const rawKeyParams = cacheKey ? cacheKey(params) : params;
120
+ const keyParams = autoCacheKey(rawKeyParams);
121
+ const key = stableJsonKey(keyParams);
122
+ const hash = hashKey(key);
123
+ const dir = generatedDir(namespace);
124
+ const prefix = promptPrefix(prefixFrom(params));
125
+ // Queue key includes namespace to prevent cross-namespace collisions.
126
+ // Two wrappers with different namespaces but identical params must not
127
+ // share the same in-flight promise.
128
+ const queueKey = `${namespace}:${key}`;
129
+ // 1. Cache hit — return immediately
130
+ const cached = findCachedFile(dir, hash);
131
+ if (cached) {
132
+ const urlPath = `/generated/${namespace}/${cached}`;
133
+ const filePath = path.join(dir, cached);
134
+ if (deserialize) {
135
+ return Promise.resolve(deserialize({ urlPath, filePath }));
136
+ }
137
+ return Promise.resolve({ src: urlPath });
138
+ }
139
+ // 2. Dedup — return pending promise if same key is in-flight
140
+ const pending = generationQueue.get(queueKey);
141
+ if (pending)
142
+ return pending;
143
+ // 3. Find stale fallback for cleanup after generation
144
+ const fallback = findFallbackFile(dir, prefix, hash);
145
+ // 4. Register progress
146
+ const regKey = `${namespace}:${hash}`;
147
+ const label = prefixFrom(params);
148
+ registerGeneration(regKey, {
149
+ namespace,
150
+ label,
151
+ model: modelFrom?.(params),
152
+ startedAt: Date.now(),
153
+ params: keyParams,
154
+ });
155
+ // 5. Generate, serialize, write to disk
156
+ // The entire body is wrapped in try/catch so that failures in serialize(),
157
+ // fs.writeFileSync(), or deserialize() also unregister progress and return
158
+ // Error instead of throwing through the promise.
159
+ const promise = (async () => {
160
+ try {
161
+ console.log(`[egaki] generating ${namespace}: ${prefix}-${hash} (${formatKeyParams(keyParams)})`);
162
+ const result = await generate(params);
163
+ const serialized = serialize(result, params);
164
+ const finalPrefix = serialized.prefix ? promptPrefix(serialized.prefix) : prefix;
165
+ const filename = `${finalPrefix}-${hash}${serialized.extension}`;
166
+ const filePath = path.join(dir, filename);
167
+ if ('bytes' in serialized) {
168
+ fs.writeFileSync(filePath, serialized.bytes);
169
+ }
170
+ else {
171
+ fs.writeFileSync(filePath, JSON.stringify(serialized.json, null, 2));
172
+ }
173
+ if (postWrite) {
174
+ try {
175
+ postWrite(filePath, result, params);
176
+ }
177
+ catch (e) {
178
+ console.error(`[egaki] postWrite error for ${namespace}:`, e);
179
+ }
180
+ }
181
+ console.log(`[egaki] generated ${namespace}: ${filename}`);
182
+ if (fallback)
183
+ moveFileToStale(dir, fallback);
184
+ unregisterGeneration(regKey);
185
+ const urlPath = `/generated/${namespace}/${filename}`;
186
+ if (deserialize) {
187
+ return deserialize({ urlPath, filePath });
188
+ }
189
+ return { src: urlPath };
190
+ }
191
+ catch (err) {
192
+ const error = err instanceof Error ? err : new Error(String(err));
193
+ unregisterGeneration(regKey, error.message);
194
+ return error;
195
+ }
196
+ finally {
197
+ generationQueue.delete(queueKey);
198
+ }
199
+ })();
200
+ generationQueue.set(queueKey, promise);
201
+ return promise;
202
+ }
203
+ cachedFn.getCacheInfo = function (params) {
204
+ const rawKeyParams = cacheKey ? cacheKey(params) : params;
205
+ return getCacheInfo(namespace, rawKeyParams, prefixFrom(params));
206
+ };
207
+ return cachedFn;
208
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for cachedGenerate — the general-purpose persisted memoization HOF.
3
+ *
4
+ * Uses a temp directory as the project root so tests are isolated and don't
5
+ * touch the real public/generated/ folder. Tests verify: cache hit/miss,
6
+ * dedup of concurrent calls, stale fallback management, auto-hashing of
7
+ * Uint8Array params, JSON serialization, progress tracking, error handling,
8
+ * and the getCacheInfo helper.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=cached-generate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-generate.test.d.ts","sourceRoot":"","sources":["../../src/cli/cached-generate.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Tests for cachedGenerate — the general-purpose persisted memoization HOF.
3
+ *
4
+ * Uses a temp directory as the project root so tests are isolated and don't
5
+ * touch the real public/generated/ folder. Tests verify: cache hit/miss,
6
+ * dedup of concurrent calls, stale fallback management, auto-hashing of
7
+ * Uint8Array params, JSON serialization, progress tracking, error handling,
8
+ * and the getCacheInfo helper.
9
+ */
10
+ import { describe, test, expect, beforeEach, afterEach } from 'vitest';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import os from 'node:os';
14
+ import { cachedGenerate, getCacheInfo, getGenerationProgress, } from "./cached-generate.js";
15
+ import { setProjectRoot } from "./cache-utils.js";
16
+ let tmpDir;
17
+ beforeEach(() => {
18
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'egaki-cached-generate-test-'));
19
+ setProjectRoot(tmpDir);
20
+ });
21
+ afterEach(() => {
22
+ fs.rmSync(tmpDir, { recursive: true, force: true });
23
+ });
24
+ function makeTestConfig(overrides) {
25
+ let callCount = 0;
26
+ const config = {
27
+ namespace: 'test',
28
+ prefixFrom: (p) => p.prompt,
29
+ generate: async (params) => {
30
+ callCount++;
31
+ return { data: `generated-${params.prompt}` };
32
+ },
33
+ serialize: (result) => ({
34
+ bytes: new Uint8Array(Buffer.from(result.data)),
35
+ extension: '.txt',
36
+ }),
37
+ ...overrides,
38
+ };
39
+ return { config, getCallCount: () => callCount };
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Cache hit / miss
43
+ // ---------------------------------------------------------------------------
44
+ describe('cache hit and miss', () => {
45
+ test('first call generates and writes file', async () => {
46
+ const { config, getCallCount } = makeTestConfig();
47
+ const fn = cachedGenerate(config);
48
+ const result = await fn({ prompt: 'hello world' });
49
+ expect(result).not.toBeInstanceOf(Error);
50
+ const r = result;
51
+ expect(r.src).toMatch(/^\/generated\/test\/hello-world-[a-f0-9]{8}\.txt$/);
52
+ expect(getCallCount()).toBe(1);
53
+ // File exists on disk
54
+ const filePath = path.join(tmpDir, 'public', r.src);
55
+ expect(fs.existsSync(filePath)).toBe(true);
56
+ expect(fs.readFileSync(filePath, 'utf-8')).toBe('generated-hello world');
57
+ });
58
+ test('second call with same params returns cached (no generation)', async () => {
59
+ const { config, getCallCount } = makeTestConfig();
60
+ const fn = cachedGenerate(config);
61
+ const r1 = await fn({ prompt: 'hello world' });
62
+ const r2 = await fn({ prompt: 'hello world' });
63
+ expect(r1).not.toBeInstanceOf(Error);
64
+ expect(r2).not.toBeInstanceOf(Error);
65
+ expect(r1.src).toBe(r2.src);
66
+ expect(getCallCount()).toBe(1); // only called once
67
+ });
68
+ test('different params generate different files', async () => {
69
+ const { config, getCallCount } = makeTestConfig();
70
+ const fn = cachedGenerate(config);
71
+ const r1 = await fn({ prompt: 'alpha' });
72
+ const r2 = await fn({ prompt: 'beta' });
73
+ expect(r1).not.toBeInstanceOf(Error);
74
+ expect(r2).not.toBeInstanceOf(Error);
75
+ expect(r1.src).not.toBe(r2.src);
76
+ expect(getCallCount()).toBe(2);
77
+ });
78
+ });
79
+ // ---------------------------------------------------------------------------
80
+ // Concurrent dedup
81
+ // ---------------------------------------------------------------------------
82
+ describe('deduplication', () => {
83
+ test('concurrent calls with same params return same promise', async () => {
84
+ let resolveGenerate;
85
+ const { config } = makeTestConfig({
86
+ generate: () => new Promise((resolve) => { resolveGenerate = resolve; }),
87
+ });
88
+ const fn = cachedGenerate(config);
89
+ const p1 = fn({ prompt: 'dedup test' });
90
+ const p2 = fn({ prompt: 'dedup test' });
91
+ // Resolve the single generation
92
+ resolveGenerate({ data: 'result' });
93
+ const [r1, r2] = await Promise.all([p1, p2]);
94
+ expect(r1).not.toBeInstanceOf(Error);
95
+ expect(r2).not.toBeInstanceOf(Error);
96
+ expect(r1.src).toBe(r2.src);
97
+ });
98
+ });
99
+ // ---------------------------------------------------------------------------
100
+ // Stale fallback management
101
+ // ---------------------------------------------------------------------------
102
+ describe('stale management', () => {
103
+ test('changing params moves old file to stale/', async () => {
104
+ const { config } = makeTestConfig();
105
+ const fn = cachedGenerate(config);
106
+ // Generate first version
107
+ const r1 = await fn({ prompt: 'hello world', seed: 1 });
108
+ expect(r1).not.toBeInstanceOf(Error);
109
+ const firstFile = path.basename(r1.src);
110
+ // Generate with different seed but same prompt prefix
111
+ const r2 = await fn({ prompt: 'hello world', seed: 2 });
112
+ expect(r2).not.toBeInstanceOf(Error);
113
+ // Old file should be in stale/
114
+ const staleDir = path.join(tmpDir, 'public', 'generated', 'test', 'stale');
115
+ const staleFiles = fs.existsSync(staleDir) ? fs.readdirSync(staleDir) : [];
116
+ expect(staleFiles).toContain(firstFile);
117
+ });
118
+ });
119
+ // ---------------------------------------------------------------------------
120
+ // Auto-hashing Uint8Array params
121
+ // ---------------------------------------------------------------------------
122
+ describe('Uint8Array auto-hashing', () => {
123
+ test('same bytes produce same cache key', async () => {
124
+ const { config, getCallCount } = makeTestConfig();
125
+ const fn = cachedGenerate(config);
126
+ const data = new Uint8Array([1, 2, 3]);
127
+ const r1 = await fn({ prompt: 'with data', inputData: data });
128
+ const r2 = await fn({ prompt: 'with data', inputData: new Uint8Array([1, 2, 3]) });
129
+ expect(getCallCount()).toBe(1); // same key → cached
130
+ expect(r1.src).toBe(r2.src);
131
+ });
132
+ test('different bytes produce different cache keys', async () => {
133
+ const { config, getCallCount } = makeTestConfig();
134
+ const fn = cachedGenerate(config);
135
+ await fn({ prompt: 'with data', inputData: new Uint8Array([1, 2, 3]) });
136
+ await fn({ prompt: 'with data', inputData: new Uint8Array([4, 5, 6]) });
137
+ expect(getCallCount()).toBe(2);
138
+ });
139
+ });
140
+ // ---------------------------------------------------------------------------
141
+ // JSON serialization
142
+ // ---------------------------------------------------------------------------
143
+ describe('JSON serialization', () => {
144
+ test('json serialize writes .json and deserialize reads it back', async () => {
145
+ const fn = cachedGenerate({
146
+ namespace: 'json-test',
147
+ prefixFrom: (p) => p.query,
148
+ generate: async (params) => ({ items: [params.query, 'b', 'c'] }),
149
+ serialize: (result) => ({
150
+ json: result,
151
+ extension: '.json',
152
+ }),
153
+ deserialize: ({ filePath }) => JSON.parse(fs.readFileSync(filePath, 'utf-8')),
154
+ });
155
+ const r1 = await fn({ query: 'search term' });
156
+ expect(r1).not.toBeInstanceOf(Error);
157
+ expect(r1.items).toEqual(['search term', 'b', 'c']);
158
+ // Second call reads from cache
159
+ const r2 = await fn({ query: 'search term' });
160
+ expect(r2).not.toBeInstanceOf(Error);
161
+ expect(r2.items).toEqual(['search term', 'b', 'c']);
162
+ });
163
+ });
164
+ // ---------------------------------------------------------------------------
165
+ // Prefix override from serialize
166
+ // ---------------------------------------------------------------------------
167
+ describe('prefix override', () => {
168
+ test('serialize can return a prefix to override the filename', async () => {
169
+ const fn = cachedGenerate({
170
+ namespace: 'prefix-test',
171
+ prefixFrom: () => 'initial',
172
+ generate: async () => ({ text: 'the actual content is here' }),
173
+ serialize: (result) => ({
174
+ bytes: new Uint8Array(Buffer.from(result.text)),
175
+ extension: '.txt',
176
+ prefix: 'the actual content is here',
177
+ }),
178
+ });
179
+ const r = await fn({ id: '123' });
180
+ expect(r).not.toBeInstanceOf(Error);
181
+ // The filename should use the override prefix, not 'initial'
182
+ expect(r.src).toMatch(/the-actual-content-is-here/);
183
+ expect(r.src).not.toMatch(/initial/);
184
+ });
185
+ });
186
+ // ---------------------------------------------------------------------------
187
+ // Progress tracking
188
+ // ---------------------------------------------------------------------------
189
+ describe('progress tracking', () => {
190
+ test('generation is registered and unregistered', async () => {
191
+ let resolveGenerate;
192
+ const fn = cachedGenerate({
193
+ namespace: 'progress-test',
194
+ prefixFrom: (p) => p.prompt,
195
+ modelFrom: (p) => `model-${p.seed}`,
196
+ generate: () => new Promise((resolve) => { resolveGenerate = resolve; }),
197
+ serialize: (result) => ({
198
+ bytes: new Uint8Array(Buffer.from(result.data)),
199
+ extension: '.txt',
200
+ }),
201
+ });
202
+ const promise = fn({ prompt: 'tracking test', seed: 42 });
203
+ // While generating, progress should show the entry
204
+ const progress = getGenerationProgress();
205
+ expect(progress.summary.total).toBe(1);
206
+ expect(progress.summary.counts['progress-test']).toBe(1);
207
+ expect(progress.generations[0]?.namespace).toBe('progress-test');
208
+ expect(progress.generations[0]?.label).toBe('tracking test');
209
+ expect(progress.generations[0]?.model).toBe('model-42');
210
+ // Resolve the generation
211
+ resolveGenerate({ data: 'done' });
212
+ await promise;
213
+ // After completion, progress should be empty
214
+ const after = getGenerationProgress();
215
+ expect(after.summary.total).toBe(0);
216
+ });
217
+ test('failed generation reports error in progress', async () => {
218
+ const fn = cachedGenerate({
219
+ namespace: 'error-progress',
220
+ prefixFrom: (p) => p.prompt,
221
+ generate: async () => { throw new Error('API failed'); },
222
+ serialize: (result) => ({
223
+ bytes: new Uint8Array(Buffer.from(result.data)),
224
+ extension: '.txt',
225
+ }),
226
+ });
227
+ const result = await fn({ prompt: 'fail test' });
228
+ expect(result).toBeInstanceOf(Error);
229
+ expect(result.message).toBe('API failed');
230
+ // Error should be in the progress drain
231
+ const progress = getGenerationProgress();
232
+ expect(progress.errors.length).toBe(1);
233
+ expect(progress.errors[0]?.error).toBe('API failed');
234
+ expect(progress.errors[0]?.namespace).toBe('error-progress');
235
+ // Errors drain on read — second call should have none
236
+ const progress2 = getGenerationProgress();
237
+ expect(progress2.errors.length).toBe(0);
238
+ });
239
+ });
240
+ // ---------------------------------------------------------------------------
241
+ // Error handling
242
+ // ---------------------------------------------------------------------------
243
+ describe('error handling', () => {
244
+ test('generate throwing returns Error', async () => {
245
+ const fn = cachedGenerate({
246
+ namespace: 'error-test',
247
+ prefixFrom: (p) => p.prompt,
248
+ generate: async () => { throw new Error('boom'); },
249
+ serialize: (result) => ({
250
+ bytes: new Uint8Array(Buffer.from(result.data)),
251
+ extension: '.txt',
252
+ }),
253
+ });
254
+ const result = await fn({ prompt: 'error test' });
255
+ expect(result).toBeInstanceOf(Error);
256
+ expect(result.message).toBe('boom');
257
+ });
258
+ test('non-Error throw is wrapped in Error', async () => {
259
+ const fn = cachedGenerate({
260
+ namespace: 'error-test-2',
261
+ prefixFrom: (p) => p.prompt,
262
+ generate: async () => { throw 'string error'; },
263
+ serialize: (result) => ({
264
+ bytes: new Uint8Array(Buffer.from(result.data)),
265
+ extension: '.txt',
266
+ }),
267
+ });
268
+ const result = await fn({ prompt: 'string error test' });
269
+ expect(result).toBeInstanceOf(Error);
270
+ expect(result.message).toBe('string error');
271
+ });
272
+ test('failed call does not cache — retry regenerates', async () => {
273
+ let shouldFail = true;
274
+ const fn = cachedGenerate({
275
+ namespace: 'retry-test',
276
+ prefixFrom: (p) => p.prompt,
277
+ generate: async (params) => {
278
+ if (shouldFail)
279
+ throw new Error('temporary failure');
280
+ return { data: `ok-${params.prompt}` };
281
+ },
282
+ serialize: (result) => ({
283
+ bytes: new Uint8Array(Buffer.from(result.data)),
284
+ extension: '.txt',
285
+ }),
286
+ });
287
+ const r1 = await fn({ prompt: 'retry' });
288
+ expect(r1).toBeInstanceOf(Error);
289
+ shouldFail = false;
290
+ const r2 = await fn({ prompt: 'retry' });
291
+ expect(r2).not.toBeInstanceOf(Error);
292
+ expect(r2.src).toMatch(/\/generated\/retry-test\//);
293
+ });
294
+ });
295
+ // ---------------------------------------------------------------------------
296
+ // Namespace isolation in dedup queue
297
+ // ---------------------------------------------------------------------------
298
+ describe('namespace isolation', () => {
299
+ test('same params in different namespaces generate independently', async () => {
300
+ let imageCallCount = 0;
301
+ let videoCallCount = 0;
302
+ const imageFn = cachedGenerate({
303
+ namespace: 'ns-image',
304
+ prefixFrom: (p) => p.prompt,
305
+ generate: async (params) => {
306
+ imageCallCount++;
307
+ return { data: `image-${params.prompt}` };
308
+ },
309
+ serialize: (result) => ({
310
+ bytes: new Uint8Array(Buffer.from(result.data)),
311
+ extension: '.png',
312
+ }),
313
+ });
314
+ const videoFn = cachedGenerate({
315
+ namespace: 'ns-video',
316
+ prefixFrom: (p) => p.prompt,
317
+ generate: async (params) => {
318
+ videoCallCount++;
319
+ return { data: `video-${params.prompt}` };
320
+ },
321
+ serialize: (result) => ({
322
+ bytes: new Uint8Array(Buffer.from(result.data)),
323
+ extension: '.mp4',
324
+ }),
325
+ });
326
+ const [imgResult, vidResult] = await Promise.all([
327
+ imageFn({ prompt: 'same params' }),
328
+ videoFn({ prompt: 'same params' }),
329
+ ]);
330
+ expect(imgResult).not.toBeInstanceOf(Error);
331
+ expect(vidResult).not.toBeInstanceOf(Error);
332
+ expect(imageCallCount).toBe(1);
333
+ expect(videoCallCount).toBe(1);
334
+ // Different src paths
335
+ expect(imgResult.src).toMatch(/ns-image/);
336
+ expect(vidResult.src).toMatch(/ns-video/);
337
+ });
338
+ });
339
+ // ---------------------------------------------------------------------------
340
+ // Recursive Uint8Array hashing
341
+ // ---------------------------------------------------------------------------
342
+ describe('recursive binary hashing', () => {
343
+ test('nested Uint8Array values produce stable cache keys', async () => {
344
+ const { config, getCallCount } = makeTestConfig();
345
+ const fn = cachedGenerate(config);
346
+ // Pass nested binary data inside an object
347
+ const r1 = await fn({ prompt: 'nested', inputData: new Uint8Array([10, 20, 30]) });
348
+ const r2 = await fn({ prompt: 'nested', inputData: new Uint8Array([10, 20, 30]) });
349
+ expect(getCallCount()).toBe(1); // same nested bytes → cached
350
+ });
351
+ });
352
+ // ---------------------------------------------------------------------------
353
+ // Serialize failure handling
354
+ // ---------------------------------------------------------------------------
355
+ describe('serialize failure', () => {
356
+ test('serialize throwing unregisters progress and returns Error', async () => {
357
+ // Drain any leftover errors from previous tests
358
+ getGenerationProgress();
359
+ const fn = cachedGenerate({
360
+ namespace: 'serialize-fail',
361
+ prefixFrom: (p) => p.prompt,
362
+ generate: async () => ({ data: 'ok' }),
363
+ serialize: () => { throw new Error('serialize exploded'); },
364
+ });
365
+ const result = await fn({ prompt: 'serialize test' });
366
+ expect(result).toBeInstanceOf(Error);
367
+ expect(result.message).toBe('serialize exploded');
368
+ // Progress should be clean — no stuck entries
369
+ const progress = getGenerationProgress();
370
+ expect(progress.summary.total).toBe(0);
371
+ // Error should be in the drain
372
+ expect(progress.errors.length).toBe(1);
373
+ expect(progress.errors[0]?.error).toBe('serialize exploded');
374
+ });
375
+ });
376
+ // ---------------------------------------------------------------------------
377
+ // cacheKey config excludes non-identity fields
378
+ // ---------------------------------------------------------------------------
379
+ describe('cacheKey config', () => {
380
+ test('excluded fields do not affect cache identity', async () => {
381
+ let callCount = 0;
382
+ const fn = cachedGenerate({
383
+ namespace: 'cachekey-test',
384
+ prefixFrom: (p) => p.label ?? p.query,
385
+ cacheKey: ({ label, ...rest }) => rest,
386
+ generate: async (params) => {
387
+ callCount++;
388
+ return { data: `result-${params.query}` };
389
+ },
390
+ serialize: (result) => ({
391
+ bytes: new Uint8Array(Buffer.from(result.data)),
392
+ extension: '.txt',
393
+ }),
394
+ });
395
+ await fn({ query: 'search', label: 'label-a' });
396
+ await fn({ query: 'search', label: 'label-b' });
397
+ // Same query, different labels → should be same cache entry
398
+ expect(callCount).toBe(1);
399
+ });
400
+ });
401
+ // ---------------------------------------------------------------------------
402
+ // getCacheInfo helper
403
+ // ---------------------------------------------------------------------------
404
+ describe('getCacheInfo', () => {
405
+ test('returns cache miss before any generation', () => {
406
+ const info = getCacheInfo('test-ns', { prompt: 'hello' }, 'hello');
407
+ expect(info.cached).toBeUndefined();
408
+ expect(info.hash).toMatch(/^[a-f0-9]{8}$/);
409
+ expect(info.prefix).toBe('hello');
410
+ });
411
+ test('returns cache hit after generation', async () => {
412
+ const { config } = makeTestConfig();
413
+ const fn = cachedGenerate(config);
414
+ await fn({ prompt: 'cached info test' });
415
+ const info = getCacheInfo('test', { prompt: 'cached info test' }, 'cached info test');
416
+ expect(info.cached).toBeDefined();
417
+ expect(info.cached).toMatch(/cached-info-test-[a-f0-9]{8}\.txt/);
418
+ });
419
+ test('returns fallbackSrc when a previous generation exists', async () => {
420
+ const { config } = makeTestConfig();
421
+ const fn = cachedGenerate(config);
422
+ // Generate first version
423
+ await fn({ prompt: 'fallback test', seed: 1 });
424
+ // Query for a different version that doesn't exist yet
425
+ // Version 1 is in the main dir and should be found as fallback
426
+ const info = getCacheInfo('test', { prompt: 'fallback test', seed: 2 }, 'fallback test');
427
+ expect(info.cached).toBeUndefined();
428
+ expect(info.fallbackSrc).toBeDefined();
429
+ expect(info.fallbackSrc).toMatch(/\/generated\/test\/fallback-test-/);
430
+ });
431
+ });
@@ -0,0 +1,16 @@
1
+ import type { SpeechProvider } from './speech-generate.js';
2
+ import type { WordTimestamp } from './transcription-generate.js';
3
+ /**
4
+ * Parse a Cartesia SSE stream into audio chunks and word timestamps.
5
+ * Uses eventsource-parser for robust SSE frame parsing.
6
+ */
7
+ export declare function parseCartesiaSseStream(body: ReadableStream<Uint8Array>): Promise<{
8
+ audioChunks: Uint8Array[];
9
+ timestamps: WordTimestamp[];
10
+ }>;
11
+ /**
12
+ * Create a Cartesia speech provider using the SSE endpoint with word timestamps.
13
+ * Returns raw PCM audio (pcm_s16le) since the SSE endpoint only supports raw container.
14
+ */
15
+ export declare function createCartesiaSpeechProvider(): SpeechProvider;
16
+ //# sourceMappingURL=cartesia-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartesia-provider.d.ts","sourceRoot":"","sources":["../../src/cli/cartesia-provider.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAA+C,MAAM,sBAAsB,CAAA;AACvG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AA8DhE;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAC/B,OAAO,CAAC;IAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAAC,UAAU,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAsCrE;AAqDD;;;GAGG;AACH,wBAAgB,4BAA4B,IAAI,cAAc,CA6D7D"}