@1agh/maude 0.43.0 → 0.44.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 (122) hide show
  1. package/apps/studio/acp/bridge.ts +44 -10
  2. package/apps/studio/acp/env.ts +23 -0
  3. package/apps/studio/acp/login-state.ts +263 -0
  4. package/apps/studio/acp/plugin-bootstrap.ts +31 -25
  5. package/apps/studio/acp/probe.ts +152 -1
  6. package/apps/studio/annotations-layer.tsx +396 -46
  7. package/apps/studio/api.ts +395 -2
  8. package/apps/studio/bin/_audio-search.mjs +140 -0
  9. package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
  10. package/apps/studio/bin/_import-asset.mjs +815 -0
  11. package/apps/studio/bin/_import-brand.mjs +635 -0
  12. package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
  13. package/apps/studio/bin/_import-tokens.mjs +1201 -0
  14. package/apps/studio/bin/_transcribe.mjs +419 -0
  15. package/apps/studio/bin/_transcribe.test.mjs +80 -0
  16. package/apps/studio/bin/audio-search.sh +28 -0
  17. package/apps/studio/bin/generate.sh +154 -0
  18. package/apps/studio/bin/import-asset.sh +34 -0
  19. package/apps/studio/bin/import-brand.sh +29 -0
  20. package/apps/studio/bin/import-tokens.sh +33 -0
  21. package/apps/studio/bin/photo-bg-remove.sh +119 -21
  22. package/apps/studio/bin/transcribe.sh +39 -0
  23. package/apps/studio/canvas-edit.ts +197 -2
  24. package/apps/studio/canvas-lib.tsx +115 -6
  25. package/apps/studio/client/app.jsx +1954 -685
  26. package/apps/studio/client/generate-dialog.jsx +352 -0
  27. package/apps/studio/client/github.js +14 -0
  28. package/apps/studio/client/inspector-controls.jsx +781 -0
  29. package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
  30. package/apps/studio/client/panels/ChatPanel.jsx +15 -33
  31. package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
  32. package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
  33. package/apps/studio/client/panels/ReadinessList.jsx +227 -7
  34. package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
  35. package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
  36. package/apps/studio/client/photo-knobs.jsx +167 -172
  37. package/apps/studio/client/styles/3-shell-maude.css +334 -28
  38. package/apps/studio/client/styles/4-components.css +223 -0
  39. package/apps/studio/client/styles/6-acp-chat.css +86 -0
  40. package/apps/studio/client/tour/quick-setup-tour.js +39 -0
  41. package/apps/studio/commands/annotation-strokes-command.ts +13 -3
  42. package/apps/studio/config.schema.json +70 -0
  43. package/apps/studio/context-menu.tsx +11 -2
  44. package/apps/studio/context.ts +16 -0
  45. package/apps/studio/design-setup-readiness.ts +115 -0
  46. package/apps/studio/dist/client.bundle.js +2097 -2088
  47. package/apps/studio/dist/styles.css +1 -1
  48. package/apps/studio/draw/palette.ts +34 -0
  49. package/apps/studio/footage/schema.test.ts +92 -0
  50. package/apps/studio/footage/schema.ts +179 -1
  51. package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
  52. package/apps/studio/generation/adapters/gemini.ts +542 -0
  53. package/apps/studio/generation/adapters/groq.test.ts +80 -0
  54. package/apps/studio/generation/adapters/groq.ts +247 -0
  55. package/apps/studio/generation/audio-library.test.ts +95 -0
  56. package/apps/studio/generation/audio-library.ts +156 -0
  57. package/apps/studio/generation/captions.test.ts +78 -0
  58. package/apps/studio/generation/captions.ts +132 -0
  59. package/apps/studio/generation/download.ts +76 -0
  60. package/apps/studio/generation/elevenlabs.test.ts +223 -0
  61. package/apps/studio/generation/gemini.test.ts +350 -0
  62. package/apps/studio/generation/jobs.test.ts +128 -0
  63. package/apps/studio/generation/jobs.ts +243 -0
  64. package/apps/studio/generation/keys.test.ts +75 -0
  65. package/apps/studio/generation/keys.ts +162 -0
  66. package/apps/studio/generation/prefs.test.ts +95 -0
  67. package/apps/studio/generation/prefs.ts +82 -0
  68. package/apps/studio/generation/registry.ts +88 -0
  69. package/apps/studio/generation/types.test.ts +123 -0
  70. package/apps/studio/generation/types.ts +266 -0
  71. package/apps/studio/generation/whisper-models.test.ts +152 -0
  72. package/apps/studio/generation/whisper-models.ts +257 -0
  73. package/apps/studio/generation/whisper-spike-results.md +61 -0
  74. package/apps/studio/git/service.ts +3 -1
  75. package/apps/studio/http.ts +867 -9
  76. package/apps/studio/media/intro.mp4 +0 -0
  77. package/apps/studio/media-commit-chain.ts +119 -0
  78. package/apps/studio/paths.ts +10 -0
  79. package/apps/studio/readiness.ts +108 -20
  80. package/apps/studio/scaffold-design.ts +1145 -0
  81. package/apps/studio/server.ts +13 -1
  82. package/apps/studio/test/_helpers.ts +15 -2
  83. package/apps/studio/test/acp-bridge.test.ts +34 -1
  84. package/apps/studio/test/acp-env.test.ts +30 -0
  85. package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
  86. package/apps/studio/test/acp-session-plugins.test.ts +72 -1
  87. package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
  88. package/apps/studio/test/annotations-layer.test.ts +174 -0
  89. package/apps/studio/test/canvas-origin-gate.test.ts +37 -0
  90. package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
  91. package/apps/studio/test/csrf-write-guard.test.ts +23 -1
  92. package/apps/studio/test/design-setup-readiness.test.ts +155 -0
  93. package/apps/studio/test/element-structural-edit.test.ts +108 -0
  94. package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
  95. package/apps/studio/test/generate-route.test.ts +106 -0
  96. package/apps/studio/test/import-asset-browser.test.ts +64 -0
  97. package/apps/studio/test/import-asset-route.test.ts +71 -0
  98. package/apps/studio/test/import-asset.test.ts +399 -0
  99. package/apps/studio/test/import-brand-browser.test.ts +88 -0
  100. package/apps/studio/test/import-brand-route.test.ts +120 -0
  101. package/apps/studio/test/import-brand.test.ts +206 -0
  102. package/apps/studio/test/import-tokens.test.ts +722 -0
  103. package/apps/studio/test/media-commit-chain.test.ts +210 -0
  104. package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
  105. package/apps/studio/test/readiness.test.ts +13 -11
  106. package/apps/studio/test/scaffold-design.test.ts +122 -0
  107. package/apps/studio/test/tour-overlay.test.tsx +16 -0
  108. package/apps/studio/tool-palette.tsx +11 -6
  109. package/apps/studio/ui-prefs.ts +130 -0
  110. package/apps/studio/use-canvas-media-drop.tsx +4 -2
  111. package/apps/studio/use-chrome-visibility.tsx +8 -2
  112. package/apps/studio/whats-new.json +96 -0
  113. package/apps/studio/ws.ts +5 -0
  114. package/cli/bin/maude.mjs +6 -6
  115. package/cli/commands/design.mjs +33 -0
  116. package/cli/lib/gitignore-block.mjs +1 -0
  117. package/cli/lib/pkg-root.mjs +107 -0
  118. package/cli/lib/pkg-root.test.mjs +79 -0
  119. package/cli/lib/reconstruct-toolset.test.mjs +194 -0
  120. package/cli/lib/update-check.mjs +8 -0
  121. package/package.json +12 -10
  122. package/plugins/design/dependencies.json +18 -0
@@ -0,0 +1,352 @@
1
+ // generate-dialog.jsx — the in-app AI-media generate action (feature-ai-media-
2
+ // generation, DDR-16x). Prompt → provider/model/aspect → enqueue on the
3
+ // privileged /_api/generate-jobs route → poll the job → the produced content-
4
+ // addressed asset lands ON the active canvas artboard by default (Phase 1 Task
5
+ // 1.1 — generation is never a dead-end modal). "Place on canvas" can be
6
+ // unchecked to generate-without-placing; the Copy path / Insert buttons are the
7
+ // secondary affordance. Sibling of `maude design generate` (same route).
8
+ //
9
+ // Main-origin shell UI (the route is privileged — never canvas-reachable).
10
+ // Modal chrome mirrors ExportDialog (st-scrim / st-dialog); a running job polls
11
+ // the job list every 1.5s until done/failed (the same completion signal the
12
+ // notification center consumes via the generate:job WS push). Auto-insert reuses
13
+ // the AssetPicker's main-origin source-write lane (insertElementShell →
14
+ // /_api/insert-element) via the `onInsert` prop passed by app.jsx.
15
+
16
+ import { useCallback, useEffect, useRef, useState } from 'react';
17
+
18
+ function Icon({ name, size = 16 }) {
19
+ const p = {
20
+ x: (
21
+ <>
22
+ <line x1="4" y1="4" x2="12" y2="12" />
23
+ <line x1="12" y1="4" x2="4" y2="12" />
24
+ </>
25
+ ),
26
+ sparkle: (
27
+ <path d="M8 2.5l1.2 3.3L12.5 7 9.2 8.2 8 11.5 6.8 8.2 3.5 7l3.3-1.2z" />
28
+ ),
29
+ copy: (
30
+ <>
31
+ <rect x="5.5" y="5.5" width="7.5" height="7.5" rx="1.2" />
32
+ <path d="M3 10.5V3.2A.7.7 0 0 1 3.7 2.5H10" />
33
+ </>
34
+ ),
35
+ }[name];
36
+ return (
37
+ <svg
38
+ width={size}
39
+ height={size}
40
+ viewBox="0 0 16 16"
41
+ fill="none"
42
+ stroke="currentColor"
43
+ strokeWidth="1.4"
44
+ strokeLinecap="round"
45
+ strokeLinejoin="round"
46
+ aria-hidden="true"
47
+ >
48
+ {p}
49
+ </svg>
50
+ );
51
+ }
52
+
53
+ const ASPECTS = ['1:1', '16:9', '9:16', '4:3', '3:4', '4:5'];
54
+
55
+ export default function GenerateDialog({ onClose, onInsert }) {
56
+ const [providers, setProviders] = useState([]);
57
+ const [provider, setProvider] = useState('');
58
+ const [models, setModels] = useState([]);
59
+ const [model, setModel] = useState('');
60
+ const [aspect, setAspect] = useState('1:1');
61
+ const [prompt, setPrompt] = useState('');
62
+ const [busy, setBusy] = useState(false);
63
+ const [status, setStatus] = useState(null); // { ok, msg }
64
+ const [resultAsset, setResultAsset] = useState(null);
65
+ // feature-ai-media-generation Phase 1 (Task 1.1) — auto-insert the finished
66
+ // image onto the canvas by default (generation is never a dead-end modal). The
67
+ // user can uncheck "Place on canvas" to generate-without-placing (the manual
68
+ // Copy path / Insert buttons remain the secondary affordance).
69
+ const [placeOnCanvas, setPlaceOnCanvas] = useState(true);
70
+ const [placed, setPlaced] = useState(null); // null | true (inserted) | false (no target)
71
+ const pollRef = useRef(null);
72
+ // Guard against a double-insert: onInsert's identity changes when the active
73
+ // artboard changes, which would otherwise re-fire the auto-insert effect and
74
+ // splice the SAME asset twice. Record the last auto-inserted path.
75
+ const autoInsertedRef = useRef(null);
76
+
77
+ // Load the image-capable providers + their configured status.
78
+ useEffect(() => {
79
+ fetch('/_api/generate/providers')
80
+ .then((r) => r.json())
81
+ .then((d) => {
82
+ const image = (d?.providers ?? []).filter((p) => p.modalities?.includes('image'));
83
+ setProviders(image);
84
+ const first = image.find((p) => p.configured) ?? image[0];
85
+ if (first) setProvider(first.id);
86
+ })
87
+ .catch(() => setStatus({ ok: false, msg: 'failed to load providers' }));
88
+ }, []);
89
+
90
+ // Model list follows the chosen provider (best-effort — the descriptor drives
91
+ // the fallback; a real listModels lands with the fal aggregator in Phase 1).
92
+ useEffect(() => {
93
+ if (!provider) return;
94
+ // Static model hints live server-side; the providers route doesn't ship them
95
+ // per-provider yet, so leave the model unset (adapter picks its default).
96
+ setModels([]);
97
+ setModel('');
98
+ }, [provider]);
99
+
100
+ useEffect(() => {
101
+ function onKey(e) {
102
+ if (e.key === 'Escape') {
103
+ e.preventDefault();
104
+ onClose();
105
+ }
106
+ }
107
+ window.addEventListener('keydown', onKey);
108
+ return () => window.removeEventListener('keydown', onKey);
109
+ }, [onClose]);
110
+
111
+ useEffect(() => () => clearInterval(pollRef.current), []);
112
+
113
+ // Auto-insert the moment a result lands (keyed on resultAsset so the fresh
114
+ // onInsert prop is used, never the stale one captured in the poll closure).
115
+ // onInsert returns false when there's no target artboard (no canvas open) — in
116
+ // that case fall back to the manual Insert/Copy affordances below.
117
+ useEffect(() => {
118
+ if (!resultAsset || !placeOnCanvas || typeof onInsert !== 'function') return;
119
+ // Insert each result at most once, even if onInsert's identity changes.
120
+ if (autoInsertedRef.current === resultAsset) return;
121
+ autoInsertedRef.current = resultAsset;
122
+ const ok = onInsert(resultAsset) === true;
123
+ setPlaced(ok);
124
+ if (ok) setStatus({ ok: true, msg: 'Placed on canvas ✓' });
125
+ else setStatus({ ok: true, msg: 'Generated — open a canvas artboard to place it, or insert below.' });
126
+ }, [resultAsset, placeOnCanvas, onInsert]);
127
+
128
+ const current = providers.find((p) => p.id === provider);
129
+ const notConfigured = current && !current.configured;
130
+
131
+ const poll = useCallback((jobId) => {
132
+ clearInterval(pollRef.current);
133
+ pollRef.current = setInterval(async () => {
134
+ try {
135
+ const d = await (await fetch('/_api/generate-jobs')).json();
136
+ const job = (d?.jobs ?? []).find((j) => j.id === jobId);
137
+ if (!job) return;
138
+ if (job.status === 'done') {
139
+ clearInterval(pollRef.current);
140
+ setBusy(false);
141
+ const asset = job.assets?.[0] ?? null;
142
+ setResultAsset(asset);
143
+ setStatus({ ok: true, msg: asset ? `Generated ${asset}` : 'Done (no asset)' });
144
+ } else if (job.status === 'failed') {
145
+ clearInterval(pollRef.current);
146
+ setBusy(false);
147
+ setStatus({ ok: false, msg: job.error || 'generation failed' });
148
+ }
149
+ } catch {
150
+ /* transient — keep polling until the deadline below */
151
+ }
152
+ }, 1500);
153
+ // Safety deadline so a wedged job doesn't spin forever.
154
+ setTimeout(() => {
155
+ if (pollRef.current) {
156
+ clearInterval(pollRef.current);
157
+ setBusy((b) => {
158
+ if (b) setStatus({ ok: false, msg: 'timed out waiting for the job' });
159
+ return false;
160
+ });
161
+ }
162
+ }, 180000);
163
+ }, []);
164
+
165
+ async function generate() {
166
+ if (!prompt.trim() || !provider) return;
167
+ setBusy(true);
168
+ setStatus(null);
169
+ setResultAsset(null);
170
+ setPlaced(null);
171
+ autoInsertedRef.current = null;
172
+ try {
173
+ const body = {
174
+ modality: 'image',
175
+ provider,
176
+ prompt: prompt.trim(),
177
+ aspectRatio: aspect,
178
+ ...(model ? { model } : {}),
179
+ };
180
+ const res = await fetch('/_api/generate-jobs', {
181
+ method: 'POST',
182
+ headers: { 'content-type': 'text/plain' },
183
+ body: JSON.stringify(body),
184
+ });
185
+ if (res.status === 202) {
186
+ const { jobId } = await res.json();
187
+ setStatus({ ok: true, msg: 'Generating…' });
188
+ poll(jobId);
189
+ } else {
190
+ setBusy(false);
191
+ setStatus({ ok: false, msg: await res.text() });
192
+ }
193
+ } catch (err) {
194
+ setBusy(false);
195
+ setStatus({ ok: false, msg: err && err.message ? err.message : 'request failed' });
196
+ }
197
+ }
198
+
199
+ return (
200
+ <div
201
+ className="st-scrim"
202
+ role="presentation"
203
+ onMouseDown={(e) => {
204
+ if (e.target === e.currentTarget) onClose();
205
+ }}
206
+ >
207
+ <div className="st-dialog" role="dialog" aria-modal="true" aria-label="Generate media">
208
+ <div className="st-dialog-hd">
209
+ <span className="st-dialog-title">Generate with AI</span>
210
+ <button type="button" className="st-iconbtn" aria-label="Close" onClick={onClose}>
211
+ <Icon name="x" size={15} />
212
+ </button>
213
+ </div>
214
+ <div className="st-dialog-bd">
215
+ <div className="st-rp-hd">Prompt</div>
216
+ <textarea
217
+ className="input st-gen-prompt"
218
+ rows={3}
219
+ value={prompt}
220
+ placeholder="a minimal hero image of a mountain lake at dawn, soft light"
221
+ aria-label="Generation prompt"
222
+ onChange={(e) => setPrompt(e.target.value)}
223
+ />
224
+ <div className="st-dialog-row">
225
+ <label className="st-dialog-lbl" htmlFor="st-gen-provider">
226
+ Provider
227
+ </label>
228
+ <select
229
+ id="st-gen-provider"
230
+ className="st-select"
231
+ value={provider}
232
+ onChange={(e) => setProvider(e.target.value)}
233
+ >
234
+ {providers.map((p) => (
235
+ <option key={p.id} value={p.id}>
236
+ {p.label}
237
+ {p.configured ? '' : ' — no key'}
238
+ </option>
239
+ ))}
240
+ </select>
241
+ </div>
242
+ {models.length > 0 && (
243
+ <div className="st-dialog-row">
244
+ <label className="st-dialog-lbl" htmlFor="st-gen-model">
245
+ Model
246
+ </label>
247
+ <select
248
+ id="st-gen-model"
249
+ className="st-select"
250
+ value={model}
251
+ onChange={(e) => setModel(e.target.value)}
252
+ >
253
+ {models.map((m) => (
254
+ <option key={m.id} value={m.id}>
255
+ {m.label}
256
+ </option>
257
+ ))}
258
+ </select>
259
+ </div>
260
+ )}
261
+ <div className="st-dialog-row">
262
+ <label className="st-dialog-lbl" htmlFor="st-gen-aspect">
263
+ Aspect
264
+ </label>
265
+ <select
266
+ id="st-gen-aspect"
267
+ className="st-select"
268
+ value={aspect}
269
+ onChange={(e) => setAspect(e.target.value)}
270
+ >
271
+ {ASPECTS.map((a) => (
272
+ <option key={a} value={a}>
273
+ {a}
274
+ </option>
275
+ ))}
276
+ </select>
277
+ </div>
278
+ {onInsert && (
279
+ <label className="st-dialog-row" style={{ gap: 8, cursor: 'pointer' }}>
280
+ <input
281
+ type="checkbox"
282
+ checked={placeOnCanvas}
283
+ onChange={(e) => setPlaceOnCanvas(e.target.checked)}
284
+ aria-label="Place the generated image on the canvas"
285
+ />
286
+ <span className="st-dialog-lbl" style={{ margin: 0 }}>
287
+ Place on canvas
288
+ </span>
289
+ </label>
290
+ )}
291
+ {notConfigured && (
292
+ <div className="st-provider-status" style={{ color: 'var(--fg-3)' }}>
293
+ No key for {current.label} — add one in Settings (⌘,).
294
+ </div>
295
+ )}
296
+ {resultAsset && (
297
+ <div className="st-gen-result">
298
+ <img className="st-gen-thumb" src={`/${resultAsset}`} alt="generated" />
299
+ <div className="st-gen-result-actions">
300
+ <button
301
+ type="button"
302
+ className="st-btn"
303
+ onClick={() => navigator.clipboard?.writeText(`/${resultAsset}`)}
304
+ >
305
+ <Icon name="copy" size={13} /> Copy path
306
+ </button>
307
+ {/* Manual Insert stays as a secondary affordance — for a
308
+ generate-without-placing run, or when auto-insert found no
309
+ target artboard (placed === false). */}
310
+ {onInsert && placed !== true && (
311
+ <button
312
+ type="button"
313
+ className="st-btn"
314
+ onClick={() => {
315
+ const ok = onInsert(resultAsset) === true;
316
+ setPlaced(ok);
317
+ setStatus(
318
+ ok
319
+ ? { ok: true, msg: 'Placed on canvas ✓' }
320
+ : { ok: false, msg: 'Open a canvas and select an artboard first.' }
321
+ );
322
+ }}
323
+ >
324
+ Insert
325
+ </button>
326
+ )}
327
+ </div>
328
+ </div>
329
+ )}
330
+ {status && (
331
+ <div
332
+ className="st-provider-status"
333
+ style={{ color: status.ok ? 'var(--accent)' : 'var(--danger, #e5484d)' }}
334
+ >
335
+ {status.msg}
336
+ </div>
337
+ )}
338
+ <div className="st-dialog-row" style={{ justifyContent: 'flex-end', marginTop: 12 }}>
339
+ <button
340
+ type="button"
341
+ className="st-btn st-gen-go"
342
+ disabled={busy || !prompt.trim() || !provider}
343
+ onClick={generate}
344
+ >
345
+ <Icon name="sparkle" size={14} /> {busy ? 'Generating…' : 'Generate'}
346
+ </button>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ );
352
+ }
@@ -107,6 +107,12 @@ export const saveExport = (filename, bytes) => invoke('save_export', { filename,
107
107
  */
108
108
  export const pickMediaFile = () => invoke('pick_media_file');
109
109
 
110
+ /**
111
+ * feature-bulk-media-insert — multi-select counterpart to pickMediaFile.
112
+ * Resolves to `[{name, bytes:[...]}, ...]` (empty array if cancelled).
113
+ */
114
+ export const pickMediaFiles = () => invoke('pick_media_files');
115
+
110
116
  // ── Tauri shell: auto-update (Phase 32 / Task 1) ────────────────────────────────
111
117
  // The shell downloads + stages a newer build in the background and emits
112
118
  // `update-ready` with { version, notes }. The client shows a banner; clicking
@@ -122,3 +128,11 @@ export const restartToUpdate = () => invoke('restart_to_update');
122
128
  export const getCrashReporting = () => invoke('prefs_get_crash_reporting');
123
129
  /** Set the opt-in state. */
124
130
  export const setCrashReporting = (enabled) => invoke('prefs_set_crash_reporting', { enabled });
131
+
132
+ // ── Tauri shell: AI-editing auto-setup opt-out (DDR-166 Decision 5) ─────────────
133
+ // Local-file-only, DEFAULT ON (unlike crash reporting — this is an opt-OUT).
134
+ // Reachable from the readiness checklist, without a terminal.
135
+ /** Current opt-in state (boolean) — whether install/sign-in buttons are offered. */
136
+ export const getClaudeAutoSetup = () => invoke('prefs_get_claude_auto_setup');
137
+ /** Set the opt-in state. Takes effect on the next sidecar spawn (app launch / project switch). */
138
+ export const setClaudeAutoSetup = (enabled) => invoke('prefs_set_claude_auto_setup', { enabled });