@ohos-ports/slides-grab 1.5.0-beta.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README-ko.md +292 -0
  3. package/README.md +301 -0
  4. package/bin/ohos-postinstall.cjs +118 -0
  5. package/bin/ppt-agent.js +620 -0
  6. package/convert.cjs +20 -0
  7. package/native/@img/sharp-libvips-openharmony-arm64/lib/glib-2.0/include/glibconfig.h +220 -0
  8. package/native/@img/sharp-libvips-openharmony-arm64/lib/index.js +1 -0
  9. package/native/@img/sharp-libvips-openharmony-arm64/lib/libexpat.so +0 -0
  10. package/native/@img/sharp-libvips-openharmony-arm64/lib/libffi.so +0 -0
  11. package/native/@img/sharp-libvips-openharmony-arm64/lib/libjpeg.so +0 -0
  12. package/native/@img/sharp-libvips-openharmony-arm64/lib/libpcre2-8.so.0 +0 -0
  13. package/native/@img/sharp-libvips-openharmony-arm64/lib/libvips-cpp.so.42.20.3 +0 -0
  14. package/native/@img/sharp-libvips-openharmony-arm64/lib/libvips.so.42.20.3 +0 -0
  15. package/native/@img/sharp-libvips-openharmony-arm64/package.json +13 -0
  16. package/native/@img/sharp-openharmony-arm64/lib/sharp-openharmony-arm64.node +0 -0
  17. package/native/@img/sharp-openharmony-arm64/package.json +16 -0
  18. package/package.json +95 -0
  19. package/runtimes/claude-code/agents/design-critic-agent.md +23 -0
  20. package/runtimes/codex/agents/slides-grab-design-critic.md +22 -0
  21. package/scripts/build-viewer.js +444 -0
  22. package/scripts/design-gate.js +258 -0
  23. package/scripts/download-video.js +213 -0
  24. package/scripts/editor-server.js +1167 -0
  25. package/scripts/figma-export.js +169 -0
  26. package/scripts/generate-image.js +116 -0
  27. package/scripts/generate-images.js +164 -0
  28. package/scripts/html2pdf.js +822 -0
  29. package/scripts/html2png.js +246 -0
  30. package/scripts/html2pptx.js +162 -0
  31. package/scripts/import-template.js +86 -0
  32. package/scripts/install-runtime.js +216 -0
  33. package/scripts/render-tldraw.js +44 -0
  34. package/scripts/validate-slides.js +221 -0
  35. package/skills/slides-grab/SKILL.md +65 -0
  36. package/skills/slides-grab/references/presentation-workflow-reference.md +61 -0
  37. package/skills/slides-grab-card-news/SKILL.md +35 -0
  38. package/skills/slides-grab-design/SKILL.md +88 -0
  39. package/skills/slides-grab-design/references/beautiful-slide-defaults.md +88 -0
  40. package/skills/slides-grab-design/references/design-gate.md +349 -0
  41. package/skills/slides-grab-design/references/design-rules.md +76 -0
  42. package/skills/slides-grab-design/references/design-system-full.md +568 -0
  43. package/skills/slides-grab-design/references/detailed-design-rules.md +70 -0
  44. package/skills/slides-grab-export/SKILL.md +55 -0
  45. package/skills/slides-grab-export/references/export-rules.md +27 -0
  46. package/skills/slides-grab-export/references/html2pptx.md +627 -0
  47. package/skills/slides-grab-export/references/ooxml.md +427 -0
  48. package/skills/slides-grab-export/references/pptx-skill-reference.md +189 -0
  49. package/skills/slides-grab-html/SKILL.md +67 -0
  50. package/skills/slides-grab-image/SKILL.md +82 -0
  51. package/skills/slides-grab-plan/SKILL.md +68 -0
  52. package/skills/slides-grab-plan/references/design-md-to-slides-conversion.md +135 -0
  53. package/skills/slides-grab-plan/references/outline-format.md +47 -0
  54. package/skills/slides-grab-plan/references/plan-workflow-reference.md +140 -0
  55. package/src/codex-imagen.js +182 -0
  56. package/src/design-diversity-data.js +6932 -0
  57. package/src/design-gate-report.js +244 -0
  58. package/src/design-gate-state.js +329 -0
  59. package/src/design-import.js +164 -0
  60. package/src/design-md-parser.js +415 -0
  61. package/src/design-styles-data.js +1928 -0
  62. package/src/design-styles.js +209 -0
  63. package/src/editor/codex-edit.js +584 -0
  64. package/src/editor/edit-subprocess.js +170 -0
  65. package/src/editor/editor-codex-prompt.md +50 -0
  66. package/src/editor/editor.html +1785 -0
  67. package/src/editor/js/editor-bbox.js +332 -0
  68. package/src/editor/js/editor-chat.js +56 -0
  69. package/src/editor/js/editor-direct-edit.js +238 -0
  70. package/src/editor/js/editor-dom.js +59 -0
  71. package/src/editor/js/editor-init.js +405 -0
  72. package/src/editor/js/editor-navigation.js +54 -0
  73. package/src/editor/js/editor-select.js +558 -0
  74. package/src/editor/js/editor-send.js +175 -0
  75. package/src/editor/js/editor-sse.js +163 -0
  76. package/src/editor/js/editor-state.js +41 -0
  77. package/src/editor/js/editor-type.js +71 -0
  78. package/src/editor/js/editor-utils.js +167 -0
  79. package/src/editor/js/model-registry.js +37 -0
  80. package/src/editor/screenshot.js +82 -0
  81. package/src/export-resolution.cjs +68 -0
  82. package/src/figma.js +71 -0
  83. package/src/html2pptx-scale.cjs +120 -0
  84. package/src/html2pptx.cjs +1262 -0
  85. package/src/image-contract.js +329 -0
  86. package/src/image-native.js +468 -0
  87. package/src/nano-banana.js +841 -0
  88. package/src/ohos-browser-bridge.cjs +272 -0
  89. package/src/pptx-raster-export.cjs +299 -0
  90. package/src/resolve.js +110 -0
  91. package/src/slide-mode.cjs +72 -0
  92. package/src/template-fidelity.js +267 -0
  93. package/src/template-import.js +505 -0
  94. package/src/template-layout.js +261 -0
  95. package/src/template-pack.js +255 -0
  96. package/src/tldraw/render.js +473 -0
  97. package/src/validation/cli.js +120 -0
  98. package/src/validation/core.js +1023 -0
  99. package/templates/chart.html +121 -0
  100. package/templates/closing.html +54 -0
  101. package/templates/content.html +50 -0
  102. package/templates/contents.html +60 -0
  103. package/templates/cover.html +64 -0
  104. package/templates/custom/.gitkeep +0 -0
  105. package/templates/custom/README.md +7 -0
  106. package/templates/design-styles/README.md +20 -0
  107. package/templates/design-styles/preview.html +4438 -0
  108. package/templates/diagram-tldraw.html +56 -0
  109. package/templates/diagram.html +98 -0
  110. package/templates/quote.html +31 -0
  111. package/templates/section-divider.html +43 -0
  112. package/templates/split-layout.html +43 -0
  113. package/templates/statistics.html +55 -0
  114. package/templates/team.html +49 -0
  115. package/templates/timeline.html +59 -0
@@ -0,0 +1,405 @@
1
+ // editor-init.js — Entry point: imports, event bindings, init()
2
+
3
+ import { state, TOOL_MODE_DRAW, TOOL_MODE_SELECT, setSlideFrame } from './editor-state.js';
4
+ import {
5
+ btnPrev, btnNext, slideIframe, slideWrapper, drawLayer, promptInput, modelSelect,
6
+ imageProviderSelect,
7
+ objectLayer,
8
+ btnSend, btnClearBboxes, slideCounter,
9
+ toggleBold, toggleItalic, toggleUnderline, toggleStrike,
10
+ alignLeft, alignCenter, alignRight,
11
+ popoverTextInput, popoverApplyText, popoverTextColorInput, popoverBgColorInput,
12
+ popoverSizeInput, popoverApplySize, toolModeDrawBtn, toolModeSelectBtn,
13
+ } from './editor-dom.js';
14
+ import {
15
+ currentSlideFile, getSlideState, normalizeModelName, setStatus,
16
+ saveSelectedModel, loadModelOptions, clamp,
17
+ } from './editor-utils.js';
18
+ import { renderChatMessages } from './editor-chat.js';
19
+ import {
20
+ onBboxChange, renderBboxes, scaleSlide, startDrawing, moveDrawing, endDrawing,
21
+ clearBboxesForCurrentSlide, initBboxLayerEvents, getXPath,
22
+ } from './editor-bbox.js';
23
+ import {
24
+ setToolMode, updateToolModeUI, renderObjectSelection, updateObjectEditorControls,
25
+ getSelectedObjectElement, setSelectedObjectXPath, updateHoveredObjectFromPointer,
26
+ clearHoveredObject, getSelectableTargetAt, readSelectedObjectStyleState,
27
+ initObjectInteractionEvents, setObjectTransformCommitHandler, consumeObjectTransformClickSuppression,
28
+ } from './editor-select.js';
29
+ import {
30
+ mutateSelectedObject, applyTextDecorationToken,
31
+ scheduleDirectSave, recordDirectEditHistory, undoDirectEdit, redoDirectEdit, clearDirectEditHistory,
32
+ consumeInternalDocumentRestore,
33
+ } from './editor-direct-edit.js';
34
+ import { updateSendState, applyChanges } from './editor-send.js';
35
+ import { goToSlide } from './editor-navigation.js';
36
+ import { connectSSE, loadRunsInitial } from './editor-sse.js';
37
+ import { configureEditorClient, getEditorClient, updateEditorClientLabels } from './editor-type.js';
38
+
39
+ // Late-binding: connect bbox changes to updateSendState
40
+ onBboxChange(updateSendState);
41
+
42
+ // Bbox layer events
43
+ initBboxLayerEvents();
44
+
45
+ // Navigation
46
+ btnPrev.addEventListener('click', () => { void goToSlide(state.currentIndex - 1); });
47
+ btnNext.addEventListener('click', () => { void goToSlide(state.currentIndex + 1); });
48
+
49
+ // Tool modes
50
+ toolModeDrawBtn.addEventListener('click', () => {
51
+ setToolMode(TOOL_MODE_DRAW);
52
+ updateEditorClientLabels();
53
+ });
54
+ toolModeSelectBtn.addEventListener('click', () => {
55
+ setToolMode(TOOL_MODE_SELECT);
56
+ updateEditorClientLabels();
57
+ });
58
+
59
+ // Clear bboxes
60
+ btnClearBboxes.addEventListener('click', clearBboxesForCurrentSlide);
61
+
62
+ // Drawing
63
+ drawLayer.addEventListener('mousedown', startDrawing);
64
+ drawLayer.addEventListener('mousemove', (event) => {
65
+ if (state.toolMode !== TOOL_MODE_SELECT) return;
66
+ updateHoveredObjectFromPointer(event.clientX, event.clientY);
67
+ });
68
+ drawLayer.addEventListener('mouseleave', clearHoveredObject);
69
+ drawLayer.addEventListener('click', (event) => {
70
+ if (state.toolMode !== TOOL_MODE_SELECT) return;
71
+ if (consumeObjectTransformClickSuppression()) return;
72
+ if (event.target && event.target.closest && event.target.closest('.object-handle')) return;
73
+ const target = getSelectableTargetAt(event.clientX, event.clientY);
74
+ if (!target) {
75
+ setSelectedObjectXPath('', 'No selectable object at this point.');
76
+ return;
77
+ }
78
+
79
+ const xpath = getXPath(target);
80
+ setSelectedObjectXPath(xpath, `Object selected on ${currentSlideFile()}.`);
81
+ });
82
+ if (objectLayer) {
83
+ objectLayer.addEventListener('mousemove', (event) => {
84
+ if (state.toolMode !== TOOL_MODE_SELECT) return;
85
+ updateHoveredObjectFromPointer(event.clientX, event.clientY);
86
+ });
87
+ objectLayer.addEventListener('mouseleave', clearHoveredObject);
88
+ objectLayer.addEventListener('click', (event) => {
89
+ if (state.toolMode !== TOOL_MODE_SELECT) return;
90
+ if (consumeObjectTransformClickSuppression()) return;
91
+ const target = event.target;
92
+ if (!(target instanceof HTMLElement)) return;
93
+ if (target.closest('.object-handle')) return;
94
+
95
+ const selectable = getSelectableTargetAt(event.clientX, event.clientY);
96
+ if (!selectable) {
97
+ setSelectedObjectXPath('', 'No selectable object at this point.');
98
+ return;
99
+ }
100
+
101
+ const xpath = getXPath(selectable);
102
+ setSelectedObjectXPath(xpath, `Object selected on ${currentSlideFile()}.`);
103
+ });
104
+ }
105
+ window.addEventListener('mousemove', moveDrawing);
106
+ window.addEventListener('mouseup', endDrawing);
107
+
108
+ initObjectInteractionEvents();
109
+ setObjectTransformCommitHandler(({ beforeHtml } = {}) => {
110
+ recordDirectEditHistory(beforeHtml);
111
+ scheduleDirectSave(300, 'Object geometry updated and saved.');
112
+ });
113
+
114
+ // Send
115
+ btnSend.addEventListener('click', applyChanges);
116
+
117
+ // Model select
118
+ modelSelect.addEventListener('change', () => {
119
+ const nextModel = normalizeModelName(modelSelect.value);
120
+ if (!state.availableModels.includes(nextModel)) {
121
+ modelSelect.value = state.selectedModel;
122
+ return;
123
+ }
124
+
125
+ const slide = currentSlideFile();
126
+ if (slide) {
127
+ const ss = getSlideState(slide);
128
+ ss.model = nextModel;
129
+ }
130
+ state.selectedModel = nextModel;
131
+ state.defaultModel = nextModel;
132
+ saveSelectedModel(state.selectedModel);
133
+ updateSendState();
134
+ setStatus(`Model selected: ${state.selectedModel}`);
135
+ });
136
+
137
+ imageProviderSelect?.addEventListener('change', () => {
138
+ state.imageProvider = imageProviderSelect.value || 'codex';
139
+ updateSendState();
140
+ setStatus(`Image provider selected: ${state.imageProvider}`);
141
+ });
142
+
143
+ // Prompt input
144
+ promptInput.addEventListener('input', () => {
145
+ const slide = currentSlideFile();
146
+ if (slide) {
147
+ const ss = getSlideState(slide);
148
+ ss.prompt = promptInput.value;
149
+ }
150
+ updateSendState();
151
+ });
152
+
153
+ // Text editing
154
+ popoverApplyText.addEventListener('click', () => {
155
+ if (popoverApplyText.disabled) return;
156
+ mutateSelectedObject((el) => {
157
+ const escaped = popoverTextInput.value
158
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
159
+ el.innerHTML = escaped.replace(/\n/g, '<br>');
160
+ }, 'Object text updated and saved.', { delay: 120 });
161
+ });
162
+
163
+ popoverApplySize.addEventListener('click', () => {
164
+ if (popoverApplySize.disabled) return;
165
+ const size = clamp(Number.parseInt(popoverSizeInput.value || '24', 10) || 24, 8, 180);
166
+ mutateSelectedObject((el) => {
167
+ el.style.fontSize = `${size}px`;
168
+ }, 'Object font size updated and saved.');
169
+ });
170
+
171
+ popoverTextInput.addEventListener('keydown', (event) => {
172
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ popoverApplyText.click();
176
+ }
177
+ });
178
+
179
+ popoverSizeInput.addEventListener('keydown', (event) => {
180
+ if (event.key === 'Enter') {
181
+ event.preventDefault();
182
+ event.stopPropagation();
183
+ popoverApplySize.click();
184
+ }
185
+ });
186
+
187
+ popoverTextColorInput.addEventListener('input', () => {
188
+ if (popoverTextColorInput.disabled) return;
189
+ mutateSelectedObject((el) => {
190
+ el.style.color = popoverTextColorInput.value;
191
+ }, 'Text color updated.', { delay: 300 });
192
+ });
193
+
194
+ popoverBgColorInput.addEventListener('input', () => {
195
+ if (popoverBgColorInput.disabled) return;
196
+ mutateSelectedObject((el) => {
197
+ el.style.backgroundColor = popoverBgColorInput.value;
198
+ }, 'Background color updated.', { delay: 300 });
199
+ });
200
+
201
+ function hasEditableFocus() {
202
+ const activeElement = document.activeElement;
203
+ if (!(activeElement instanceof HTMLElement)) return false;
204
+ if (activeElement.matches('input, textarea, select')) return true;
205
+ return activeElement.isContentEditable;
206
+ }
207
+
208
+ // Style toggles
209
+ toggleBold.addEventListener('click', () => {
210
+ mutateSelectedObject((el) => {
211
+ const nextBold = !readSelectedObjectStyleState(el).bold;
212
+ el.style.fontWeight = nextBold ? '700' : '400';
213
+ }, 'Object font weight updated and saved.');
214
+ });
215
+
216
+ toggleItalic.addEventListener('click', () => {
217
+ mutateSelectedObject((el) => {
218
+ const nextItalic = !readSelectedObjectStyleState(el).italic;
219
+ el.style.fontStyle = nextItalic ? 'italic' : 'normal';
220
+ }, 'Object font style updated and saved.');
221
+ });
222
+
223
+ toggleUnderline.addEventListener('click', () => {
224
+ mutateSelectedObject((el) => {
225
+ const nextUnderline = !readSelectedObjectStyleState(el).underline;
226
+ applyTextDecorationToken(el, 'underline', nextUnderline);
227
+ }, 'Object underline updated and saved.');
228
+ });
229
+
230
+ toggleStrike.addEventListener('click', () => {
231
+ mutateSelectedObject((el) => {
232
+ const nextStrike = !readSelectedObjectStyleState(el).strike;
233
+ applyTextDecorationToken(el, 'line-through', nextStrike);
234
+ }, 'Object strikethrough updated and saved.');
235
+ });
236
+
237
+ // Alignment
238
+ alignLeft.addEventListener('click', () => {
239
+ mutateSelectedObject((el) => {
240
+ el.style.textAlign = 'left';
241
+ }, 'Object alignment updated and saved.');
242
+ });
243
+
244
+ alignCenter.addEventListener('click', () => {
245
+ mutateSelectedObject((el) => {
246
+ el.style.textAlign = 'center';
247
+ }, 'Object alignment updated and saved.');
248
+ });
249
+
250
+ alignRight.addEventListener('click', () => {
251
+ mutateSelectedObject((el) => {
252
+ el.style.textAlign = 'right';
253
+ }, 'Object alignment updated and saved.');
254
+ });
255
+
256
+ // Global keyboard
257
+ document.addEventListener('keydown', (event) => {
258
+ const inEditableField = hasEditableFocus();
259
+ const key = event.key.toLowerCase();
260
+ const canHandleHistoryShortcut = !inEditableField || document.activeElement?.matches?.('input[type="color"]');
261
+
262
+ if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && canHandleHistoryShortcut) {
263
+ if (key === 'z') {
264
+ event.preventDefault();
265
+ if (event.shiftKey) {
266
+ redoDirectEdit();
267
+ } else {
268
+ undoDirectEdit();
269
+ }
270
+ return;
271
+ }
272
+
273
+ if (key === 'y') {
274
+ event.preventDefault();
275
+ redoDirectEdit();
276
+ return;
277
+ }
278
+ }
279
+
280
+ if (state.toolMode === TOOL_MODE_SELECT && (event.ctrlKey || event.metaKey) && !inEditableField) {
281
+ if (key === 'b') { event.preventDefault(); if (!toggleBold.disabled) toggleBold.click(); return; }
282
+ if (key === 'i') { event.preventDefault(); if (!toggleItalic.disabled) toggleItalic.click(); return; }
283
+ if (key === 'u') { event.preventDefault(); if (!toggleUnderline.disabled) toggleUnderline.click(); return; }
284
+ }
285
+
286
+ if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
287
+ event.preventDefault();
288
+ applyChanges();
289
+ return;
290
+ }
291
+
292
+ if (event.key === 'Escape') {
293
+ if (document.activeElement) document.activeElement.blur();
294
+ return;
295
+ }
296
+
297
+ if (inEditableField) return;
298
+
299
+ if (event.key === 'ArrowLeft') {
300
+ event.preventDefault();
301
+ void goToSlide(state.currentIndex - 1);
302
+ } else if (event.key === 'ArrowRight') {
303
+ event.preventDefault();
304
+ void goToSlide(state.currentIndex + 1);
305
+ }
306
+ });
307
+
308
+ // Resize
309
+ window.addEventListener('resize', scaleSlide);
310
+
311
+ // Iframe load
312
+ slideIframe.addEventListener('load', () => {
313
+ const slide = currentSlideFile();
314
+ if (slide) {
315
+ if (!consumeInternalDocumentRestore(slide)) {
316
+ clearDirectEditHistory(slide);
317
+ }
318
+ const ss = getSlideState(slide);
319
+ if (ss.selectedObjectXPath && !getSelectedObjectElement(slide)) {
320
+ ss.selectedObjectXPath = '';
321
+ }
322
+ }
323
+ state.hoveredObjectXPath = '';
324
+ renderBboxes();
325
+ renderObjectSelection();
326
+ updateObjectEditorControls();
327
+ updateSendState();
328
+ });
329
+
330
+ function applySlideFrameCss(width, height) {
331
+ if (slideWrapper) {
332
+ slideWrapper.style.width = `${width}px`;
333
+ slideWrapper.style.height = `${height}px`;
334
+ }
335
+ if (slideIframe) {
336
+ slideIframe.style.width = `${width}px`;
337
+ slideIframe.style.height = `${height}px`;
338
+ }
339
+ }
340
+
341
+ async function loadEditorConfig() {
342
+ const serverAuthoredType = state.editorType;
343
+ configureEditorClient(serverAuthoredType);
344
+ let cfg = {};
345
+ try {
346
+ const res = await fetch('/api/config');
347
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
348
+ cfg = await res.json();
349
+ if (cfg?.editorType !== serverAuthoredType) {
350
+ throw new Error(`Editor type mismatch: page=${serverAuthoredType}, config=${cfg?.editorType || 'missing'}`);
351
+ }
352
+ } catch {
353
+ // Keep the server-authored editor type and default frame dimensions.
354
+ }
355
+
356
+ const w = cfg?.framePx?.width;
357
+ const h = cfg?.framePx?.height;
358
+ if (w && h) {
359
+ setSlideFrame(w, h);
360
+ applySlideFrameCss(w, h);
361
+ }
362
+ if (cfg?.slideMode) {
363
+ document.body.dataset.slideMode = cfg.slideMode;
364
+ }
365
+ }
366
+
367
+ // Init
368
+ async function init() {
369
+ setStatus('Loading slide list...');
370
+
371
+ await loadEditorConfig();
372
+
373
+ try {
374
+ const res = await fetch('/api/slides');
375
+ if (!res.ok) {
376
+ throw new Error(`Failed to fetch slide list: ${res.status}`);
377
+ }
378
+
379
+ state.slides = await res.json();
380
+
381
+ if (state.slides.length === 0) {
382
+ setStatus('No slides found.');
383
+ slideCounter.textContent = '0 / 0';
384
+ return;
385
+ }
386
+
387
+ const editorClient = getEditorClient();
388
+ if (editorClient.usesModel) await loadModelOptions();
389
+ updateToolModeUI();
390
+ updateEditorClientLabels();
391
+ await goToSlide(0);
392
+ scaleSlide();
393
+ await loadRunsInitial();
394
+ connectSSE();
395
+
396
+ setStatus(editorClient.usesModel
397
+ ? `Ready. Model: ${state.selectedModel}. Draw red pending bboxes, run Codex, then review green bboxes.`
398
+ : `Ready. Image provider: ${state.imageProvider}. Draw red pending bboxes, then regenerate the image.`);
399
+ } catch (error) {
400
+ setStatus(`Error loading slides: ${error.message}`);
401
+ console.error('Init error:', error);
402
+ }
403
+ }
404
+
405
+ init();
@@ -0,0 +1,54 @@
1
+ // editor-navigation.js — Slide navigation (goToSlide, persistDraft)
2
+
3
+ import { state } from './editor-state.js';
4
+ import {
5
+ slideIframe, slideCounter, btnPrev, btnNext, sessionFileChip,
6
+ promptInput, modelSelect,
7
+ } from './editor-dom.js';
8
+ import { currentSlideFile, getSlideState, normalizeModelName, setStatus } from './editor-utils.js';
9
+ import { renderChatMessages } from './editor-chat.js';
10
+ import { renderBboxes, scaleSlide } from './editor-bbox.js';
11
+ import { renderObjectSelection, updateObjectEditorControls } from './editor-select.js';
12
+ import { flushDirectSaveForSlide } from './editor-direct-edit.js';
13
+ import { updateSendState } from './editor-send.js';
14
+
15
+ export function persistCurrentSlideDraft() {
16
+ const slide = currentSlideFile();
17
+ if (!slide) return;
18
+ const ss = getSlideState(slide);
19
+ ss.prompt = promptInput.value;
20
+ ss.model = normalizeModelName(modelSelect.value) || ss.model || state.defaultModel;
21
+ }
22
+
23
+ export async function goToSlide(index) {
24
+ if (index < 0 || index >= state.slides.length) return;
25
+
26
+ const previousSlide = currentSlideFile();
27
+ persistCurrentSlideDraft();
28
+ if (previousSlide) {
29
+ await flushDirectSaveForSlide(previousSlide);
30
+ }
31
+
32
+ state.currentIndex = index;
33
+ const slide = currentSlideFile();
34
+ slideIframe.src = `/slides/${slide}`;
35
+ if (sessionFileChip) sessionFileChip.textContent = slide;
36
+ slideCounter.textContent = `${state.currentIndex + 1} / ${state.slides.length}`;
37
+ btnPrev.disabled = state.currentIndex === 0;
38
+ btnNext.disabled = state.currentIndex === state.slides.length - 1;
39
+
40
+ const ss = getSlideState(slide);
41
+ if (!state.availableModels.includes(normalizeModelName(ss.model))) {
42
+ ss.model = state.defaultModel;
43
+ }
44
+ state.selectedModel = ss.model;
45
+ modelSelect.value = state.selectedModel;
46
+ promptInput.value = ss.prompt || '';
47
+ state.hoveredObjectXPath = '';
48
+ renderChatMessages();
49
+ renderBboxes();
50
+ renderObjectSelection();
51
+ updateObjectEditorControls();
52
+ updateSendState();
53
+ setStatus(`Loaded ${slide}`);
54
+ }