pi-design-deck 0.1.1 → 0.3.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/README.md +54 -13
- package/deck-schema.ts +30 -0
- package/deck-server.ts +76 -19
- package/export-html.ts +329 -0
- package/form/css/controls.css +171 -0
- package/form/css/layout.css +56 -0
- package/form/css/preview.css +60 -6
- package/form/deck.html +2 -0
- package/form/js/deck-core.js +60 -2
- package/form/js/deck-interact.js +63 -19
- package/form/js/deck-render.js +95 -6
- package/form/js/deck-session.js +140 -27
- package/generate-prompts.ts +18 -12
- package/index.ts +364 -66
- package/package.json +2 -1
- package/prompts/deck-discover.md +3 -1
- package/prompts/deck-plan.md +3 -1
- package/prompts/deck.md +3 -1
- package/skills/design-deck/SKILL.md +44 -8
- package/skills/design-deck/references/component-gallery/INDEX.md +88 -0
- package/skills/design-deck/references/component-gallery/LOOKUP.md +592 -0
- package/skills/design-deck/references/component-gallery/components/INDEX.md +106 -0
- package/skills/design-deck/references/component-gallery/components/actions.md +354 -0
- package/skills/design-deck/references/component-gallery/components/data-display.md +812 -0
- package/skills/design-deck/references/component-gallery/components/feedback.md +513 -0
- package/skills/design-deck/references/component-gallery/components/inputs.md +921 -0
- package/skills/design-deck/references/component-gallery/components/layout.md +167 -0
- package/skills/design-deck/references/component-gallery/components/navigation.md +350 -0
- package/skills/design-deck/references/component-gallery/components/overlays.md +208 -0
- package/skills/design-deck/references/component-gallery/components/utilities.md +29 -0
- package/skills/design-deck/references/component-gallery/components.md +1383 -0
package/form/js/deck-session.js
CHANGED
|
@@ -41,11 +41,26 @@ function showSaveToast(message, isError) {
|
|
|
41
41
|
saveToastTimer = setTimeout(() => { toast.classList.add("hidden"); saveToastTimer = null; }, 3000);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function buildSelectedNotes() {
|
|
45
|
+
const notes = {};
|
|
46
|
+
for (const [slideId, noteData] of Object.entries(optionNotes)) {
|
|
47
|
+
if (noteData && selections[slideId] === noteData.label && noteData.notes) {
|
|
48
|
+
notes[slideId] = noteData.notes;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return notes;
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
async function saveDeck() {
|
|
45
55
|
if (isClosed) return;
|
|
46
56
|
try {
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
57
|
+
const payload = { token: sessionToken, selections, notes: buildSelectedNotes() };
|
|
58
|
+
if (finalNotes) payload.finalNotes = finalNotes;
|
|
59
|
+
const result = await postJson("/save", payload);
|
|
60
|
+
if (result.ok) {
|
|
61
|
+
markSaved(new Date().toISOString());
|
|
62
|
+
showSaveToast(`Saved to ${result.relativePath}`);
|
|
63
|
+
}
|
|
49
64
|
else showSaveToast(result.error || "Save failed", true);
|
|
50
65
|
} catch {
|
|
51
66
|
showSaveToast("Save failed", true);
|
|
@@ -71,10 +86,11 @@ function stopHeartbeat() {
|
|
|
71
86
|
function disableDeckInteractions() {
|
|
72
87
|
if (btnBack) btnBack.disabled = true;
|
|
73
88
|
if (btnNext) btnNext.disabled = true;
|
|
89
|
+
if (btnSave) btnSave.disabled = true;
|
|
74
90
|
document.querySelectorAll(".btn-gen-more, .btn-regen").forEach((button) => {
|
|
75
91
|
button.disabled = true;
|
|
76
92
|
});
|
|
77
|
-
document.querySelectorAll(".gen-prompt").forEach((input) => {
|
|
93
|
+
document.querySelectorAll(".gen-prompt, .gen-count").forEach((input) => {
|
|
78
94
|
input.disabled = true;
|
|
79
95
|
});
|
|
80
96
|
document.querySelectorAll(".model-pill, .model-list-item, .model-default-check").forEach((el) => {
|
|
@@ -98,7 +114,10 @@ async function submitDeck() {
|
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
try {
|
|
101
|
-
|
|
117
|
+
const notes = buildSelectedNotes();
|
|
118
|
+
const payload = { token: sessionToken, selections, notes };
|
|
119
|
+
if (finalNotes) payload.finalNotes = finalNotes;
|
|
120
|
+
await postJson("/submit", payload);
|
|
102
121
|
clearSelectionsStorage();
|
|
103
122
|
isClosed = true;
|
|
104
123
|
if (submitButton) {
|
|
@@ -242,16 +261,22 @@ function restoreGenerateButton(slideId) {
|
|
|
242
261
|
const pending = pendingGenerate.get(slideId);
|
|
243
262
|
if (!pending || pending.isRegen) return;
|
|
244
263
|
|
|
245
|
-
|
|
246
|
-
|
|
264
|
+
// Clear timeout if any
|
|
265
|
+
if (pending.timeoutId) clearTimeout(pending.timeoutId);
|
|
266
|
+
|
|
267
|
+
// Remove all skeletons from DOM (query directly, don't rely on array)
|
|
268
|
+
const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
|
|
269
|
+
if (slideElement) {
|
|
270
|
+
slideElement.querySelectorAll(".option-skeleton").forEach((skel) => skel.remove());
|
|
247
271
|
}
|
|
272
|
+
|
|
248
273
|
pending.button.classList.remove("loading");
|
|
249
274
|
const plus = pending.button.querySelector(".btn-gen-plus");
|
|
250
275
|
if (plus) plus.textContent = "+";
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
276
|
+
pending.button.lastChild.textContent = "Generate";
|
|
277
|
+
|
|
254
278
|
if (pending.input && !isClosed) pending.input.disabled = false;
|
|
279
|
+
if (pending.countSelect && !isClosed) pending.countSelect.disabled = false;
|
|
255
280
|
|
|
256
281
|
pendingGenerate.delete(slideId);
|
|
257
282
|
}
|
|
@@ -292,6 +317,7 @@ function insertGeneratedOption(slideId, option, model) {
|
|
|
292
317
|
if (current === totalSlides - 1) {
|
|
293
318
|
updateSummary();
|
|
294
319
|
}
|
|
320
|
+
markDirty();
|
|
295
321
|
}
|
|
296
322
|
|
|
297
323
|
function replaceSlideOptions(slideId, newOptions) {
|
|
@@ -301,6 +327,7 @@ function replaceSlideOptions(slideId, newOptions) {
|
|
|
301
327
|
slide.options = newOptions;
|
|
302
328
|
|
|
303
329
|
delete selections[slideId];
|
|
330
|
+
delete optionNotes[slideId];
|
|
304
331
|
saveSelectionsToStorage();
|
|
305
332
|
|
|
306
333
|
const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
|
|
@@ -309,16 +336,28 @@ function replaceSlideOptions(slideId, newOptions) {
|
|
|
309
336
|
const optionsGrid = slideElement.querySelector(".options");
|
|
310
337
|
if (!optionsGrid) return;
|
|
311
338
|
|
|
339
|
+
// Remove regeneration overlay and state
|
|
340
|
+
const overlay = optionsGrid.querySelector(".regen-overlay");
|
|
341
|
+
if (overlay) overlay.remove();
|
|
342
|
+
optionsGrid.classList.remove("regenerating");
|
|
343
|
+
optionsGrid.style.position = "";
|
|
344
|
+
|
|
312
345
|
optionsGrid.innerHTML = "";
|
|
313
|
-
optionsGrid.style.opacity = "";
|
|
314
|
-
optionsGrid.style.pointerEvents = "";
|
|
315
346
|
optionsGrid.className = `options ${optionCountClass(newOptions.length, slide.columns)}`;
|
|
316
347
|
|
|
317
348
|
newOptions.forEach((option) => {
|
|
318
349
|
const card = createOptionCard(option, slideId, false);
|
|
350
|
+
card.classList.add("option-regenerated");
|
|
319
351
|
optionsGrid.appendChild(card);
|
|
320
352
|
});
|
|
321
353
|
|
|
354
|
+
// Remove animation class after animation completes
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
optionsGrid.querySelectorAll(".option-regenerated").forEach((el) => {
|
|
357
|
+
el.classList.remove("option-regenerated");
|
|
358
|
+
});
|
|
359
|
+
}, 500);
|
|
360
|
+
|
|
322
361
|
equalizeBlockHeights(slideElement);
|
|
323
362
|
|
|
324
363
|
const pick = slideElement.querySelector(".slide-pick");
|
|
@@ -327,6 +366,7 @@ function replaceSlideOptions(slideId, newOptions) {
|
|
|
327
366
|
if (current === totalSlides - 1) {
|
|
328
367
|
updateSummary();
|
|
329
368
|
}
|
|
369
|
+
markDirty();
|
|
330
370
|
}
|
|
331
371
|
|
|
332
372
|
function connectEvents() {
|
|
@@ -343,9 +383,37 @@ function connectEvents() {
|
|
|
343
383
|
if (!payload || typeof payload.slideId !== "string" || !payload.option) {
|
|
344
384
|
return;
|
|
345
385
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
386
|
+
const pending = pendingGenerate.get(payload.slideId);
|
|
387
|
+
const model = pending?.model || null;
|
|
388
|
+
|
|
389
|
+
// Remove one skeleton
|
|
390
|
+
if (pending?.skeletons?.length > 0) {
|
|
391
|
+
const skel = pending.skeletons.shift();
|
|
392
|
+
if (skel.parentElement) skel.remove();
|
|
393
|
+
}
|
|
394
|
+
|
|
348
395
|
insertGeneratedOption(payload.slideId, payload.option, model);
|
|
396
|
+
|
|
397
|
+
// Track received count and restore button when all received
|
|
398
|
+
if (pending) {
|
|
399
|
+
pending.receivedCount = (pending.receivedCount || 0) + 1;
|
|
400
|
+
if (pending.receivedCount >= (pending.expectedCount || 1)) {
|
|
401
|
+
restoreGenerateButton(payload.slideId);
|
|
402
|
+
} else {
|
|
403
|
+
// Reset timeout for next option
|
|
404
|
+
if (pending.timeoutId) clearTimeout(pending.timeoutId);
|
|
405
|
+
pending.timeoutId = setTimeout(() => {
|
|
406
|
+
const current = pendingGenerate.get(payload.slideId);
|
|
407
|
+
if (!current || current.isRegen) return;
|
|
408
|
+
const received = current.receivedCount || 0;
|
|
409
|
+
const expected = current.expectedCount || 1;
|
|
410
|
+
restoreGenerateButton(payload.slideId);
|
|
411
|
+
if (received < expected) {
|
|
412
|
+
showSaveToast(`Generated ${received} of ${expected} options`, true);
|
|
413
|
+
}
|
|
414
|
+
}, 30000);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
349
417
|
});
|
|
350
418
|
|
|
351
419
|
events.addEventListener("generate-failed", (event) => {
|
|
@@ -359,6 +427,8 @@ function connectEvents() {
|
|
|
359
427
|
restoreGenerateButton(payload.slideId);
|
|
360
428
|
if (payload.reason === "timeout") {
|
|
361
429
|
showSaveToast("Generation timed out — try again", true);
|
|
430
|
+
} else {
|
|
431
|
+
showSaveToast("Generation failed", true);
|
|
362
432
|
}
|
|
363
433
|
}
|
|
364
434
|
});
|
|
@@ -388,6 +458,8 @@ function connectEvents() {
|
|
|
388
458
|
restoreRegenButton(payload.slideId);
|
|
389
459
|
if (payload.reason === "timeout") {
|
|
390
460
|
showSaveToast("Regeneration timed out — try again", true);
|
|
461
|
+
} else {
|
|
462
|
+
showSaveToast("Regeneration failed", true);
|
|
391
463
|
}
|
|
392
464
|
}
|
|
393
465
|
});
|
|
@@ -415,7 +487,7 @@ function connectEvents() {
|
|
|
415
487
|
});
|
|
416
488
|
}
|
|
417
489
|
|
|
418
|
-
async function generateMore(button, slideId, input) {
|
|
490
|
+
async function generateMore(button, slideId, input, countSelect) {
|
|
419
491
|
if (isClosed || pendingGenerate.size > 0) return;
|
|
420
492
|
|
|
421
493
|
const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
|
|
@@ -428,21 +500,42 @@ async function generateMore(button, slideId, input) {
|
|
|
428
500
|
|
|
429
501
|
const prompt = input ? input.value.trim() : "";
|
|
430
502
|
if (input) input.value = "";
|
|
503
|
+
|
|
504
|
+
const count = countSelect ? parseInt(countSelect.value, 10) || 1 : 1;
|
|
505
|
+
|
|
506
|
+
// Create skeletons for each option being generated
|
|
507
|
+
const skeletons = [];
|
|
508
|
+
for (let i = 0; i < count; i++) {
|
|
509
|
+
const skeleton = createElement("div", "option-skeleton");
|
|
510
|
+
optionsGrid.appendChild(skeleton);
|
|
511
|
+
skeletons.push(skeleton);
|
|
512
|
+
}
|
|
431
513
|
|
|
432
|
-
const skeleton = createElement("div", "option-skeleton");
|
|
433
|
-
optionsGrid.appendChild(skeleton);
|
|
434
|
-
|
|
435
|
-
const originalText = button.childNodes[1] ? button.childNodes[1].textContent || "" : "";
|
|
436
514
|
button.classList.add("loading");
|
|
437
515
|
const plus = button.querySelector(".btn-gen-plus");
|
|
438
516
|
if (plus) plus.textContent = "";
|
|
439
|
-
|
|
517
|
+
button.lastChild.textContent = "Generating...";
|
|
440
518
|
if (input) input.disabled = true;
|
|
519
|
+
if (countSelect) countSelect.disabled = true;
|
|
520
|
+
|
|
521
|
+
// Set timeout for generation (30s per option)
|
|
522
|
+
const timeoutId = setTimeout(() => {
|
|
523
|
+
const pending = pendingGenerate.get(slideId);
|
|
524
|
+
if (pending && !pending.isRegen) {
|
|
525
|
+
const received = pending.receivedCount || 0;
|
|
526
|
+
restoreGenerateButton(slideId);
|
|
527
|
+
if (received === 0) {
|
|
528
|
+
showSaveToast("Generation timed out — try again", true);
|
|
529
|
+
} else {
|
|
530
|
+
showSaveToast(`Generated ${received} of ${count} options`, true);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}, 30000 * count);
|
|
441
534
|
|
|
442
|
-
pendingGenerate.set(slideId, { button,
|
|
535
|
+
pendingGenerate.set(slideId, { button, skeletons, input, countSelect, model: selectedModel || null, expectedCount: count, receivedCount: 0, timeoutId });
|
|
443
536
|
|
|
444
537
|
try {
|
|
445
|
-
const body = { token: sessionToken, slideId };
|
|
538
|
+
const body = { token: sessionToken, slideId, count };
|
|
446
539
|
if (prompt) body.prompt = prompt;
|
|
447
540
|
if (hasModelBar) {
|
|
448
541
|
body.model = selectedModel;
|
|
@@ -470,8 +563,21 @@ async function regenerateSlide(button, slideId) {
|
|
|
470
563
|
const prompt = input ? input.value.trim() : "";
|
|
471
564
|
if (input) input.value = "";
|
|
472
565
|
|
|
473
|
-
|
|
474
|
-
|
|
566
|
+
// Create skeleton overlay
|
|
567
|
+
const optionCount = slide.options?.length || 2;
|
|
568
|
+
const colClass = optionsGrid.classList.contains("cols-3") ? "cols-3" :
|
|
569
|
+
optionsGrid.classList.contains("cols-1") ? "cols-1" : "cols-2";
|
|
570
|
+
const overlay = createElement("div", `regen-overlay ${colClass}`);
|
|
571
|
+
for (let i = 0; i < optionCount; i++) {
|
|
572
|
+
overlay.appendChild(createElement("div", "regen-skeleton"));
|
|
573
|
+
}
|
|
574
|
+
const status = createElement("div", "regen-status", "Regenerating options...");
|
|
575
|
+
overlay.appendChild(status);
|
|
576
|
+
|
|
577
|
+
// Position overlay relative to options grid
|
|
578
|
+
optionsGrid.style.position = "relative";
|
|
579
|
+
optionsGrid.classList.add("regenerating");
|
|
580
|
+
optionsGrid.appendChild(overlay);
|
|
475
581
|
|
|
476
582
|
const originalText = button.textContent || "";
|
|
477
583
|
button.classList.add("loading");
|
|
@@ -482,7 +588,7 @@ async function regenerateSlide(button, slideId) {
|
|
|
482
588
|
const genMoreBtn = slideElement.querySelector(".btn-gen-more");
|
|
483
589
|
if (genMoreBtn) genMoreBtn.disabled = true;
|
|
484
590
|
|
|
485
|
-
pendingGenerate.set(slideId, { button, originalText, input, isRegen: true, optionsGrid, genMoreBtn });
|
|
591
|
+
pendingGenerate.set(slideId, { button, originalText, input, isRegen: true, optionsGrid, genMoreBtn, overlay });
|
|
486
592
|
|
|
487
593
|
try {
|
|
488
594
|
const body = { token: sessionToken, slideId };
|
|
@@ -502,7 +608,7 @@ function restoreRegenButton(slideId) {
|
|
|
502
608
|
if (!pending || !pending.isRegen) return;
|
|
503
609
|
pendingGenerate.delete(slideId);
|
|
504
610
|
|
|
505
|
-
const { button, originalText, input, optionsGrid, genMoreBtn } = pending;
|
|
611
|
+
const { button, originalText, input, optionsGrid, genMoreBtn, overlay } = pending;
|
|
506
612
|
if (button) {
|
|
507
613
|
button.classList.remove("loading");
|
|
508
614
|
button.disabled = false;
|
|
@@ -510,8 +616,11 @@ function restoreRegenButton(slideId) {
|
|
|
510
616
|
}
|
|
511
617
|
if (input) input.disabled = false;
|
|
512
618
|
if (optionsGrid) {
|
|
513
|
-
optionsGrid.
|
|
514
|
-
optionsGrid.style.
|
|
619
|
+
optionsGrid.classList.remove("regenerating");
|
|
620
|
+
optionsGrid.style.position = "";
|
|
621
|
+
}
|
|
622
|
+
if (overlay && overlay.parentElement) {
|
|
623
|
+
overlay.remove();
|
|
515
624
|
}
|
|
516
625
|
if (genMoreBtn) genMoreBtn.disabled = false;
|
|
517
626
|
}
|
|
@@ -539,6 +648,9 @@ function initSaveShortcut() {
|
|
|
539
648
|
document.querySelectorAll(".mod-key").forEach((el) => {
|
|
540
649
|
el.textContent = isMac ? "⌘" : "Ctrl";
|
|
541
650
|
});
|
|
651
|
+
if (btnSave) {
|
|
652
|
+
btnSave.addEventListener("click", () => saveDeck());
|
|
653
|
+
}
|
|
542
654
|
document.addEventListener("keydown", (e) => {
|
|
543
655
|
const mod = isMac ? e.metaKey : e.ctrlKey;
|
|
544
656
|
if (mod && e.key === "s") {
|
|
@@ -570,6 +682,7 @@ function init() {
|
|
|
570
682
|
fetchModels().then((data) => {
|
|
571
683
|
if (data) initModelBar(data);
|
|
572
684
|
});
|
|
685
|
+
updateSaveStatus();
|
|
573
686
|
|
|
574
687
|
document.addEventListener("keydown", handleKeydown);
|
|
575
688
|
window.addEventListener("beforeunload", sendCancelBeacon);
|
package/generate-prompts.ts
CHANGED
|
@@ -28,15 +28,15 @@ function optionTemplate(hasBlocks: boolean): string {
|
|
|
28
28
|
|
|
29
29
|
function modelHints(generateModel?: string, thinking?: string, action?: string): string {
|
|
30
30
|
if (!generateModel) return "";
|
|
31
|
-
const verb = action === "replace-options" ? "replace-options" : "add-
|
|
32
|
-
let hint = `\nGenerate
|
|
31
|
+
const verb = action === "replace-options" ? "replace-options" : "add-options";
|
|
32
|
+
let hint = `\nGenerate options using deck_generate({ model: "${generateModel}", task: "..." }), then push with ${verb}.`;
|
|
33
33
|
if (thinking && thinking !== "off") {
|
|
34
34
|
hint += `\nUse thinking level: "${thinking}".`;
|
|
35
35
|
}
|
|
36
36
|
return hint;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | undefined, prompt?: string, generateModel?: string, thinking?: string): string {
|
|
39
|
+
export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | undefined, prompt?: string, generateModel?: string, thinking?: string, count: number = 1): string {
|
|
40
40
|
const title = slide?.title ?? slideId;
|
|
41
41
|
const context = slide?.context ? `\nContext: ${slide.context}` : "";
|
|
42
42
|
|
|
@@ -54,15 +54,20 @@ export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | unde
|
|
|
54
54
|
const { hasBlocks, formatHint } = formatHintForSlide(slide);
|
|
55
55
|
const template = optionTemplate(hasBlocks);
|
|
56
56
|
const userInstructions = prompt ? `\nUser instructions: "${prompt}"` : "";
|
|
57
|
+
|
|
58
|
+
const optionWord = count === 1 ? "option" : "options";
|
|
57
59
|
|
|
58
60
|
return (
|
|
59
|
-
"The design deck is still open.\n\n" +
|
|
60
|
-
`User
|
|
61
|
+
"The design deck is still open and waiting for your response.\n\n" +
|
|
62
|
+
`User clicked "Generate ${count} ${optionWord}" for slide \"${title}\".${context}${userInstructions}\n\n` +
|
|
61
63
|
`Existing options:\n${existingText}\n\n` +
|
|
62
|
-
`
|
|
63
|
-
`
|
|
64
|
-
`
|
|
65
|
-
|
|
64
|
+
`YOU MUST generate ${count} distinctive additional ${optionWord} and call design_deck with add-options (one call with all options in an array). ` +
|
|
65
|
+
`Do not skip this step or decide the user has enough options — they explicitly requested ${count === 1 ? "another one" : `${count} more`}.${modelHints(generateModel, thinking, "add-options")}\n\n` +
|
|
66
|
+
`design_deck({\"action\":\"add-options\",\"slideId\":\"${slideId}\",\"options\":\"[${template}${count > 1 ? ", ..." : ""}]\"})` +
|
|
67
|
+
`\n\nThe options field must be a JSON string containing an array of ${count} option object${count > 1 ? "s" : ""}.\n` +
|
|
68
|
+
`Each option needs: label, optional description, optional aside (explanatory notes below preview), optional recommended, and either previewHtml or previewBlocks.\n` +
|
|
69
|
+
`${formatHint}` +
|
|
70
|
+
(count > 1 ? `\n\nMake each option distinctive — they should represent genuinely different approaches.` : "")
|
|
66
71
|
);
|
|
67
72
|
}
|
|
68
73
|
|
|
@@ -75,9 +80,10 @@ export function buildRegenerateResult(slideId: string, slide: DeckSlide | undefi
|
|
|
75
80
|
const userInstructions = prompt ? `\nUser instructions: "${prompt}"` : "";
|
|
76
81
|
|
|
77
82
|
return (
|
|
78
|
-
"The design deck is still open.\n\n" +
|
|
79
|
-
`User
|
|
80
|
-
`
|
|
83
|
+
"The design deck is still open and waiting for your response.\n\n" +
|
|
84
|
+
`User clicked "Regenerate all" for slide \"${title}\".${context}${userInstructions}\n\n` +
|
|
85
|
+
`YOU MUST generate ${optionCount} fresh, distinctive options and call design_deck with replace-options. ` +
|
|
86
|
+
`Do not skip this step — the user explicitly requested regeneration.${modelHints(generateModel, thinking, "replace-options")}\n\n` +
|
|
81
87
|
`design_deck({\"action\":\"replace-options\",\"slideId\":\"${slideId}\",\"options\":\"[${template}, ...]\"})` +
|
|
82
88
|
`\n\nThe options field must be a JSON string containing an array of ${optionCount} option objects.\n` +
|
|
83
89
|
`Each option needs: label, optional description, optional aside, optional recommended, and either previewHtml or previewBlocks.\n` +
|