@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.
- package/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +396 -46
- package/apps/studio/api.ts +395 -2
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/photo-bg-remove.sh +119 -21
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +197 -2
- package/apps/studio/canvas-lib.tsx +115 -6
- package/apps/studio/client/app.jsx +1954 -685
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +14 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/photo-knobs.jsx +167 -172
- package/apps/studio/client/styles/3-shell-maude.css +334 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +11 -2
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +2097 -2088
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/footage/schema.test.ts +92 -0
- package/apps/studio/footage/schema.ts +179 -1
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/service.ts +3 -1
- package/apps/studio/http.ts +867 -9
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +13 -1
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +37 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/element-structural-edit.test.ts +108 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/tool-palette.tsx +11 -6
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/use-canvas-media-drop.tsx +4 -2
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/whats-new.json +96 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +33 -0
- package/cli/lib/gitignore-block.mjs +1 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +12 -10
- 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 });
|