feat(regen): per-slide regeneration dialog with block toggles and title editing#410
Open
jaumemir wants to merge 41 commits intoTHU-MAIC:mainfrom
Open
feat(regen): per-slide regeneration dialog with block toggles and title editing#410jaumemir wants to merge 41 commits intoTHU-MAIC:mainfrom
jaumemir wants to merge 41 commits intoTHU-MAIC:mainfrom
Conversation
8-task plan covering API endpoints, hook, dialog component, sidebar button, Stage state machine, and manual verification steps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /api/generate/media-prompt calls a brief LLM turn to convert a slide's indication text into a concise image/video generation prompt, used when the user switches to a media type not present in the original slide. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Synchronous POST /api/generate/scene-content-only that wraps generateSceneContentFromInput and returns raw slide elements without creating or persisting a scene. Loads stage metadata and outlines from server storage so the client only needs to send the edited outline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the single "Internal" banner into "Exported core" (above generateAndStoreMedia) and "Internal helpers" (above callImageApi / callVideoApi / fetchAsBlob) to accurately reflect visibility. Added JSDoc to generateAndStoreMedia matching the style of the other two public functions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Issue 1: reject empty/whitespace indicationText (was bypassing !falsy check) - Issue 3: only append "..." in log line when prompt exceeds 60 chars - Issue 4: split mediaType validation into two checks with correct error codes (MISSING_REQUIRED_FIELD when absent, INVALID_REQUEST when wrong value) - Issue 5: add beforeEach(vi.clearAllMocks) and test for invalid mediaType Auth pattern unchanged: neither scene-actions nor image sibling routes use requireAuth. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tent-only Replace manual x-api-key/x-base-url header reading with resolveModelFromHeaders to follow the server-side key resolution chain. Also cast result.content to GeneratedSlideContent instead of unknown[], add stageData null check (404), improve error message wording, and add a missing stageId test case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 5th test case covering the loadStage-returns-null (404) guard path. Refactored storage mock to use vi.fn() so mockReturnValueOnce works per-test. Also apply themeId shorthand property fix in the route. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements per-slide regeneration orchestration: scene-content-only → scene-actions → TTS audio (with splitLongSpeechActions + user override) → media. Exports pure helpers (outlineToIndication, indicationToOutline, buildMediaGenerations, applyAudioOverride) for use by RegenerateSlideDialog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… commit - Fix 1: Remove mediaPrompt guard from Step 3 (spec says only mediaType !== 'none' required) - Fix 2: Commit overridden speech actions to store before TTS loop so text override is always persisted even if all TTS calls fail - Fix 3: Type newContent as SceneContent instead of unknown, removing the as never cast Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace direct mutation of speechAction objects with index-based immutable updates - Add ttsEnabled guard (matching use-scene-generator.ts pattern) before TTS loop - Fix stale allOutlines in Step 4 outline sync — read fresh from store.getState() - Change store variable from snapshot to store module (getState() pattern) - Add guard for missing json.scene.content in Step 1b actions response Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…i18n strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the ↺ Unicode character in aria-hidden to prevent screen readers from announcing it, add a title attribute matching the existing retry button pattern, and document regenState prop intent in the interface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ideDialog - Add AbortController ref to cancel in-flight media-prompt fetches on media type switch or dialog close/unmount - Pass indication as argument to generatePromptForType (remove stale closure dep) - Add DialogDescription (sr-only) for screen readers - Wrap ↺ decorative characters in aria-hidden spans - Add htmlFor/id pairs to Label+Textarea (indication, audioText) - Use role=group + aria-labelledby for the media type button group Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al to Stage Wire useSceneRegenerator hook into Stage component with full state machine (idle → dialog_open → regenerating → review), a review bar with accept/undo/edit-again actions, and a confirm AlertDialog when navigating away during review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- C1: make regenerate() return boolean; handleRegenerate restores backup and reopens dialog on failure - C2: replace dynamic import() of already-statically-imported module with static import - I1: move RegenState type to module scope - I2: read scene from getCurrentScene() in handleRegenerate to avoid stale closure - I3: inline confirmRegenDiscard logic to remove cascade rememoisation - I4: add onOpenChange to regen confirm AlertDialog so Escape key dismisses it Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pt and set Kling negative_prompt
- Back up the SceneOutline before regeneration starts alongside the scene content, so 'Undo' restores the original indication text in the dialog (previously the outline was updated by Step 4 of the regenerator but never rolled back on discard/undo). - Restore the outline backup in handleRegenUndo, confirmRegenDiscard, and the error-recovery path in handleRegenerate. - Clear backupOutline on handleRegenAccept and confirmRegenKeep. - Add providerOptions.google.generateAudio = false to Veo adapter so Veo 3+ models generate silent video (audio is handled via TTS). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hanged - Add modifyAudio toggle (Switch) to RegenerateSlideDialog; default off. When off, the narration textarea is hidden and skipAudio: true is passed to the regenerator, saving time when only slide content changes. - In use-scene-regenerator: when skipAudio=true, capture existing speech actions before generating new content, then map old text + audioId + audioUrl onto new speech actions by index, skipping the TTS step entirely. - Add modifyAudio field to RegenerateFormValues so state survives error retries. - Fix veo-adapter: gate generateAudio=false to models that support it (Veo 3+ non-fast); fast and Veo 2.0 models reject this API parameter. - Add stage.regen.modifyAudio and stage.regen.audioKeep i18n keys to all 5 locales (en-US, ca, zh-CN, ja-JP, ru-RU). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… empty text
Media:
- Add 'keep' option to media selector ("No regenerar" in Catalan): preserves
existing image/video from the slide without calling any media API.
When 'keep' is selected, the regenerator reads existing image/video elements
(type + src) from the scene canvas before updating, determines the media type
for the outline (so LLM includes the layout slot), then injects the old src
values back after content generation, skipping Step 3 entirely.
- Default media selection now prefers 'keep' when the outline already had media.
- Media prompt textarea is hidden when 'keep' is selected (no prompt needed).
- Add stage.regen.mediaKeep i18n key to all 5 locales.
Narration toggle fix:
- When skipAudio=true, lastRegenValues.audioText was stored as '' causing the
textarea to appear empty when the user activated "Modify narration" on retry.
Fix: capture current speech action text from the scene when skipAudio=true.
Veo audio (separate hotfix already committed):
- Guard generateAudio=false to non-fast Veo 3+ models only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Theme selector: - Add themeId field to RegenerateParams and RegenerateFormValues. - Fetch /api/themes when the dialog opens; show a Select (color dot + name) only when the list is non-empty. - Default to the current settingsStore.themeId (the theme selected at generation time). Persisted in lastRegenValues for retry sessions. - Pass themeId to /api/generate/scene-content-only so LLM prompt instructions reflect the chosen theme. Bug fix: - isSubmitDisabled now also excludes mediaType='keep', so selecting 'No regenerar' (Keep) no longer blocks the Regenerate button. i18n: add stage.regen.theme key to all 5 locales. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dialog footer - New /api/generate/narration-text endpoint — LLM generates spoken narration text from the slide indication (description + key points) - Extract ModelSelectorPopover to components/generation/model-selector-popover.tsx with a CompactModelSelector wrapper (store-connected, zero props needed) - Regen dialog: "Generar amb IA" button under narration textarea (visible when Modify narration toggle is ON) — calls narration-text endpoint, auto-fills textarea - Regen dialog footer left: CompactModelSelector + MediaPopover controls (LLM model, image/video model, TTS model) mirroring the generation toolbar - i18n: generateNarration + generatingNarration keys in all 5 locales Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When narration-text or media-prompt API calls fail (e.g. no model configured, bad deployment), show inline error message instead of silently swallowing it. Error text includes hint pointing to the LLM model controls in the dialog footer bottom-left. i18n: add aiGenerationError + aiModelHint keys in all 5 locales. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds four new translation keys across all five locale files: - modifySlide: toggle for blocking slide regeneration - slideTitle: label for the title input field - slideKeep: message indicating slide will be preserved - slideWarningMediaNeeded: warning about media requiring slide regeneration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Slide helper - Add skipSlide optional field to RegenerateParams interface - Export resolveSkipSlide pure helper function that determines whether to skip slide regeneration based on media type and existing media slot availability Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add import of resolveSkipSlide from use-scene-regenerator - Add 7 comprehensive unit tests covering: - skipSlide=false always returns false - mediaType='none' or 'keep' returns true when skipSlide=true - New media (image/video) only skips if existing media slot present - Returns false for new media when no existing slot All tests passing (16 total: 9 existing + 7 new). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…utline.title Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g to dialog Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…astRegenValues - Add missing `title` and `regenerateSlide` fields to `setLastRegenValues` call in stage.tsx so RegenerateFormValues is fully populated on retry - Move `hasExistingMedia`/`needsNewMedia`/`showSlideWarning` constants before `handleSubmit` in regenerate-slide-dialog.tsx - Fix `updatedOutline` construction: when `!regenerateSlide` (and not forceSlideRegen), spread original outline to preserve all fields (title, description, keyPoints) instead of always calling indicationToOutline Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…showing Two bugs fixed: 1. skipSlide=true + new media: existing canvas element has a real URL (not gen_img_1), so SlideRenderer never consults the media store. After media generation completes, directly patch the matching element src with the server URL via updateScene. 2. skipSlide=false + repeated regens: gen_img_1 is a shared store key. If a previous regen stored gen_img_1=old-url, enqueueTasks skips it and the new slide briefly flashes the wrong image. Fix: resetTask before enqueueTasks to create a fresh pending entry, then patch the canvas src after generation so gen_img_1 is never kept as a permanent placeholder (preventing cross-slide contamination). Add resetTask(elementId) to MediaGenerationState for single-task removal. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After generateAndStoreMedia completes, the server URL is deterministic (e.g. /api/stages/xxx/media/gen_img_1). Patching the canvas with this URL caused the browser to serve the old cached image instead of the newly generated one. Fix: append ?t=<timestamp> to the URL when patching canvas elements, forcing a fresh fetch on each regeneration cycle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a per-slide regeneration dialog that lets users selectively regenerate parts of an existing slide without touching everything:
Modify slideswitch): when OFF, slide content/actions generation is skipped and the existing slide is preserved. Includes a conflict-aware auto-override: if new media is requested but the slide has no existing media slot, the slide is regenerated anyway to create the placeholder element.outline.title, live-binds to<DialogTitle>. Only editable when the slide toggle is ON.Modify narrationswitch): skip TTS generation when narration is unchanged./api/generate/narration-text).Keep / None / Image / Video): independently toggle media regeneration with auto-generated prompts.ca.jsonadditions here apply on top of it.New files
app/api/generate/scene-content-only/route.tsapp/api/generate/narration-text/route.tsapp/api/generate/media-prompt/route.tscomponents/classroom/regenerate-slide-dialog.tsxcomponents/generation/model-selector-popover.tsxlib/hooks/use-scene-regenerator.tslib/i18n/locales/ca.jsonModified files
components/stage.tsxcomponents/stage/scene-sidebar.tsxcomponents/generation/generation-toolbar.tsxModelSelectorPopoverto shared componentlib/media/media-orchestrator.tsgenerateAndStoreMediafor reuselib/i18n/locales/{en-US,zh-CN,ja-JP,ru-RU}.jsonTest plan
<DialogTitle>updates live → regenerated slide and sidebar show new titleresolveSkipSlideunit tests:pnpm test -- tests/hooks/use-scene-regenerator.test.ts🤖 Generated with Claude Code