chansung commited on
Commit
e54f4f1
·
verified ·
1 Parent(s): 1eeef20

Create script.js

Browse files
Files changed (1) hide show
  1. script.js +1642 -0
script.js ADDED
@@ -0,0 +1,1642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ console.log("Script execution started.");
2
+ // --- Global Variables ---
3
+ let generatedCode = [];
4
+ let currentCleanedCode = [];
5
+
6
+ let evolutionTimeline = [];
7
+ let activeTimelineIndex = -1;
8
+ let selectedVariationGridIndex = -1;
9
+ let currentFullscreenHistoryIndex = -1;
10
+
11
+ let originalUserPromptForCurrentGeneration = '';
12
+ let lastPreviewUpdateTime = [];
13
+ let previewUpdateInterval = 500;
14
+ let numVariationsToGenerate = 4;
15
+ let activeApiControllers = [];
16
+
17
+ let lastGenerationConfig = {
18
+ prompt: '',
19
+ isRefinement: false,
20
+ numVariations: 4,
21
+ refinedTimelineIndex: -1
22
+ };
23
+
24
+
25
+ // --- DOM Element References ---
26
+ let apiKeyEl, codeOutputEl, errorMessageEl;
27
+ let modelSelEl;
28
+ let selectButtons = [];
29
+ let fullscreenButtons = [];
30
+ let previewItems = [];
31
+
32
+ let refinementLoadingIndicator;
33
+ let mainContentEl, configButtonEl;
34
+ let intervalSliderEl, intervalValueDisplayEl;
35
+
36
+ let fullscreenOverlayEl, fullscreenIframeEl, exitFullscreenBtnEl;
37
+ let perspectiveViewportEl, previewGridWrapperEl;
38
+
39
+ let historyPanelEl, historyPanelPlaceholderEl;
40
+ let selectedCodeTitleH3El;
41
+ let mainContentTitleH1El, mainContentSubtitleH2El;
42
+ let fullscreenHistoryNavEl, historyNavPrevBtnEl, historyNavNextBtnEl;
43
+ let promptModalOverlayEl, promptModalContentEl, modalUserPromptEl, modalGenerateBtnEl, modalCancelBtnEl, modalLoadingIndicatorEl;
44
+ let modalRefinementCheckboxEl, numVariationsSliderEl, numVariationsValueDisplayEl;
45
+ let configModalOverlayEl, configModalContentEl, configModalCloseBtnEl, copyCodeButtonEl;
46
+ let historyToggleButtonEl, historyArrowDownEl, historyArrowUpEl;
47
+ let exportCodeButtonEl;
48
+ let newButtonEl;
49
+ let confirmModalOverlayEl, confirmModalMessageEl, confirmModalConfirmBtnEl, confirmModalCancelBtnEl;
50
+ let currentConfirmCallback = null;
51
+ let historyNavLeftBtnEl, historyNavRightBtnEl;
52
+ let promptDisplayModalOverlayEl, promptDisplayModalContentEl, fullPromptTextEl, promptDisplayModalCloseBtnEl;
53
+ let showPromptModalButtonEl; // Added for the new button
54
+ let modalContextSizeValueEl, modalMaxTokensValueEl, modalMaxTokensSliderEl; // Added for new modal elements
55
+
56
+
57
+ // --- Constants ---
58
+ const API_BASE_URL = 'https://api.novita.ai/v3/openai'; // New Novita API
59
+
60
+ const MODEL_DETAILS = {
61
+ "deepseek/deepseek-v3-turbo": { context: 64000, maxOutput: 16000, defaultOutput: 8192 },
62
+ "deepseek/deepseek-r1-turbo": { context: 64000, maxOutput: 16000, defaultOutput: 8192 },
63
+ "qwen/qwen3-235b-a22b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 },
64
+ "qwen/qwen3-30b-a3b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 },
65
+ "qwen/qwen3-32b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 },
66
+ "google/gemma-3-27b-it": { context: 32000, maxOutput: 32000, defaultOutput: 8192 },
67
+ "mistralai/mistral-nemo": { context: 131072, maxOutput: 131072, defaultOutput: 16384 },
68
+ "meta-llama/llama-4-scout-17b-16e-instruct": { context: 131072, maxOutput: 131072, defaultOutput: 16384 },
69
+ "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { context: 1048576, maxOutput: 1048576, defaultOutput: 16384 }
70
+ };
71
+
72
+
73
+ // --- Helper Function to Process Stream for One Variation ---
74
+ async function processStreamForVariation(apiKey, userPrompt, variationIndex, modelName, signal) {
75
+ console.log(`[Variation ${variationIndex + 1}] Starting generation with model ${modelName}...`);
76
+ const previewFrame = document.getElementById(`preview-frame-${variationIndex + 1}`);
77
+ const loader = document.getElementById(`preview-loader-${variationIndex + 1}`);
78
+ const selectBtn = selectButtons[variationIndex];
79
+ const fullscreenBtn = fullscreenButtons[variationIndex];
80
+
81
+ if (!previewFrame) { console.error(`[Variation ${variationIndex + 1}] Preview frame missing.`); return false; }
82
+ if (!loader) { console.warn(`[Variation ${variationIndex + 1}] Loader missing.`); }
83
+
84
+
85
+ if (loader) loader.classList.remove('hidden');
86
+ if (selectBtn) selectBtn.disabled = true;
87
+ if (fullscreenBtn) fullscreenBtn.disabled = true;
88
+
89
+ if (previewFrame) updateLivePreviewInGrid(variationIndex, '<div class="flex items-center justify-center h-full"><p class="text-slate-500">Generating...</p></div>', false);
90
+ if(lastPreviewUpdateTime[variationIndex] !== undefined) lastPreviewUpdateTime[variationIndex] = 0;
91
+
92
+
93
+ const API_ENDPOINT = `${API_BASE_URL}/chat/completions`; // New Novita API endpoint
94
+ let rawGeneratedCode = '';
95
+ let htmlBlockStarted = false;
96
+ let success = false;
97
+ let isInsideThinkBlock = false; // Flag to track if we are inside a <think> block
98
+
99
+ // Construct conversation history for Novita API
100
+ // The prompt passed to this function (userPrompt) is already formatted with instructions and user request.
101
+ const conversationHistory = [
102
+ { role: "user", content: userPrompt }
103
+ ];
104
+
105
+ try {
106
+ console.log(`[Variation ${variationIndex + 1}] Novita API call with model: ${modelName}`);
107
+ const response = await fetch(API_ENDPOINT, {
108
+ method: 'POST',
109
+ headers: {
110
+ 'Content-Type': 'application/json',
111
+ 'Authorization': `Bearer ${apiKey}` // Novita uses Bearer token
112
+ },
113
+ body: JSON.stringify({
114
+ model: modelName, // Model name for Novita
115
+ messages: conversationHistory,
116
+ stream: true,
117
+ reasoning: { exclude: true }, // Added reasoning parameter
118
+ max_tokens: parseInt(modalMaxTokensSliderEl.value, 10) // Send max_tokens
119
+ }),
120
+ signal: signal
121
+ });
122
+
123
+ if (!response.ok) {
124
+ let errorDetails = `API Error: ${response.status} ${response.statusText}`;
125
+ try {
126
+ const errorDataText = await response.text(); // Try to get text first for more detailed errors
127
+ console.error(`[Variation ${variationIndex + 1}] Novita API Error Response Text:`, errorDataText);
128
+ const errorData = JSON.parse(errorDataText); // Then try to parse as JSON
129
+ errorDetails += ` - ${errorData?.error?.message || errorData?.detail || errorDataText || 'No specific message.'}`;
130
+ } catch (e) {
131
+ errorDetails += ` - Could not parse error response.`;
132
+ }
133
+ throw new Error(errorDetails);
134
+ }
135
+
136
+ const reader = response.body.getReader();
137
+ const decoder = new TextDecoder();
138
+ let sseBuffer = ""; // Buffer for incomplete SSE messages
139
+ let streamEnded = false;
140
+
141
+ while (!streamEnded) {
142
+ const { done, value } = await reader.read();
143
+ if (done) {
144
+ streamEnded = true;
145
+ break;
146
+ }
147
+
148
+ sseBuffer += decoder.decode(value, { stream: true });
149
+ let eolIndex;
150
+ while ((eolIndex = sseBuffer.indexOf('\n')) >= 0) {
151
+ const line = sseBuffer.substring(0, eolIndex).trim();
152
+ sseBuffer = sseBuffer.substring(eolIndex + 1);
153
+
154
+ if (line.startsWith('data: ')) {
155
+ const data = line.substring(6).trim();
156
+ if (data === '[DONE]') {
157
+ streamEnded = true;
158
+ break;
159
+ }
160
+ try {
161
+ const json = JSON.parse(data);
162
+ const deltaContent = json.choices?.[0]?.delta?.content;
163
+
164
+ if (deltaContent) {
165
+ let currentChunk = deltaContent;
166
+ let processedChunkForThisDelta = '';
167
+
168
+ while (currentChunk.length > 0) {
169
+ if (isInsideThinkBlock) {
170
+ const endThinkTagIndex = currentChunk.indexOf('</think>');
171
+ if (endThinkTagIndex !== -1) {
172
+ // Found the end tag. Content after it is actual.
173
+ processedChunkForThisDelta += currentChunk.substring(endThinkTagIndex + '</think>'.length);
174
+ isInsideThinkBlock = false;
175
+ currentChunk = ''; // Current delta's relevant part processed
176
+ } else {
177
+ // Still inside think block, discard this part of the chunk
178
+ currentChunk = ''; // Discard and move to next delta if any
179
+ }
180
+ } else {
181
+ const startThinkTagIndex = currentChunk.indexOf('<think>');
182
+ if (startThinkTagIndex !== -1) {
183
+ // Found a start tag. Content before it is actual.
184
+ processedChunkForThisDelta += currentChunk.substring(0, startThinkTagIndex);
185
+ isInsideThinkBlock = true;
186
+ // The rest of currentChunk starts after <think>
187
+ currentChunk = currentChunk.substring(startThinkTagIndex + '<think>'.length);
188
+ // This remaining part of currentChunk will be re-evaluated in the next iteration
189
+ // of this inner while loop, now with isInsideThinkBlock = true.
190
+ } else {
191
+ // No think tags, entire chunk is actual content
192
+ processedChunkForThisDelta += currentChunk;
193
+ currentChunk = '';
194
+ }
195
+ }
196
+ }
197
+
198
+ if (processedChunkForThisDelta.length > 0) {
199
+ rawGeneratedCode += processedChunkForThisDelta;
200
+ // The htmlBlockStarted logic might need re-evaluation.
201
+ // For now, assume content is part of the HTML stream.
202
+ if (!htmlBlockStarted) {
203
+ // Try to detect '```html' or start accumulating if it appears
204
+ const marker = "```html";
205
+ let tempAccumulated = rawGeneratedCode; // Use the cleaned rawGeneratedCode
206
+ const markerIndex = tempAccumulated.indexOf(marker);
207
+ if (markerIndex !== -1) {
208
+ htmlBlockStarted = true;
209
+ let codeStartIndex = markerIndex + marker.length;
210
+ if (tempAccumulated[codeStartIndex] === '\n') {
211
+ codeStartIndex++;
212
+ }
213
+ rawGeneratedCode = tempAccumulated.substring(codeStartIndex);
214
+ } else if (tempAccumulated.trim().startsWith("<!DOCTYPE html") || tempAccumulated.trim().startsWith("<html")) {
215
+ // If it looks like HTML already, assume block started
216
+ htmlBlockStarted = true;
217
+ }
218
+ }
219
+ // If htmlBlockStarted is true, or we assume it's direct HTML content
220
+ if (htmlBlockStarted || !(rawGeneratedCode.includes("```html"))) {
221
+ const now = Date.now();
222
+ if (lastPreviewUpdateTime[variationIndex] !== undefined && (now - lastPreviewUpdateTime[variationIndex] >= previewUpdateInterval)) {
223
+ // Strip markdown for preview if still present at this stage
224
+ let previewCode = rawGeneratedCode; // Use the cleaned rawGeneratedCode
225
+ if (previewCode.includes("```html")) {
226
+ previewCode = previewCode.substring(previewCode.indexOf("```html") + 7).trimStart();
227
+ }
228
+ if (previewCode.endsWith("```")) {
229
+ previewCode = previewCode.substring(0, previewCode.length - 3).trimEnd();
230
+ }
231
+ updateLivePreviewInGrid(variationIndex, previewCode, true);
232
+ lastPreviewUpdateTime[variationIndex] = now;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ } catch (e) {
238
+ console.warn(`[Var ${variationIndex + 1}] JSON parse error in stream:`, e, "Problematic Line:", line, "Data:", data);
239
+ }
240
+ }
241
+ }
242
+ if (streamEnded) break; // Exit outer loop if [DONE] was processed
243
+ }
244
+
245
+ console.log(`[Variation ${variationIndex + 1}] Streaming finished. Raw content length: ${rawGeneratedCode.length}`);
246
+ let finalExtractedHtml = null;
247
+ let processingErrorMessage = null;
248
+
249
+ // Process the rawGeneratedCode after stream ends
250
+ // This needs to handle potential markdown ```html ... ``` blocks
251
+ let tempHtml = rawGeneratedCode.trim();
252
+ const htmlMarker = "```html";
253
+ const markerIndex = tempHtml.indexOf(htmlMarker);
254
+
255
+ if (markerIndex !== -1) {
256
+ htmlBlockStarted = true; // Confirm block started if marker found
257
+ tempHtml = tempHtml.substring(markerIndex + htmlMarker.length).trimStart();
258
+ }
259
+ // Remove trailing ``` if present
260
+ if (tempHtml.endsWith("```")) {
261
+ tempHtml = tempHtml.substring(0, tempHtml.length - 3).trimEnd();
262
+ }
263
+
264
+ // If no ```html was found, but it looks like html, use it directly
265
+ if (!htmlBlockStarted && (tempHtml.startsWith("<!DOCTYPE") || tempHtml.startsWith("<html"))) {
266
+ htmlBlockStarted = true; // Assume it's HTML
267
+ }
268
+
269
+ if (htmlBlockStarted && tempHtml.length > 0) {
270
+ finalExtractedHtml = tempHtml;
271
+ const minLength = 20;
272
+ const hasStructuralTag = /<!DOCTYPE html|<html[^>]*>|<head[^>]*>|<body[^>]*>/i.test(finalExtractedHtml);
273
+ const hasAnyTag = /<[a-zA-Z][^>]*>/.test(finalExtractedHtml);
274
+
275
+ if (finalExtractedHtml.length >= minLength && (hasStructuralTag || hasAnyTag)) {
276
+ success = true;
277
+ console.log(`[Variation ${variationIndex + 1}] HTML validation SUCCEEDED.`);
278
+ } else {
279
+ success = false;
280
+ console.warn("[Variation " + (variationIndex + 1) + "] HTML validation FAILED (length: " + finalExtractedHtml.length + ", structural: " + hasStructuralTag + ", anyTag: " + hasAnyTag + "). Review content manually.");
281
+ processingErrorMessage = "// Warning: Generated content did not pass basic HTML validation.";
282
+ if (!finalExtractedHtml) finalExtractedHtml = processingErrorMessage; // Ensure some content for display
283
+ }
284
+ } else if (tempHtml.length > 0 && !htmlBlockStarted) { // Content received but not identified as HTML block
285
+ processingErrorMessage = `// Warning: Content received, but 'Written by Novita AI' or similar marker not found, or content doesn't look like HTML. Length: ${tempHtml.length}`;
286
+ console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
287
+ finalExtractedHtml = tempHtml; // Display what was received
288
+ success = false; // Treat as not fully successful if not identified as clean HTML
289
+ } else {
290
+ processingErrorMessage = `// Error: No meaningful content generated or 'Written by Novita AI' marker not found.`;
291
+ console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`);
292
+ finalExtractedHtml = processingErrorMessage; // Show error message
293
+ success = false;
294
+ }
295
+
296
+ if (success && finalExtractedHtml) {
297
+ let processedHtml = finalExtractedHtml;
298
+ const requiredHeadContent = `
299
+ <meta charset="UTF-8">
300
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
301
+ <script src="https://cdn.tailwindcss.com"><\\/script>
302
+ <link rel="preconnect" href="https://rsms.me/">
303
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
304
+ <style>
305
+ html { font-family: 'Inter', sans-serif; }
306
+ @supports (font-variation-settings: normal) {
307
+ html { font-family: 'Inter var', sans-serif; }
308
+ }
309
+ body {
310
+ background-color: #f8fafc; /* slate-50 */
311
+ color: #0f172a; /* slate-900 */
312
+ padding: 1rem;
313
+ }
314
+ </style>`;
315
+
316
+ if (!processedHtml.includes('<head>')) {
317
+ processedHtml = `<head>${requiredHeadContent}</head><body>${processedHtml}</body>`;
318
+ } else {
319
+ let tempRequired = "";
320
+ if (!processedHtml.includes('cdn.tailwindcss.com')) tempRequired += ` <script src="https://cdn.tailwindcss.com"><\\/script>\\n`;
321
+ if (!processedHtml.includes('inter.css')) tempRequired += ` <link rel="preconnect" href="https://rsms.me/">\\n <link rel="stylesheet" href="https://rsms.me/inter/inter.css">\\n`;
322
+ if (!processedHtml.includes("html { font-family: 'Inter', sans-serif; }")) {
323
+ tempRequired += ` <style>\\n html { font-family: 'Inter', sans-serif; }\\n @supports (font-variation-settings: normal) { html { font-family: 'Inter var', sans-serif; } }\\n body { background-color: #f8fafc; color: #0f172a; padding: 1rem; }\\n </style>\\n`;
324
+ }
325
+ if (tempRequired) {
326
+ processedHtml = processedHtml.replace(/<\/head>/i, `${tempRequired}</head>`);
327
+ }
328
+ }
329
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = processedHtml;
330
+
331
+ const interactionScript = `<script>(function() { const VARIATION_INDEX = ${variationIndex}; })(); <\\/script>`;
332
+ const bodyEndIndex = processedHtml.lastIndexOf('</body>');
333
+ if(generatedCode[variationIndex] !== undefined) {
334
+ generatedCode[variationIndex] = (bodyEndIndex !== -1)
335
+ ? processedHtml.slice(0, bodyEndIndex) + interactionScript + processedHtml.slice(bodyEndIndex)
336
+ : processedHtml + interactionScript;
337
+ }
338
+ updateLivePreviewInGrid(variationIndex, null, true);
339
+ } else {
340
+ generatedCode[variationIndex] = '';
341
+ if (finalExtractedHtml) {
342
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = finalExtractedHtml; // Store the potentially partial/error code
343
+ updateLivePreviewInGrid(variationIndex, finalExtractedHtml, false);
344
+ } else {
345
+ const displayError = processingErrorMessage || "// Unknown error during generation.";
346
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = displayError;
347
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">${displayError.replace(/\\n/g, '<br>')}</div>`, false);
348
+ }
349
+ }
350
+
351
+ } catch (error) {
352
+ if (error.name === 'AbortError') {
353
+ console.log(`[Variation ${variationIndex + 1}] Fetch aborted.`);
354
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-yellow-400 font-medium">Generation Cancelled.</div>`, false);
355
+ } else {
356
+ console.error(`[Variation ${variationIndex + 1}] Generation error:`, error);
357
+ updateLivePreviewInGrid(variationIndex, `<div class="p-4 text-red-400 font-medium">Error: ${error.message}</div>`, false);
358
+ }
359
+ if(currentCleanedCode[variationIndex] !== undefined) currentCleanedCode[variationIndex] = `// Error: ${error.message}`;
360
+ if(generatedCode[variationIndex] !== undefined) generatedCode[variationIndex] = '';
361
+ success = false; // Explicitly set success to false on error
362
+ } finally {
363
+ const finalLoader = document.getElementById(`preview-loader-${variationIndex + 1}`);
364
+ if (finalLoader) finalLoader.classList.add('hidden');
365
+ if (selectBtn) selectBtn.disabled = !success;
366
+ if (fullscreenBtn) fullscreenBtn.disabled = !success;
367
+ }
368
+ return success;
369
+ }
370
+
371
+ // --- Main Function to Generate Variations ---
372
+ async function generateVariations() {
373
+ console.log("generateVariations called.");
374
+ const userPromptText = modalUserPromptEl.value.trim(); // Renamed from userPrompt to avoid conflict
375
+
376
+ if (!apiKeyEl || !modalUserPromptEl || !modelSelEl || !previewGridWrapperEl || !modalGenerateBtnEl || !modalLoadingIndicatorEl || !errorMessageEl || !codeOutputEl) {
377
+ console.error("Cannot generate variations: One or more critical elements are missing.");
378
+ if (errorMessageEl) errorMessageEl.textContent = "Initialization error. Cannot generate.";
379
+ return;
380
+ }
381
+
382
+ const apiKey = apiKeyEl.value.trim();
383
+ const selectedModel = modelSelEl.value;
384
+ const currentIsRefinementMode = modalRefinementCheckboxEl.checked;
385
+ const currentNumVariations = parseInt(numVariationsSliderEl.value, 10);
386
+
387
+ if (!apiKey || !userPromptText) {
388
+ errorMessageEl.textContent = 'Error: API Key and Prompt (via Alt+P) are required.';
389
+ if (!userPromptText && !promptModalOverlayEl.classList.contains('visible')) showPromptModal();
390
+ return;
391
+ }
392
+ if (!selectedModel) {
393
+ errorMessageEl.textContent = 'Error: Please select a model.';
394
+ return;
395
+ }
396
+
397
+ lastGenerationConfig = {
398
+ prompt: userPromptText,
399
+ isRefinement: currentIsRefinementMode,
400
+ numVariations: currentNumVariations,
401
+ refinedTimelineIndex: currentIsRefinementMode ? activeTimelineIndex : -1
402
+ };
403
+
404
+
405
+ errorMessageEl.textContent = '';
406
+ console.log(`Mode: ${currentIsRefinementMode ? 'Refinement' : 'Initial'}, Model: ${selectedModel}, Variations: ${currentNumVariations}`);
407
+
408
+ let baseCodeForRefinement = null;
409
+ let contextPromptForRefinement = '';
410
+ originalUserPromptForCurrentGeneration = userPromptText; // Use the new variable name
411
+
412
+ if (currentIsRefinementMode && activeTimelineIndex !== -1 && evolutionTimeline[activeTimelineIndex]) {
413
+ baseCodeForRefinement = evolutionTimeline[activeTimelineIndex].code;
414
+ contextPromptForRefinement = evolutionTimeline[activeTimelineIndex].originalUserPrompt;
415
+ console.log(`Refining Evolution ${activeTimelineIndex + 1}. Original context: "${contextPromptForRefinement}"`);
416
+ } else if (currentIsRefinementMode) {
417
+ errorMessageEl.textContent = 'Error: No active evolution selected to refine. Uncheck "refine" or select an evolution from history.';
418
+ return;
419
+ }
420
+
421
+ modalGenerateBtnEl.disabled = true;
422
+ modalLoadingIndicatorEl.classList.remove('hidden');
423
+ if (codeOutputEl && selectedCodeTitleH3El) {
424
+ codeOutputEl.innerHTML = '<code class="language-html">// Select a variation to view its code.</code>';
425
+ selectedCodeTitleH3El.textContent = "Selected Code:";
426
+ }
427
+ selectedVariationGridIndex = -1;
428
+
429
+ numVariationsToGenerate = currentNumVariations;
430
+ generatedCode = Array(numVariationsToGenerate).fill('');
431
+ currentCleanedCode = Array(numVariationsToGenerate).fill('');
432
+ lastPreviewUpdateTime = Array(numVariationsToGenerate).fill(0);
433
+ selectButtons = Array(numVariationsToGenerate).fill(null);
434
+ fullscreenButtons = Array(numVariationsToGenerate).fill(null);
435
+ previewItems = Array(numVariationsToGenerate).fill(null);
436
+
437
+
438
+ showFourGridPreviewUI();
439
+
440
+ activeApiControllers = Array(numVariationsToGenerate).fill(null).map(() => new AbortController());
441
+
442
+ // Construct the prompts for Novita - they will be passed as the 'user' message content
443
+ const promptsForNovita = Array(numVariationsToGenerate).fill(null).map((_, i) => {
444
+ if (currentIsRefinementMode && baseCodeForRefinement) {
445
+ // For Novita, the prompt should be structured clearly for the model to understand the task.
446
+ // The API call itself will use a messages array. This is the content for the 'user' role.
447
+ return `
448
+ CONTEXT: You are an expert web developer specializing in HTML, Tailwind CSS, and JavaScript.
449
+ Original User Request (for context of the base code): "${contextPromptForRefinement}"
450
+ Base HTML Code to Refine:
451
+ \`\`\`html
452
+ ${baseCodeForRefinement}
453
+ \`\`\`
454
+ Your Task: Implement the following refinement instructions for Variation ${i + 1} of ${numVariationsToGenerate}.
455
+ User's Refinement Instructions: "${userPromptText}"
456
+
457
+ Instructions for your output:
458
+ 1. Analyze the Base Code and the User's Refinement Instructions.
459
+ 2. Modify ONLY the necessary parts of the Base Code to implement the refinement.
460
+ 3. Try a slightly different approach if possible, as this is one of several variations.
461
+ 4. Ensure the refined code remains a single, complete, runnable HTML document.
462
+ 5. ALWAYS include Tailwind CSS CDN (<script src="https://cdn.tailwindcss.com"><\\/script>) and Inter font (<link rel="stylesheet" href="https://rsms.me/inter/inter.css">) in the <head>.
463
+ 6. Add basic body styling for readability: <style>html { font-family: 'Inter', sans-serif; } body { background-color: #f8fafc; color: #0f172a; padding: 1rem; }</style>
464
+ 7. Output ONLY the raw, complete, refined HTML code. Start your response with \`\`\`html and end with \`\`\`
465
+ Refined Code:`;
466
+ } else {
467
+ return `
468
+ CONTEXT: You are an expert web developer specializing in clean, modern HTML, CSS (using Tailwind CSS classes), and JavaScript.
469
+ Your Task: Generate the complete, runnable HTML code for Variation ${i + 1} of ${numVariationsToGenerate} based on the User Request below.
470
+
471
+ Instructions for your output:
472
+ 1. Ensure the code is self-contained.
473
+ 2. ALWAYS include Tailwind CSS CDN (<script src="https://cdn.tailwindcss.com"><\\/script>) in the <head>.
474
+ 3. ALWAYS include Inter font (<link rel="stylesheet" href="https://rsms.me/inter/inter.css">) and set html { font-family: 'Inter', sans-serif; } in a <style> tag in the <head>.
475
+ 4. Add basic body styling for readability: body { background-color: #f8fafc; color: #0f172a; padding: 1rem; } in the same <style> tag.
476
+ 5. Add HTML comments to explain the code if helpful.
477
+ 6. Try to make this variation distinct.
478
+ 7. Output ONLY the raw HTML code. Start your response with \`\`\`html and end with \`\`\`
479
+
480
+ User Request:
481
+ "${originalUserPromptForCurrentGeneration}"
482
+ Code:`;
483
+ }
484
+ });
485
+
486
+ const generationPromises = promptsForNovita.map((promptContent, index) =>
487
+ processStreamForVariation(apiKey, promptContent, index, selectedModel, activeApiControllers[index].signal)
488
+ );
489
+
490
+ try {
491
+ const results = await Promise.allSettled(generationPromises);
492
+ console.log("Generation promises settled:", results);
493
+ const successfulGenerations = results.filter(r => r.status === 'fulfilled' && r.value === true).length;
494
+ if (successfulGenerations === 0 && !results.some(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
495
+ errorMessageElement.textContent = 'Error: All variations failed.';
496
+ }
497
+ else if (successfulGenerations < numVariationsToGenerate && !results.every(r => r.status === 'rejected' && r.reason.name === 'AbortError')) {
498
+ errorMessageElement.textContent = `Warning: ${numVariationsToGenerate - successfulGenerations} var(s) failed or were cancelled.`;
499
+ }
500
+
501
+ updateMainContentTitles("Select a Variation", "Click 'Select' on a preview below.");
502
+
503
+ } catch (error) {
504
+ console.error("Parallel generation error:", error);
505
+ errorMessageElement.textContent = `Unexpected Error: ${error.message}`;
506
+ showInitialPreviewStateUI();
507
+ } finally {
508
+ modalGenerateBtnEl.disabled = false;
509
+ modalLoadingIndicatorEl.classList.add('hidden');
510
+ console.log("Generation process finished.");
511
+ }
512
+ }
513
+
514
+ // --- UI Update Functions ---
515
+ function createPreviewItemDOM(index, isGridItem = true) {
516
+ const item = document.createElement('div');
517
+ item.id = `preview-item-${index + 1}`;
518
+ item.className = isGridItem ? 'preview-item-perspective' : 'single-preview-item';
519
+ if (isGridItem) item.dataset.variationGridIndex = index;
520
+
521
+ const header = document.createElement('div'); header.className = 'preview-header';
522
+ const title = document.createElement('span'); title.className = 'preview-header-title';
523
+ title.textContent = isGridItem ? `Variation ${index + 1}` : (evolutionTimeline[activeTimelineIndex]?.prompt.substring(0,30) + '...' || `Evolution ${activeTimelineIndex + 1}`);
524
+ header.appendChild(title);
525
+
526
+ const btns = document.createElement('div'); btns.className = 'preview-header-buttons';
527
+
528
+ const fsBtn = document.createElement('button');
529
+ fsBtn.className = 'fullscreen-btn p-1 focus:outline-none focus:ring-1 focus:ring-cyan-500 rounded disabled:opacity-50';
530
+ fsBtn.dataset.idx = index;
531
+ fsBtn.title = 'Full Screen'; fsBtn.disabled = true;
532
+ fsBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>`;
533
+ fsBtn.addEventListener('click', (e) => {
534
+ const idxToFullscreen = isGridItem ? parseInt(e.currentTarget.dataset.idx) : activeTimelineIndex;
535
+ const fromHistory = !isGridItem;
536
+ if (!isNaN(idxToFullscreen) && (isGridItem ? generatedCode[idxToFullscreen] : evolutionTimeline[idxToFullscreen]?.code)) {
537
+ enterFullscreen(idxToFullscreen, fromHistory);
538
+ }
539
+ });
540
+
541
+ btns.appendChild(fsBtn);
542
+
543
+ if (isGridItem) {
544
+ const selBtn = document.createElement('button');
545
+ selBtn.className = 'select-variation-btn futuristic-button px-3 py-1 text-xs';
546
+ selBtn.dataset.variationGridIndex = index; selBtn.disabled = true; selBtn.textContent = 'Select';
547
+ selBtn.addEventListener('click', handleSelectVariationFromGrid);
548
+ selectButtons[index] = selBtn;
549
+ btns.appendChild(selBtn);
550
+ }
551
+
552
+ header.appendChild(btns);
553
+ item.appendChild(header);
554
+
555
+ const bodyEl = document.createElement('div'); bodyEl.className = 'preview-body';
556
+ const loaderDiv = document.createElement('div'); loaderDiv.id = `preview-loader-${index + 1}`;
557
+ loaderDiv.className = 'preview-loader ' + (isGridItem ? 'hidden' : '');
558
+ loaderDiv.innerHTML = '<div class="spinner"></div>';
559
+ bodyEl.appendChild(loaderDiv);
560
+
561
+ const iframeEl = document.createElement('iframe');
562
+ iframeEl.id = isGridItem ? `preview-frame-${index + 1}` : 'single-large-preview-frame';
563
+ iframeEl.title = `Preview ${index + 1}`; iframeEl.className = 'preview-frame';
564
+ iframeEl.srcdoc = '<div class="flex items-center justify-center h-full"><p class="text-slate-400">Preparing...</p></div>';
565
+ bodyEl.appendChild(iframeEl);
566
+ item.appendChild(bodyEl);
567
+
568
+ if (isGridItem) {
569
+ previewItems[index] = item;
570
+ fullscreenButtons[index] = fsBtn;
571
+ }
572
+ return item;
573
+ }
574
+
575
+
576
+ function showFourGridPreviewUI() {
577
+ previewGridWrapperEl.innerHTML = '';
578
+ if (numVariationsToGenerate === 1) {
579
+ previewGridWrapperEl.className = 'single-mode';
580
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none';
581
+ const item = createPreviewItemDOM(0, true);
582
+ previewGridWrapperEl.appendChild(item);
583
+
584
+ } else if (numVariationsToGenerate === 2) {
585
+ previewGridWrapperEl.className = 'grid grid-cols-2 grid-rows-1 gap-6';
586
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
587
+ for (let i = 0; i < numVariationsToGenerate; i++) {
588
+ const item = createPreviewItemDOM(i, true);
589
+ previewGridWrapperEl.appendChild(item);
590
+ }
591
+ } else {
592
+ previewGridWrapperEl.className = 'grid-mode';
593
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
594
+ for (let i = 0; i < numVariationsToGenerate; i++) {
595
+ const item = createPreviewItemDOM(i, true);
596
+ previewGridWrapperEl.appendChild(item);
597
+ }
598
+ }
599
+ updateSelectedGridItemUI();
600
+ }
601
+
602
+ function showSingleLargePreviewUI(htmlContent, titleText, fullPromptText) {
603
+ previewGridWrapperEl.innerHTML = '';
604
+ previewGridWrapperEl.className = 'single-mode';
605
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = 'none';
606
+
607
+ const item = document.createElement('div');
608
+ item.className = 'single-preview-item';
609
+
610
+ const bodyEl = document.createElement('div');
611
+ bodyEl.className = 'preview-body';
612
+
613
+ const iframeEl = document.createElement('iframe');
614
+ iframeEl.id = `single-large-preview-frame`;
615
+ iframeEl.title = titleText;
616
+ iframeEl.className = 'preview-frame';
617
+ iframeEl.srcdoc = htmlContent;
618
+
619
+ bodyEl.appendChild(iframeEl);
620
+ item.appendChild(bodyEl);
621
+ previewGridWrapperEl.appendChild(item);
622
+
623
+ // Handle subtitle truncation and clickability
624
+ const maxPromptLength = 50;
625
+ let displaySubtitle = fullPromptText;
626
+ mainContentSubtitleH2El.classList.remove('prompt-truncated');
627
+ delete mainContentSubtitleH2El.dataset.fullPrompt;
628
+
629
+ if (fullPromptText && fullPromptText.length > maxPromptLength) {
630
+ displaySubtitle = fullPromptText.substring(0, maxPromptLength) + "... (click to view full)";
631
+ mainContentSubtitleH2El.classList.add('prompt-truncated');
632
+ mainContentSubtitleH2El.dataset.fullPrompt = fullPromptText; // Store full prompt
633
+ }
634
+
635
+ updateMainContentTitles(titleText, displaySubtitle); // Use potentially truncated text
636
+ }
637
+
638
+ function showInitialPreviewStateUI() {
639
+ previewGridWrapperEl.innerHTML = '<div class="col-span-2 row-span-2 flex items-center justify-center text-slate-500 text-lg"><p>Press Alt+O, then Alt+P to begin.</p></div>';
640
+ previewGridWrapperEl.className = 'grid-mode';
641
+ if(perspectiveViewportEl) perspectiveViewportEl.style.perspective = '1500px';
642
+ updateMainContentTitles("Live Previews", "Powered by Open Models from Novita.AI Model API");
643
+ if (codeOutputEl) codeOutputEl.innerHTML = '<code class="language-html">// Select a variation or history item to view its code.</code>';
644
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = "Selected Code:";
645
+ selectedVariationGridIndex = -1;
646
+ // Reset subtitle state when going back to initial view
647
+ if (mainContentSubtitleH2El) {
648
+ mainContentSubtitleH2El.classList.remove('prompt-truncated');
649
+ delete mainContentSubtitleH2El.dataset.fullPrompt;
650
+ }
651
+ }
652
+
653
+ function updateMainContentTitles(title, subtitle) {
654
+ if (mainContentTitleH1El) mainContentTitleH1El.textContent = title;
655
+ if (mainContentSubtitleH2El) mainContentSubtitleH2El.textContent = subtitle;
656
+ }
657
+
658
+ function updateSelectedGridItemUI() {
659
+ previewItems.forEach((item, index) => {
660
+ if (!item) return;
661
+ item.classList.toggle('selected', index === selectedVariationGridIndex);
662
+ });
663
+ selectButtons.forEach((button, index) => {
664
+ if (!button) return;
665
+ // Button enabled/disabled state is handled by procStream's success.
666
+ // const hasCode = currentCleanedCode[index]?.length > 0 && !currentCleanedCode[index].startsWith("// Error");
667
+ // button.disabled = !hasCode;
668
+ // if (fullscreenButtons[index]) { fullscreenButtons[index].disabled = !hasCode; }
669
+
670
+ button.classList.remove('selected-state');
671
+ if (index === selectedVariationGridIndex) {
672
+ button.textContent = 'Selected';
673
+ button.classList.add('selected-state');
674
+ } else {
675
+ button.textContent = 'Select';
676
+ }
677
+ });
678
+ }
679
+
680
+ function updateLivePreviewInGrid(index, codeToRender = null, applyZoom = false) {
681
+ let baseHtml = codeToRender !== null ? codeToRender : generatedCode[index];
682
+ const frame = document.getElementById(`preview-frame-${index + 1}`);
683
+ if (!frame) { console.warn(`[updateLivePreviewInGrid][Var ${index + 1}] Frame not found.`); return; }
684
+
685
+ if (typeof baseHtml !== 'string') {
686
+ baseHtml = '<div class="p-4 text-orange-500">Invalid content received</div>';
687
+ applyZoom = false;
688
+ }
689
+ let finalHtml = baseHtml;
690
+ try {
691
+ if (applyZoom && numVariationsToGenerate > 1) {
692
+ const scaleStyle = `<style>html { transform: scale(0.5); transform-origin: 0 0; width: 200%; height: 200%; overflow: auto !important; } body { overflow: visible !important; min-height: 100% !important; height: auto !important; width: auto !important; }</style>`;
693
+ const headEndIndex = finalHtml.toLowerCase().lastIndexOf('</head>');
694
+ if (headEndIndex !== -1) {
695
+ finalHtml = finalHtml.slice(0, headEndIndex) + scaleStyle + finalHtml.slice(headEndIndex);
696
+ } else {
697
+ finalHtml = scaleStyle + finalHtml;
698
+ }
699
+ }
700
+ frame.srcdoc = finalHtml;
701
+ } catch (e) {
702
+ console.error(`[Var ${index + 1}] Error setting srcdoc for grid:`, e);
703
+ try {
704
+ frame.srcdoc = `<div class="p-4 text-red-500 font-semibold">Preview Render Error</div>`;
705
+ } catch (finalError) { console.error("Failed to display error in grid iframe:", finalError); }
706
+ }
707
+ }
708
+
709
+ // --- Event Handlers ---
710
+ function handleSelectVariationFromGrid(event) {
711
+ const idx = parseInt(event.target.dataset.variationGridIndex, 10);
712
+ // Check if code for this variation is valid (not an error message)
713
+ if (isNaN(idx) || !currentCleanedCode[idx] || currentCleanedCode[idx].startsWith("// Error")) {
714
+ console.warn(`Cannot select variation ${idx + 1}, code is invalid or generation failed.`);
715
+ return;
716
+ }
717
+
718
+ activeApiControllers.forEach((controller, controllerIndex) => {
719
+ if (controllerIndex !== idx && controller) {
720
+ console.log(`Aborting request for variation ${controllerIndex + 1}`);
721
+ controller.abort();
722
+ }
723
+ });
724
+ activeApiControllers = [];
725
+
726
+
727
+ selectedVariationGridIndex = idx;
728
+ const displayCode = generatedCode[idx];
729
+ const storeCode = currentCleanedCode[idx];
730
+
731
+ let originalPromptForThisEvolution;
732
+ let parentTimelineIdx = null;
733
+
734
+ const wasThisGenerationARefinement = lastGenerationConfig.isRefinement;
735
+ const refinedIndexForThisGen = lastGenerationConfig.refinedTimelineIndex;
736
+
737
+ if (wasThisGenerationARefinement && refinedIndexForThisGen !== -1 && evolutionTimeline[refinedIndexForThisGen]) {
738
+ originalPromptForThisEvolution = evolutionTimeline[refinedIndexForThisGen].originalUserPrompt;
739
+ parentTimelineIdx = refinedIndexForThisGen;
740
+ } else {
741
+ originalPromptForThisEvolution = originalUserPromptForCurrentGeneration;
742
+ }
743
+
744
+ const newHistoryEntry = {
745
+ id: 'evo-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
746
+ prompt: originalUserPromptForCurrentGeneration,
747
+ originalUserPrompt: originalPromptForThisEvolution,
748
+ code: storeCode,
749
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit'}),
750
+ parentIndex: parentTimelineIdx
751
+ };
752
+ evolutionTimeline.push(newHistoryEntry);
753
+ activeTimelineIndex = evolutionTimeline.length - 1;
754
+
755
+ console.log(`Variation ${idx + 1} selected from grid. Added to timeline as Evolution ${activeTimelineIndex + 1}.`);
756
+
757
+ renderHistoryPanel();
758
+ showSingleLargePreviewUI(displayCode, `Evolution ${activeTimelineIndex + 1}: Active`, `Prompt: "${newHistoryEntry.prompt}"`);
759
+
760
+ if (codeOutputEl) {
761
+ codeOutputEl.textContent = storeCode;
762
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1}):`;
763
+ codeOutputEl.classList.remove('text-slate-200');
764
+ codeOutputEl.classList.add('text-slate-400');
765
+ if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
766
+ }
767
+
768
+
769
+ updateSelectedGridItemUI();
770
+ updateHistoryNavigationButtons(); // Update nav buttons
771
+
772
+ if(modalUserPromptEl) modalUserPromptEl.value = '';
773
+ if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
774
+ if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
775
+ }
776
+
777
+ function handleHistoryItemClick(timelineIdxToView) {
778
+ if (timelineIdxToView < 0 || timelineIdxToView >= evolutionTimeline.length) {
779
+ console.warn("Invalid history index clicked:", timelineIdxToView);
780
+ return;
781
+ }
782
+ activeTimelineIndex = timelineIdxToView;
783
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
784
+
785
+ console.log(`History item ${activeTimelineIndex + 1} selected.`);
786
+
787
+ const displayCodeForHistory = historyEntry.code;
788
+
789
+ showSingleLargePreviewUI(displayCodeForHistory, `Evolution ${activeTimelineIndex + 1}: Active (Historical)`, `Prompt: "${historyEntry.prompt}"`);
790
+
791
+ if (codeOutputEl) {
792
+ codeOutputEl.textContent = historyEntry.code;
793
+ if (selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
794
+ codeOutputEl.classList.remove('text-slate-200');
795
+ codeOutputEl.classList.add('text-slate-400');
796
+ if (codeOutputEl?.parentElement) { codeOutputEl.parentElement.scrollTop = 0; }
797
+ }
798
+
799
+ renderHistoryPanel();
800
+ updateHistoryNavigationButtons(); // Update nav buttons
801
+
802
+ if(modalUserPromptEl) modalUserPromptEl.value = '';
803
+ if(modalUserPromptEl) modalUserPromptEl.placeholder = `Refine Evolution ${activeTimelineIndex + 1}...`;
804
+ if(modalRefinementCheckboxEl) modalRefinementCheckboxEl.checked = true;
805
+ selectedVariationGridIndex = -1;
806
+ updateSelectedGridItemUI();
807
+ }
808
+
809
+
810
+ // --- Render History Panel ---
811
+ function createHistoryThumbnailDOM(entry, index) {
812
+ const thumbItem = document.createElement('div');
813
+ thumbItem.className = 'history-thumbnail-item group';
814
+ thumbItem.dataset.timelineIndex = index;
815
+ thumbItem.setAttribute('aria-label', `Evolution Step ${index + 1}: ${entry.prompt.substring(0, 30)}...`);
816
+
817
+ // Note: Active class and dynamic transforms/z-index are applied in renderHistoryPanel
818
+
819
+ const previewContainer = document.createElement('div');
820
+ previewContainer.className = 'history-thumbnail-preview-container';
821
+ previewContainer.title = `Click to view Evolution ${index + 1}`;
822
+ previewContainer.addEventListener('click', () => handleHistoryItemClick(index));
823
+
824
+ const iframe = document.createElement('iframe');
825
+ iframe.className = 'history-thumbnail-preview';
826
+ iframe.title = `Preview of Evolution ${index + 1}`;
827
+ const scaledContent = `
828
+ <style>
829
+ html { transform: scale(0.25); transform-origin: 0 0; width: 400%; height: 400%; overflow: hidden !important; background-color: #fff; }
830
+ body { width: 100%; height: 100%; overflow: hidden !important; padding: 0 !important; margin: 0 !important; }
831
+ </style>
832
+ ${entry.code}
833
+ `;
834
+ iframe.srcdoc = scaledContent;
835
+ previewContainer.appendChild(iframe);
836
+
837
+ const titleEl = document.createElement('div');
838
+ titleEl.className = 'history-thumbnail-title';
839
+ titleEl.textContent = `Evo ${index + 1}: ${entry.prompt.substring(0, 20)}${entry.prompt.length > 20 ? '...' : ''}`;
840
+ titleEl.title = `Prompt: ${entry.prompt}\\nClick to view Evolution ${index + 1}`;
841
+ titleEl.addEventListener('click', () => handleHistoryItemClick(index));
842
+
843
+ const fsBtn = document.createElement('button');
844
+ fsBtn.className = 'history-thumbnail-fullscreen-btn';
845
+ fsBtn.title = 'View Fullscreen';
846
+ fsBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>`;
847
+ fsBtn.addEventListener('click', (e) => {
848
+ e.stopPropagation();
849
+ enterFullscreen(index, true);
850
+ });
851
+
852
+ thumbItem.appendChild(previewContainer);
853
+ thumbItem.appendChild(titleEl);
854
+ thumbItem.appendChild(fsBtn);
855
+ return thumbItem;
856
+ }
857
+
858
+ function renderHistoryPanel() {
859
+ if (!historyPanelEl || !historyPanelPlaceholderEl) return;
860
+ historyPanelEl.innerHTML = ''; // Clear previous items
861
+
862
+ if (evolutionTimeline.length === 0) {
863
+ historyPanelEl.appendChild(historyPanelPlaceholderEl);
864
+ historyPanelPlaceholderEl.classList.remove('hidden');
865
+ return;
866
+ }
867
+
868
+ historyPanelPlaceholderEl.classList.add('hidden');
869
+
870
+ const totalItems = evolutionTimeline.length;
871
+ const middleIndex = Math.floor(totalItems / 2);
872
+ // const maxRotation = 15; // Max rotation in degrees for edge items (rotation is now 0)
873
+ const yOffsetFactor = 4; // How much non-active items lift towards the edges
874
+ const zOffsetFactor = -15; // How much non-active items move back towards the edges
875
+ const baseZIndex = 10; // Base z-index for non-active items
876
+
877
+ // Define specific offsets for the active item to make it stand out
878
+ const activeYOffset = -15; // Moves active item further up
879
+ const activeZOffset = 25; // Moves active item further forward
880
+
881
+ evolutionTimeline.forEach((entry, index) => {
882
+ const thumbItem = createHistoryThumbnailDOM(entry, index);
883
+ let finalTransform;
884
+ let finalZIndex;
885
+
886
+ if (index === activeTimelineIndex) {
887
+ thumbItem.classList.add('active-history-item');
888
+ // Transform for the active item: more prominent
889
+ finalTransform = `translateY(${activeYOffset}px) translateZ(${activeZOffset}px) rotate(0deg)`;
890
+ // The .active-history-item class in CSS sets z-index: 50.
891
+ // We can set it here too for consistency or rely on CSS.
892
+ finalZIndex = 50;
893
+ } else {
894
+ // Calculate transformations relative to the middle for non-active items
895
+ const deltaFromMiddle = index - middleIndex;
896
+ const rotation = 0; // Rotation is kept at 0
897
+ const translateY = Math.abs(deltaFromMiddle) * yOffsetFactor;
898
+ const translateZ = Math.abs(deltaFromMiddle) * zOffsetFactor;
899
+
900
+ finalTransform = `translateY(${translateY}px) translateZ(${translateZ}px) rotate(${rotation}deg)`;
901
+ finalZIndex = baseZIndex - Math.abs(deltaFromMiddle);
902
+ }
903
+
904
+ thumbItem.style.zIndex = `${finalZIndex}`;
905
+ thumbItem.style.transform = finalTransform;
906
+
907
+ historyPanelEl.appendChild(thumbItem);
908
+ });
909
+ const activeThumb = historyPanelEl.querySelector('.active-history-item');
910
+ if (activeThumb) {
911
+ // Keep scrollIntoView, maybe adjust behavior if needed
912
+ activeThumb.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
913
+ }
914
+ }
915
+
916
+ // --- Full Screen Logic ---
917
+ function enterFullscreen(index, isFromHistory = false) {
918
+ if (!fullscreenIframeEl || !fullscreenOverlayEl || !document.body || !fullscreenHistoryNavEl || !historyNavPrevBtnEl || !historyNavNextBtnEl) {
919
+ console.error("Cannot enter fullscreen: Overlay or nav elements missing.");
920
+ return;
921
+ }
922
+
923
+ let codeToDisplay;
924
+ currentFullscreenHistoryIndex = -1;
925
+ fullscreenHistoryNavEl.classList.remove('visible');
926
+
927
+ if (isFromHistory) {
928
+ if (index < 0 || index >= evolutionTimeline.length || !evolutionTimeline[index]) {
929
+ console.warn("Cannot enter fullscreen for history item", index); return;
930
+ }
931
+ codeToDisplay = evolutionTimeline[index].code;
932
+ currentFullscreenHistoryIndex = index;
933
+
934
+ fullscreenHistoryNavEl.classList.add('visible');
935
+ historyNavPrevBtnEl.disabled = (currentFullscreenHistoryIndex <= 0);
936
+ historyNavNextBtnEl.disabled = (currentFullscreenHistoryIndex >= evolutionTimeline.length - 1);
937
+
938
+ } else {
939
+ if (index < 0 || index >= numVariationsToGenerate || !generatedCode[index]) {
940
+ console.warn("Cannot enter fullscreen for variation grid item", index); return;
941
+ }
942
+ codeToDisplay = generatedCode[index];
943
+ }
944
+
945
+ fullscreenIframeEl.srcdoc = codeToDisplay;
946
+ fullscreenOverlayEl.classList.add('visible');
947
+ document.body.classList.add('fullscreen-active');
948
+ document.documentElement.style.overflow = 'hidden';
949
+ }
950
+ function exitFullscreen() {
951
+ if (!fullscreenOverlayEl || !document.body || !fullscreenIframeEl || !fullscreenHistoryNavEl) {
952
+ console.error("Cannot exit fullscreen: Overlay or nav elements missing.");
953
+ return;
954
+ }
955
+ fullscreenOverlayEl.classList.remove('visible');
956
+ document.body.classList.remove('fullscreen-active');
957
+ document.documentElement.style.overflow = '';
958
+ currentFullscreenHistoryIndex = -1;
959
+ fullscreenHistoryNavEl.classList.remove('visible');
960
+ setTimeout(() => { if (fullscreenIframeEl) fullscreenIframeEl.srcdoc = 'about:blank'; }, 300);
961
+ }
962
+
963
+ function showPreviousHistoryInFullscreen() {
964
+ if (currentFullscreenHistoryIndex > 0) {
965
+ currentFullscreenHistoryIndex--;
966
+ activeTimelineIndex = currentFullscreenHistoryIndex;
967
+ enterFullscreen(activeTimelineIndex, true);
968
+ renderHistoryPanel();
969
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
970
+ if (historyEntry && codeOutputEl) {
971
+ codeOutputEl.textContent = historyEntry.code;
972
+ if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
973
+ }
974
+ }
975
+ }
976
+
977
+ function showNextHistoryInFullscreen() {
978
+ if (currentFullscreenHistoryIndex < evolutionTimeline.length - 1) {
979
+ currentFullscreenHistoryIndex++;
980
+ activeTimelineIndex = currentFullscreenHistoryIndex;
981
+ enterFullscreen(activeTimelineIndex, true);
982
+ renderHistoryPanel();
983
+ const historyEntry = evolutionTimeline[activeTimelineIndex];
984
+ if (historyEntry && codeOutputEl) {
985
+ codeOutputEl.textContent = historyEntry.code;
986
+ if(selectedCodeTitleH3El) selectedCodeTitleH3El.textContent = `Code (Evolution ${activeTimelineIndex + 1} - Historical):`;
987
+ }
988
+ }
989
+ }
990
+
991
+ // --- Prompt Modal Logic ---
992
+ function showPromptModal() {
993
+ if (!promptModalOverlayEl || !modalUserPromptEl || !modalRefinementCheckboxEl || !numVariationsSliderEl || !modelSelEl || !modalContextSizeValueEl || !modalMaxTokensValueEl || !modalMaxTokensSliderEl) return;
994
+
995
+ updateModelSpecificUI(modelSelEl.value); // Update UI based on current model when modal opens
996
+
997
+ modalRefinementCheckboxEl.checked = (activeTimelineIndex !== -1);
998
+ numVariationsSliderEl.value = lastGenerationConfig.numVariations;
999
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
1000
+
1001
+ // modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget; // Removed
1002
+ // if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget; // Removed
1003
+
1004
+ // Animation handling
1005
+ promptModalOverlayEl.classList.remove('modal-anim-fade-out');
1006
+ promptModalOverlayEl.style.display = 'flex'; // Or 'block' if that was its original display type
1007
+ promptModalOverlayEl.classList.add('modal-anim-fade-in');
1008
+ // promptModalOverlayEl.classList.add('visible'); // Keep this if it controls other things besides opacity/visibility
1009
+
1010
+ modalUserPromptEl.focus();
1011
+ }
1012
+
1013
+ function hidePromptModal() {
1014
+ if (!promptModalOverlayEl) return;
1015
+
1016
+ promptModalOverlayEl.classList.remove('modal-anim-fade-in');
1017
+ promptModalOverlayEl.classList.add('modal-anim-fade-out');
1018
+
1019
+ // Wait for animation to finish before hiding
1020
+ const handleAnimationEnd = () => {
1021
+ promptModalOverlayEl.style.display = 'none';
1022
+ // promptModalOverlayEl.classList.remove('visible'); // Keep this if it controls other things
1023
+ promptModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1024
+ };
1025
+ promptModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1026
+ }
1027
+
1028
+ function handleModalGenerate() {
1029
+ if (!modalUserPromptEl || !modalGenerateBtnEl) return;
1030
+ const modalPrompt = modalUserPromptEl.value.trim();
1031
+ if (modalPrompt) {
1032
+ hidePromptModal();
1033
+ if (!modalGenerateBtnEl.disabled) {
1034
+ generateVariations();
1035
+ }
1036
+ } else {
1037
+ console.warn("Modal prompt is empty. Not generating.");
1038
+ }
1039
+ }
1040
+
1041
+ // --- Config Modal Logic ---
1042
+ function showConfigModal() {
1043
+ if (!configModalOverlayEl) return;
1044
+
1045
+ // Animation handling
1046
+ configModalOverlayEl.classList.remove('modal-anim-fade-out');
1047
+ configModalOverlayEl.style.display = 'flex'; // Or 'block'
1048
+ configModalOverlayEl.classList.add('modal-anim-fade-in');
1049
+ // configModalOverlayEl.classList.add('visible');
1050
+ }
1051
+ function hideConfigModal() {
1052
+ if (!configModalOverlayEl) return;
1053
+
1054
+ configModalOverlayEl.classList.remove('modal-anim-fade-in');
1055
+ configModalOverlayEl.classList.add('modal-anim-fade-out');
1056
+
1057
+ const handleAnimationEnd = () => {
1058
+ configModalOverlayEl.style.display = 'none';
1059
+ // configModalOverlayEl.classList.remove('visible');
1060
+ configModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1061
+ };
1062
+ configModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1063
+ }
1064
+
1065
+ // --- Confirmation Modal Logic ---
1066
+ function showConfirmModal(message, onConfirmCallback) {
1067
+ if (!confirmModalOverlayEl || !confirmModalMessageEl || !confirmModalConfirmBtnEl || !confirmModalCancelBtnEl) {
1068
+ console.error("Confirmation modal elements not found!");
1069
+ // Fallback to default confirm if modal elements are missing
1070
+ if (confirm(message)) {
1071
+ onConfirmCallback();
1072
+ }
1073
+ return;
1074
+ }
1075
+
1076
+ confirmModalMessageEl.textContent = message;
1077
+ currentConfirmCallback = onConfirmCallback; // Store the callback
1078
+
1079
+ // Clear previous listeners (important!)
1080
+ confirmModalConfirmBtnEl.replaceWith(confirmModalConfirmBtnEl.cloneNode(true));
1081
+ confirmModalCancelBtnEl.replaceWith(confirmModalCancelBtnEl.cloneNode(true));
1082
+ // Re-find buttons after cloning
1083
+ confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
1084
+ confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
1085
+
1086
+ // Add new listeners
1087
+ confirmModalConfirmBtnEl.addEventListener('click', handleConfirm);
1088
+ confirmModalCancelBtnEl.addEventListener('click', hideConfirmModal);
1089
+
1090
+ // Show modal with animation
1091
+ confirmModalOverlayEl.classList.remove('modal-anim-fade-out');
1092
+ confirmModalOverlayEl.style.display = 'flex';
1093
+ confirmModalOverlayEl.classList.add('modal-anim-fade-in');
1094
+ }
1095
+
1096
+ function hideConfirmModal() {
1097
+ if (!confirmModalOverlayEl) return;
1098
+
1099
+ confirmModalOverlayEl.classList.remove('modal-anim-fade-in');
1100
+ confirmModalOverlayEl.classList.add('modal-anim-fade-out');
1101
+
1102
+ const handleAnimationEnd = () => {
1103
+ confirmModalOverlayEl.style.display = 'none';
1104
+ currentConfirmCallback = null; // Clear callback when hidden
1105
+ confirmModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1106
+ };
1107
+ confirmModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1108
+ }
1109
+
1110
+ function handleConfirm() {
1111
+ hideConfirmModal();
1112
+ if (typeof currentConfirmCallback === 'function') {
1113
+ currentConfirmCallback(); // Execute the stored callback
1114
+ }
1115
+ currentConfirmCallback = null; // Clear callback after execution
1116
+ }
1117
+
1118
+ // --- History Navigation Logic ---
1119
+ function navigateToPreviousHistory() {
1120
+ if (activeTimelineIndex > 0) {
1121
+ handleHistoryItemClick(activeTimelineIndex - 1);
1122
+ }
1123
+ }
1124
+
1125
+ function navigateToNextHistory() {
1126
+ if (activeTimelineIndex < evolutionTimeline.length - 1) {
1127
+ handleHistoryItemClick(activeTimelineIndex + 1);
1128
+ }
1129
+ }
1130
+
1131
+ function updateHistoryNavigationButtons() {
1132
+ if (!historyNavLeftBtnEl || !historyNavRightBtnEl) return;
1133
+ historyNavLeftBtnEl.disabled = activeTimelineIndex <= 0;
1134
+ historyNavRightBtnEl.disabled = activeTimelineIndex >= evolutionTimeline.length - 1;
1135
+ }
1136
+
1137
+ // --- Helper function to call Gemini for code splitting ---
1138
+ async function fetchCodeSplitFromGemini(apiKey, fullHtmlContent) {
1139
+ const exportModelName = "gemini-2.5-flash-preview-04-17"; // Hardcoded model for export
1140
+ const API_ENDPOINT = `${API_BASE_URL}${exportModelName}:generateContent?key=${apiKey}`;
1141
+ const prompt = `
1142
+ You are an expert web developer. You are given a single HTML document that might contain inline CSS within <style> tags and inline JavaScript within <script> tags. Your task is to separate this document into three distinct components: HTML structure, CSS styles, and JavaScript code.
1143
+
1144
+ Follow these instructions carefully:
1145
+ 1. **HTML Output**: This should be the main HTML structure.
1146
+ * If there were inline <style> tags, remove them. Add a <link rel="stylesheet" href="style.css"> in the <head> instead.
1147
+ * If there were inline <script> tags (especially those not setting up initial variables or configurations that need to be in the head), try to move their content to what will become an external script.js file. Add <script src="script.js" defer><\\/script> before the closing </body> tag. For simple, short scripts that are clearly for page setup and are in the head, they can sometimes remain, but prefer externalizing functional code.
1148
+ * Ensure the HTML output is clean and well-formed.
1149
+ 2. **CSS Output**: This should contain ALL CSS rules extracted from any <style>...</style> blocks in the original HTML. If no <style> blocks were present, this should be an empty string or a comment indicating no CSS.
1150
+ 3. **JavaScript Output**: This should contain ALL JavaScript code extracted from any <script>...</script> blocks (that are not JSON-LD or other non-executable script types). If no functional <script> blocks were present, this should be an empty string or a comment indicating no JavaScript.
1151
+
1152
+ Provide the output STRICTLY as a JSON object with the following keys: "html_code", "css_code", "js_code".
1153
+
1154
+ Example of desired JSON output format:
1155
+ {
1156
+ "html_code": "<!DOCTYPE html>...<link rel=\\\"stylesheet\\\" href=\\\"style.css\\\"><script src=\\\"script.js\\\" defer><\\/script></body></html>",
1157
+ "css_code": "body { font-family: sans-serif; } ...",
1158
+ "js_code": "console.log(\\\'Hello World!\\\'); ...\"
1159
+ }
1160
+
1161
+ Original HTML content:
1162
+ \`\`\`html
1163
+ ${fullHtmlContent}\n\`\`\`
1164
+
1165
+ Return ONLY the JSON object. Do not include any other explanatory text or markdown formatting outside the JSON structure itself.
1166
+ `;
1167
+
1168
+ try {
1169
+ const response = await fetch(API_ENDPOINT, {
1170
+ method: 'POST',
1171
+ headers: { 'Content-Type': 'application/json' },
1172
+ body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
1173
+ });
1174
+
1175
+ if (!response.ok) {
1176
+ const errorData = await response.json().catch(() => null);
1177
+ const errorMsg = errorData?.error?.message || `HTTP Error: ${response.status}`;
1178
+ console.error('Gemini API Error:', errorMsg);
1179
+ throw new Error(`API Error: ${errorMsg}`);
1180
+ }
1181
+
1182
+ const responseData = await response.json();
1183
+ const candidate = responseData.candidates?.[0];
1184
+ if (candidate?.content?.parts?.[0]?.text) {
1185
+ let rawText = candidate.content.parts[0].text;
1186
+ console.log("Raw response from Gemini for splitting:", rawText); // Log the raw response
1187
+
1188
+ let jsonString = null;
1189
+
1190
+ // Attempt 1: Look for JSON within markdown code blocks
1191
+ const markdownJsonMatch = rawText.match(/```json\n(\{[\s\S]*\})\n```/s) || rawText.match(/```\n(\{[\s\S]*\})\n```/s);
1192
+ if (markdownJsonMatch && markdownJsonMatch[1]) {
1193
+ jsonString = markdownJsonMatch[1];
1194
+ } else {
1195
+ // Attempt 2: Fallback to existing regex if no markdown block found or it's malformed
1196
+ const directJsonMatch = rawText.match(/\{.*\}/s);
1197
+ if (directJsonMatch) {
1198
+ jsonString = directJsonMatch[0];
1199
+ }
1200
+ }
1201
+
1202
+ if (jsonString) {
1203
+ try {
1204
+ return JSON.parse(jsonString);
1205
+ } catch (parseError) {
1206
+ console.error("Failed to parse JSON string from model:", jsonString, parseError);
1207
+ throw new Error("JSON parsing failed after attempting to clean model response."); // Simplified error
1208
+ }
1209
+ }
1210
+ throw new Error("Clean JSON object not found in model's response after attempting to extract.");
1211
+ }
1212
+ throw new Error("No valid content found in model's response.");
1213
+
1214
+ } catch (error) {
1215
+ console.error('Error fetching or parsing split code:', error);
1216
+ throw error; // Re-throw to be caught by caller
1217
+ }
1218
+ }
1219
+
1220
+ // --- Full Prompt Display Modal Logic ---
1221
+ function showFullPromptModal(fullPrompt) {
1222
+ if (!promptDisplayModalOverlayEl || !fullPromptTextEl) return;
1223
+
1224
+ fullPromptTextEl.textContent = fullPrompt;
1225
+
1226
+ // Animation handling
1227
+ promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-out');
1228
+ promptDisplayModalOverlayEl.style.display = 'flex';
1229
+ promptDisplayModalOverlayEl.classList.add('modal-anim-fade-in');
1230
+ }
1231
+
1232
+ function hideFullPromptModal() {
1233
+ if (!promptDisplayModalOverlayEl) return;
1234
+
1235
+ promptDisplayModalOverlayEl.classList.remove('modal-anim-fade-in');
1236
+ promptDisplayModalOverlayEl.classList.add('modal-anim-fade-out');
1237
+
1238
+ const handleAnimationEnd = () => {
1239
+ promptDisplayModalOverlayEl.style.display = 'none';
1240
+ promptDisplayModalOverlayEl.removeEventListener('animationend', handleAnimationEnd);
1241
+ };
1242
+ promptDisplayModalOverlayEl.addEventListener('animationend', handleAnimationEnd);
1243
+ }
1244
+
1245
+ // --- Initialization ---
1246
+ document.addEventListener('DOMContentLoaded', () => {
1247
+ console.log("DOMContentLoaded event fired.");
1248
+
1249
+ apiKeyEl = document.getElementById('api-key');
1250
+ modelSelEl = document.getElementById('model-select');
1251
+ const codeOutputPre = document.getElementById('code-output');
1252
+ if (codeOutputPre) { codeOutputEl = codeOutputPre.querySelector('code'); } else { console.error("Code output <pre> not found");}
1253
+ errorMessageEl = document.getElementById('error-message');
1254
+ refinementLoadingIndicator = document.getElementById('refinement-loading-indicator');
1255
+ mainContentEl = document.getElementById('main-content');
1256
+ configButtonEl = document.getElementById('config-button');
1257
+ intervalSliderEl = document.getElementById('preview-interval-slider');
1258
+ intervalValueDisplayEl = document.getElementById('interval-value');
1259
+ fullscreenOverlayEl = document.getElementById('fullscreen-overlay');
1260
+ fullscreenIframeEl = document.getElementById('fullscreen-iframe');
1261
+ exitFullscreenBtnEl = document.getElementById('exit-fullscreen-btn');
1262
+ perspectiveViewportEl = document.getElementById('perspective-viewport');
1263
+ previewGridWrapperEl = document.getElementById('preview-grid-wrapper');
1264
+ historyPanelEl = document.getElementById('history-panel');
1265
+ historyPanelPlaceholderEl = document.getElementById('history-panel-placeholder');
1266
+ selectedCodeTitleH3El = document.getElementById('selected-code-title');
1267
+ mainContentTitleH1El = document.getElementById('main-content-title');
1268
+ mainContentSubtitleH2El = document.getElementById('main-content-subtitle');
1269
+ fullscreenHistoryNavEl = document.getElementById('fullscreen-history-nav');
1270
+ historyNavPrevBtnEl = document.getElementById('history-nav-prev');
1271
+ historyNavNextBtnEl = document.getElementById('history-nav-next');
1272
+ promptModalOverlayEl = document.getElementById('prompt-modal-overlay');
1273
+ promptModalContentEl = document.getElementById('prompt-modal-content');
1274
+ modalUserPromptEl = document.getElementById('modal-user-prompt');
1275
+ modalGenerateBtnEl = document.getElementById('modal-generate-button');
1276
+ modalCancelBtnEl = document.getElementById('modal-cancel-button');
1277
+ modalLoadingIndicatorEl = document.getElementById('modal-loading-indicator');
1278
+ modalRefinementCheckboxEl = document.getElementById('modal-refinement-checkbox');
1279
+ numVariationsSliderEl = document.getElementById('num-variations-slider');
1280
+ numVariationsValueDisplayEl = document.getElementById('num-variations-value');
1281
+ configModalOverlayEl = document.getElementById('config-modal-overlay');
1282
+ configModalContentEl = document.getElementById('config-modal-content');
1283
+ configModalCloseBtnEl = document.getElementById('config-modal-close-button');
1284
+ copyCodeButtonEl = document.getElementById('copy-code-button');
1285
+ exportCodeButtonEl = document.getElementById('export-code-button');
1286
+ historyToggleButtonEl = document.getElementById('history-toggle-button');
1287
+ historyArrowDownEl = document.getElementById('history-arrow-down');
1288
+ historyArrowUpEl = document.getElementById('history-arrow-up');
1289
+ newButtonEl = document.getElementById('new-button');
1290
+ confirmModalOverlayEl = document.getElementById('confirm-modal-overlay');
1291
+ confirmModalMessageEl = document.getElementById('confirm-modal-message');
1292
+ confirmModalConfirmBtnEl = document.getElementById('confirm-modal-confirm-button');
1293
+ confirmModalCancelBtnEl = document.getElementById('confirm-modal-cancel-button');
1294
+ historyNavLeftBtnEl = document.getElementById('history-nav-left-button');
1295
+ historyNavRightBtnEl = document.getElementById('history-nav-right-button');
1296
+ promptDisplayModalOverlayEl = document.getElementById('prompt-display-modal-overlay');
1297
+ promptDisplayModalContentEl = document.getElementById('prompt-display-modal-content');
1298
+ fullPromptTextEl = document.getElementById('full-prompt-text');
1299
+ promptDisplayModalCloseBtnEl = document.getElementById('prompt-display-modal-close-button');
1300
+ showPromptModalButtonEl = document.getElementById('show-prompt-modal-button'); // Added
1301
+ modalContextSizeValueEl = document.getElementById('modal-context-size-value'); // Added
1302
+ modalMaxTokensValueEl = document.getElementById('modal-max-tokens-value'); // Added
1303
+ modalMaxTokensSliderEl = document.getElementById('modal-max-tokens-slider'); // Added
1304
+
1305
+ console.log("Checking history toggle elements:");
1306
+ console.log("historyToggleButtonEl:", historyToggleButtonEl);
1307
+ console.log("historyPanelEl:", historyPanelEl);
1308
+ console.log("historyArrowDownEl:", historyArrowDownEl);
1309
+ console.log("historyArrowUpEl:", historyArrowUpEl);
1310
+
1311
+ // --- Check if all required elements exist ---
1312
+ let missingElements = [];
1313
+ const requiredElements = { apiKeyEl, modelSelEl, codeOutputEl, errorMessageEl, refinementLoadingIndicator, mainContentEl, configButtonEl, intervalSliderEl, intervalValueDisplayEl, fullscreenOverlayEl, fullscreenIframeEl, exitFullscreenBtnEl, perspectiveViewportEl, previewGridWrapperEl, historyPanelEl, historyPanelPlaceholderEl, selectedCodeTitleH3El, mainContentTitleH1El, mainContentSubtitleH2El, fullscreenHistoryNavEl, historyNavPrevBtnEl, historyNavNextBtnEl, promptModalOverlayEl, promptModalContentEl, modalUserPromptEl, modalGenerateBtnEl, modalCancelBtnEl, modalLoadingIndicatorEl, modalRefinementCheckboxEl, numVariationsSliderEl, numVariationsValueDisplayEl, configModalOverlayEl, configModalContentEl, configModalCloseBtnEl, copyCodeButtonEl, exportCodeButtonEl, historyToggleButtonEl, historyArrowDownEl, historyArrowUpEl, newButtonEl, confirmModalOverlayEl, confirmModalMessageEl, confirmModalConfirmBtnEl, confirmModalCancelBtnEl, historyNavLeftBtnEl, historyNavRightBtnEl, promptDisplayModalOverlayEl, promptDisplayModalContentEl, fullPromptTextEl, promptDisplayModalCloseBtnEl, showPromptModalButtonEl, modalContextSizeValueEl, modalMaxTokensValueEl, modalMaxTokensSliderEl }; // Added new modal elements
1314
+ for (const key in requiredElements) { if (!requiredElements[key]) { missingElements.push(key); } }
1315
+
1316
+ if (missingElements.length > 0) {
1317
+ console.error("Initialization Error: Critical elements missing!", missingElements);
1318
+ if (document.body) { document.body.innerHTML = `<div class="fixed inset-0 bg-red-900 text-red-200 p-8 flex flex-col items-center justify-center text-center"><h2 class="text-2xl font-bold mb-4">Application Initialization Error</h2><p class="mb-2">Could not find element(s): ${missingElements.join(', ')}.</p><p>Please ensure the HTML structure is correct.</p></div>`; }
1319
+ else { alert(`Initialization Error: Critical elements missing: ${missingElements.join(', ')}.`); }
1320
+ return;
1321
+ }
1322
+
1323
+ // --- Initial UI Setup ---
1324
+ showInitialPreviewStateUI();
1325
+ renderHistoryPanel();
1326
+ updateHistoryNavigationButtons(); // Initial button state
1327
+
1328
+ // --- Event Listeners ---
1329
+ if (newButtonEl) {
1330
+ newButtonEl.addEventListener('click', () => {
1331
+ showConfirmModal(
1332
+ 'Start a new session? This will clear the current state.',
1333
+ () => { location.reload(); } // Pass the reload logic as the callback
1334
+ );
1335
+ });
1336
+ }
1337
+
1338
+ if (configButtonEl) configButtonEl.addEventListener('click', showConfigModal);
1339
+ if (showPromptModalButtonEl) showPromptModalButtonEl.addEventListener('click', showPromptModal); // Added listener for new button
1340
+ if (configModalCloseBtnEl) configModalCloseBtnEl.addEventListener('click', hideConfigModal);
1341
+ if (configModalOverlayEl) configModalOverlayEl.addEventListener('click', (e) => {
1342
+ if (e.target === configModalOverlayEl) { hideConfigModal(); }
1343
+ });
1344
+
1345
+ if (exitFullscreenBtnEl) exitFullscreenBtnEl.addEventListener('click', exitFullscreen);
1346
+ if (historyNavPrevBtnEl) historyNavPrevBtnEl.addEventListener('click', showPreviousHistoryInFullscreen);
1347
+ if (historyNavNextBtnEl) historyNavNextBtnEl.addEventListener('click', showNextHistoryInFullscreen);
1348
+
1349
+ if (modalGenerateBtnEl) modalGenerateBtnEl.addEventListener('click', handleModalGenerate);
1350
+ if (modalCancelBtnEl) modalCancelBtnEl.addEventListener('click', hidePromptModal);
1351
+ if (promptModalOverlayEl) promptModalOverlayEl.addEventListener('click', (e) => {
1352
+ if (e.target === promptModalOverlayEl) { hidePromptModal(); }
1353
+ });
1354
+ if (confirmModalOverlayEl) { // Add overlay click listener for confirm modal
1355
+ confirmModalOverlayEl.addEventListener('click', (e) => {
1356
+ if (e.target === confirmModalOverlayEl) { hideConfirmModal(); }
1357
+ });
1358
+ }
1359
+ if (promptDisplayModalOverlayEl) { // Listener for full prompt display modal overlay click
1360
+ promptDisplayModalOverlayEl.addEventListener('click', (e) => {
1361
+ if (e.target === promptDisplayModalOverlayEl) { hideFullPromptModal(); }
1362
+ });
1363
+ }
1364
+ if (promptDisplayModalCloseBtnEl) { // Listener for full prompt display modal close button
1365
+ promptDisplayModalCloseBtnEl.addEventListener('click', hideFullPromptModal);
1366
+ }
1367
+ if (mainContentSubtitleH2El) { // Listener for clicking the subtitle
1368
+ mainContentSubtitleH2El.addEventListener('click', (e) => {
1369
+ const fullPrompt = e.target.dataset.fullPrompt;
1370
+ if (fullPrompt) { // Check if the data attribute exists (meaning it was truncated)
1371
+ showFullPromptModal(fullPrompt);
1372
+ }
1373
+ });
1374
+ }
1375
+ if (modalUserPromptEl) modalUserPromptEl.addEventListener('keydown', (event) => {
1376
+ if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
1377
+ event.preventDefault(); handleModalGenerate();
1378
+ }
1379
+ });
1380
+ if (numVariationsSliderEl) {
1381
+ numVariationsSliderEl.addEventListener('input', (event) => {
1382
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = event.target.value;
1383
+ });
1384
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
1385
+ }
1386
+
1387
+ if (intervalSliderEl) {
1388
+ intervalSliderEl.addEventListener('input', (event) => {
1389
+ previewUpdateInterval = parseInt(event.target.value, 10);
1390
+ if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
1391
+ });
1392
+ previewUpdateInterval = parseInt(intervalSliderEl.value, 10);
1393
+ if(intervalValueDisplayEl) intervalValueDisplayEl.textContent = previewUpdateInterval;
1394
+ }
1395
+
1396
+ if (copyCodeButtonEl) {
1397
+ copyCodeButtonEl.addEventListener('click', () => {
1398
+ if (codeOutputEl && codeOutputEl.textContent && codeOutputEl.textContent !== '// Select a variation or history item to view its code.') {
1399
+ const textToCopy = codeOutputEl.textContent;
1400
+ const textArea = document.createElement("textarea");
1401
+ textArea.value = textToCopy;
1402
+ textArea.style.position = "fixed"; // Prevent scrolling to bottom
1403
+ document.body.appendChild(textArea);
1404
+ textArea.focus();
1405
+ textArea.select();
1406
+ try {
1407
+ const successful = document.execCommand('copy');
1408
+ if (successful) {
1409
+ const originalText = copyCodeButtonEl.innerHTML;
1410
+ copyCodeButtonEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check inline-block mr-1"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!`;
1411
+ copyCodeButtonEl.classList.add('copied');
1412
+ setTimeout(() => {
1413
+ copyCodeButtonEl.innerHTML = originalText;
1414
+ copyCodeButtonEl.classList.remove('copied');
1415
+ }, 2000);
1416
+ } else {
1417
+ console.error('Fallback: Failed to copy code using execCommand.');
1418
+ }
1419
+ } catch (err) {
1420
+ console.error('Fallback: Error copying code using execCommand: ', err);
1421
+ }
1422
+ document.body.removeChild(textArea);
1423
+ }
1424
+ });
1425
+ }
1426
+
1427
+ if (exportCodeButtonEl) {
1428
+ exportCodeButtonEl.addEventListener('click', async () => {
1429
+ const currentCode = codeOutputEl?.textContent;
1430
+ const apiKey = apiKeyEl?.value;
1431
+
1432
+ if (!currentCode || currentCode.startsWith('//') || currentCode.trim() === '') {
1433
+ alert('No code selected or available to export.');
1434
+ return;
1435
+ }
1436
+ if (!apiKey) {
1437
+ alert('API Key is missing. Please configure it in settings.');
1438
+ showConfigModal(); // Show config modal if API key is missing
1439
+ return;
1440
+ }
1441
+ if (typeof JSZip === 'undefined') {
1442
+ alert('JSZip library is not loaded. Export cannot proceed.');
1443
+ console.error("JSZip is not defined!");
1444
+ return;
1445
+ }
1446
+
1447
+ const originalButtonContent = exportCodeButtonEl.innerHTML;
1448
+ exportCodeButtonEl.disabled = true;
1449
+ exportCodeButtonEl.innerHTML = '<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Exporting...';
1450
+
1451
+ try {
1452
+ const splitCode = await fetchCodeSplitFromGemini(apiKey, currentCode);
1453
+
1454
+ if (!splitCode || typeof splitCode.html_code === 'undefined' || typeof splitCode.css_code === 'undefined' || typeof splitCode.js_code === 'undefined') {
1455
+ throw new Error('Invalid response structure from code splitting model.');
1456
+ }
1457
+
1458
+ const zip = new JSZip();
1459
+ zip.file("index.html", splitCode.html_code);
1460
+ zip.file("style.css", splitCode.css_code);
1461
+ zip.file("script.js", splitCode.js_code);
1462
+
1463
+ const zipBlob = await zip.generateAsync({ type: "blob" });
1464
+
1465
+ const downloadLink = document.createElement('a');
1466
+ downloadLink.href = URL.createObjectURL(zipBlob);
1467
+ downloadLink.download = "exported_code.zip";
1468
+ document.body.appendChild(downloadLink);
1469
+ downloadLink.click();
1470
+ document.body.removeChild(downloadLink);
1471
+ URL.revokeObjectURL(downloadLink.href);
1472
+
1473
+ exportCodeButtonEl.innerHTML = originalButtonContent;
1474
+
1475
+ } catch (error) {
1476
+ console.error("Export failed:", error);
1477
+ alert(`Export failed: ${error.message}`);
1478
+ exportCodeButtonEl.innerHTML = originalButtonContent;
1479
+ } finally {
1480
+ exportCodeButtonEl.disabled = false;
1481
+ }
1482
+ });
1483
+ }
1484
+
1485
+ if (historyToggleButtonEl && historyPanelEl && historyArrowDownEl && historyArrowUpEl) {
1486
+ historyToggleButtonEl.addEventListener('click', () => {
1487
+ const isCollapsed = historyPanelEl.classList.toggle('history-collapsed');
1488
+ const rootStyles = getComputedStyle(document.documentElement);
1489
+ const expandedHeight = rootStyles.getPropertyValue('--history-panel-expanded-height').trim();
1490
+ const collapsedHeight = rootStyles.getPropertyValue('--history-panel-collapsed-height').trim();
1491
+
1492
+ if (isCollapsed) {
1493
+ document.documentElement.style.setProperty('--history-panel-current-height', collapsedHeight);
1494
+ historyArrowDownEl.classList.add('hidden');
1495
+ historyArrowUpEl.classList.remove('hidden');
1496
+ } else {
1497
+ document.documentElement.style.setProperty('--history-panel-current-height', expandedHeight);
1498
+ historyArrowDownEl.classList.remove('hidden');
1499
+ historyArrowUpEl.classList.add('hidden');
1500
+ // Ensure content is visible if re-expanding and it was set to display:none directly
1501
+ // This is now handled by the .history-collapsed CSS rule for children.
1502
+ renderHistoryPanel(); // Re-render to ensure items are correctly displayed if they were hidden
1503
+ }
1504
+ });
1505
+ }
1506
+
1507
+ if (historyNavLeftBtnEl) {
1508
+ historyNavLeftBtnEl.addEventListener('click', navigateToPreviousHistory);
1509
+ }
1510
+ if (historyNavRightBtnEl) {
1511
+ historyNavRightBtnEl.addEventListener('click', navigateToNextHistory);
1512
+ }
1513
+
1514
+ // Keyboard Shortcuts Listener
1515
+ document.addEventListener('keydown', (event) => {
1516
+ if (event.key === 'Escape') {
1517
+ // Check style.display instead of .visible class for animated modals
1518
+ if (configModalOverlayEl.style.display !== 'none' && !configModalOverlayEl.classList.contains('modal-anim-fade-out')) {
1519
+ hideConfigModal();
1520
+ } else if (promptModalOverlayEl.style.display !== 'none' && !promptModalOverlayEl.classList.contains('modal-anim-fade-out')) {
1521
+ hidePromptModal();
1522
+ } else if (confirmModalOverlayEl.style.display !== 'none' && !confirmModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for confirm modal
1523
+ hideConfirmModal();
1524
+ } else if (promptDisplayModalOverlayEl.style.display !== 'none' && !promptDisplayModalOverlayEl.classList.contains('modal-anim-fade-out')) { // Add Escape handler for prompt display modal
1525
+ hideFullPromptModal();
1526
+ } else if (document.body.classList.contains('fullscreen-active')) {
1527
+ exitFullscreen();
1528
+ }
1529
+ }
1530
+ const targetTagName = event.target ? event.target.tagName.toLowerCase() : null;
1531
+ const isTypingInInputOrTextarea = targetTagName === 'input' || targetTagName === 'textarea';
1532
+
1533
+ if (document.body.classList.contains('fullscreen-active') && currentFullscreenHistoryIndex !== -1 && !isTypingInInputOrTextarea) {
1534
+ if (event.key.toLowerCase() === 'w') {
1535
+ event.preventDefault();
1536
+ showPreviousHistoryInFullscreen();
1537
+ } else if (event.key.toLowerCase() === 'd') {
1538
+ event.preventDefault();
1539
+ showNextHistoryInFullscreen();
1540
+ }
1541
+ }
1542
+ if (event.altKey && !isTypingInInputOrTextarea) {
1543
+ if (event.key.toLowerCase() === 'p' || event.code === 'KeyP') {
1544
+ event.preventDefault();
1545
+ if (!promptModalOverlayEl.classList.contains('visible') && !configModalOverlayEl.classList.contains('visible')) {
1546
+ showPromptModal();
1547
+ }
1548
+ }
1549
+ else if (event.key.toLowerCase() === 'j' || event.code === 'KeyJ') {
1550
+ event.preventDefault();
1551
+ if (lastGenerationConfig.prompt) {
1552
+ console.log("Alt+J: Regenerating with last settings:", lastGenerationConfig);
1553
+ modalUserPromptEl.value = lastGenerationConfig.prompt;
1554
+ modalRefinementCheckboxEl.checked = lastGenerationConfig.isRefinement;
1555
+ numVariationsSliderEl.value = lastGenerationConfig.numVariations;
1556
+ if(numVariationsValueDisplayEl) numVariationsValueDisplayEl.textContent = numVariationsSliderEl.value;
1557
+ // Lines below referencing removed elements must be commented out:
1558
+ // modalThinkingBudgetSliderEl.value = lastGenerationConfig.thinkingBudget;
1559
+ // if(modalThinkingBudgetValueDisplayEl) modalThinkingBudgetValueDisplayEl.textContent = lastGenerationConfig.thinkingBudget;
1560
+
1561
+ if (lastGenerationConfig.isRefinement) {
1562
+ activeTimelineIndex = lastGenerationConfig.refinedTimelineIndex;
1563
+ }
1564
+ handleModalGenerate();
1565
+ } else {
1566
+ console.log("Alt+J: No last generation settings found. Opening prompt modal.");
1567
+ showPromptModal();
1568
+ }
1569
+ }
1570
+ else if (event.key.toLowerCase() === 'o' || event.code === 'KeyO') {
1571
+ event.preventDefault();
1572
+ if (!configModalOverlayEl.classList.contains('visible') && !promptModalOverlayEl.classList.contains('visible')) {
1573
+ showConfigModal();
1574
+ }
1575
+ }
1576
+ // History navigation shortcuts (non-fullscreen)
1577
+ if (event.key === 'PageUp') {
1578
+ event.preventDefault();
1579
+ navigateToPreviousHistory();
1580
+ } else if (event.key === 'PageDown') {
1581
+ event.preventDefault();
1582
+ navigateToNextHistory();
1583
+ }
1584
+ }
1585
+ });
1586
+ console.log("Initialization setup complete.");
1587
+
1588
+ if (modelSelEl) { // Add event listener for model selection change
1589
+ modelSelEl.addEventListener('change', (event) => {
1590
+ updateModelSpecificUI(event.target.value);
1591
+ });
1592
+ }
1593
+
1594
+ if (modalMaxTokensSliderEl) { // Add event listener for max tokens slider
1595
+ modalMaxTokensSliderEl.addEventListener('input', (event) => {
1596
+ if (modalMaxTokensValueEl) {
1597
+ // Convert the slider value to a number and then to a locale string
1598
+ const numericValue = parseInt(event.target.value, 10);
1599
+ modalMaxTokensValueEl.textContent = numericValue.toLocaleString();
1600
+ }
1601
+ });
1602
+ }
1603
+ });
1604
+
1605
+ // --- Function to Update Model-Specific UI Elements ---
1606
+ function updateModelSpecificUI(selectedModelName) {
1607
+ if (!modalContextSizeValueEl || !modalMaxTokensValueEl || !modalMaxTokensSliderEl) return;
1608
+
1609
+ const details = MODEL_DETAILS[selectedModelName];
1610
+ if (details) {
1611
+ modalContextSizeValueEl.textContent = details.context.toLocaleString();
1612
+
1613
+ modalMaxTokensSliderEl.min = 256; // A sensible minimum
1614
+ modalMaxTokensSliderEl.max = details.maxOutput;
1615
+
1616
+ // Set the slider to the model's maximum output tokens by default
1617
+ let defaultValue = details.maxOutput;
1618
+ // Ensure it's not less than the slider's defined minimum (though maxOutput should always be higher)
1619
+ defaultValue = Math.max(parseInt(modalMaxTokensSliderEl.min, 10), defaultValue);
1620
+ modalMaxTokensSliderEl.value = defaultValue;
1621
+ modalMaxTokensValueEl.textContent = defaultValue.toLocaleString();
1622
+
1623
+ // Adjust step for very large ranges if necessary, or keep it fixed.
1624
+ // For simplicity, keeping step fixed for now as defined in HTML or can be dynamic.
1625
+ // Example: if maxOutput is very large, step might be larger.
1626
+ if (details.maxOutput > 32000) {
1627
+ modalMaxTokensSliderEl.step = 512;
1628
+ } else if (details.maxOutput > 8000) {
1629
+ modalMaxTokensSliderEl.step = 256;
1630
+ } else {
1631
+ modalMaxTokensSliderEl.step = 128;
1632
+ }
1633
+
1634
+ } else {
1635
+ modalContextSizeValueEl.textContent = "N/A";
1636
+ modalMaxTokensValueEl.textContent = "N/A";
1637
+ modalMaxTokensSliderEl.min = 256;
1638
+ modalMaxTokensSliderEl.max = 4096; // Default if model not found
1639
+ modalMaxTokensSliderEl.value = 1024;
1640
+ modalMaxTokensSliderEl.step = 128;
1641
+ }
1642
+ }