console.log("Script execution started."); // --- Global Variables --- let generatedCode = []; let currentCleanedCode = []; let evolutionTimeline = []; let activeTimelineIndex = -1; let selectedVariationGridIndex = -1; let currentFullscreenHistoryIndex = -1; let originalUserPromptForCurrentGeneration = ''; let lastPreviewUpdateTime = []; let previewUpdateInterval = 500; let numVariationsToGenerate = 4; let activeApiControllers = []; let lastGenerationConfig = { prompt: '', isRefinement: false, numVariations: 4, refinedTimelineIndex: -1 }; // --- DOM Element References --- let apiKeyEl, codeOutputEl, errorMessageEl; let modelSelEl; let selectButtons = []; let fullscreenButtons = []; let previewItems = []; let refinementLoadingIndicator; let mainContentEl, configButtonEl; let intervalSliderEl, intervalValueDisplayEl; let fullscreenOverlayEl, fullscreenIframeEl, exitFullscreenBtnEl; let perspectiveViewportEl, previewGridWrapperEl; let historyPanelEl, historyPanelPlaceholderEl; let selectedCodeTitleH3El; let mainContentTitleH1El, mainContentSubtitleH2El; let fullscreenHistoryNavEl, historyNavPrevBtnEl, historyNavNextBtnEl; let promptModalOverlayEl, promptModalContentEl, modalUserPromptEl, modalGenerateBtnEl, modalCancelBtnEl, modalLoadingIndicatorEl; let modalRefinementCheckboxEl, numVariationsSliderEl, numVariationsValueDisplayEl; let configModalOverlayEl, configModalContentEl, configModalCloseBtnEl, copyCodeButtonEl; let historyToggleButtonEl, historyArrowDownEl, historyArrowUpEl; let exportCodeButtonEl; let newButtonEl; let confirmModalOverlayEl, confirmModalMessageEl, confirmModalConfirmBtnEl, confirmModalCancelBtnEl; let currentConfirmCallback = null; let historyNavLeftBtnEl, historyNavRightBtnEl; let promptDisplayModalOverlayEl, promptDisplayModalContentEl, fullPromptTextEl, promptDisplayModalCloseBtnEl; let showPromptModalButtonEl; // Added for the new button let modalContextSizeValueEl, modalMaxTokensValueEl, modalMaxTokensSliderEl; // Added for new modal elements // --- Constants --- const API_BASE_URL = 'https://api.novita.ai/v3/openai'; // New Novita API const MODEL_DETAILS = { "deepseek/deepseek-v3-turbo": { context: 64000, maxOutput: 16000, defaultOutput: 8192 }, "deepseek/deepseek-r1-turbo": { context: 64000, maxOutput: 16000, defaultOutput: 8192 }, "qwen/qwen3-235b-a22b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 }, "qwen/qwen3-30b-a3b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 }, "qwen/qwen3-32b-fp8": { context: 128000, maxOutput: 128000, defaultOutput: 16384 }, "google/gemma-3-27b-it": { context: 32000, maxOutput: 32000, defaultOutput: 8192 }, "mistralai/mistral-nemo": { context: 131072, maxOutput: 131072, defaultOutput: 16384 }, "meta-llama/llama-4-scout-17b-16e-instruct": { context: 131072, maxOutput: 131072, defaultOutput: 16384 }, "meta-llama/llama-4-maverick-17b-128e-instruct-fp8": { context: 1048576, maxOutput: 1048576, defaultOutput: 16384 } }; // --- Helper Function to Process Stream for One Variation --- async function processStreamForVariation(apiKey, userPrompt, variationIndex, modelName, signal) { console.log(`[Variation ${variationIndex + 1}] Starting generation with model ${modelName}...`); const previewFrame = document.getElementById(`preview-frame-${variationIndex + 1}`); const loader = document.getElementById(`preview-loader-${variationIndex + 1}`); const selectBtn = selectButtons[variationIndex]; const fullscreenBtn = fullscreenButtons[variationIndex]; if (!previewFrame) { console.error(`[Variation ${variationIndex + 1}] Preview frame missing.`); return false; } if (!loader) { console.warn(`[Variation ${variationIndex + 1}] Loader missing.`); } if (loader) loader.classList.remove('hidden'); if (selectBtn) selectBtn.disabled = true; if (fullscreenBtn) fullscreenBtn.disabled = true; if (previewFrame) updateLivePreviewInGrid(variationIndex, '

Generating...

', false); if(lastPreviewUpdateTime[variationIndex] !== undefined) lastPreviewUpdateTime[variationIndex] = 0; const API_ENDPOINT = `${API_BASE_URL}/chat/completions`; // New Novita API endpoint let rawGeneratedCode = ''; let htmlBlockStarted = false; let success = false; let isInsideThinkBlock = false; // Flag to track if we are inside a block // Construct conversation history for Novita API // The prompt passed to this function (userPrompt) is already formatted with instructions and user request. const conversationHistory = [ { role: "user", content: userPrompt } ]; try { console.log(`[Variation ${variationIndex + 1}] Novita API call with model: ${modelName}`); const response = await fetch(API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` // Novita uses Bearer token }, body: JSON.stringify({ model: modelName, // Model name for Novita messages: conversationHistory, stream: true, reasoning: { exclude: true }, // Added reasoning parameter max_tokens: parseInt(modalMaxTokensSliderEl.value, 10) // Send max_tokens }), signal: signal }); if (!response.ok) { let errorDetails = `API Error: ${response.status} ${response.statusText}`; try { const errorDataText = await response.text(); // Try to get text first for more detailed errors console.error(`[Variation ${variationIndex + 1}] Novita API Error Response Text:`, errorDataText); const errorData = JSON.parse(errorDataText); // Then try to parse as JSON errorDetails += ` - ${errorData?.error?.message || errorData?.detail || errorDataText || 'No specific message.'}`; } catch (e) { errorDetails += ` - Could not parse error response.`; } throw new Error(errorDetails); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let sseBuffer = ""; // Buffer for incomplete SSE messages let streamEnded = false; while (!streamEnded) { const { done, value } = await reader.read(); if (done) { streamEnded = true; break; } sseBuffer += decoder.decode(value, { stream: true }); let eolIndex; while ((eolIndex = sseBuffer.indexOf('\n')) >= 0) { const line = sseBuffer.substring(0, eolIndex).trim(); sseBuffer = sseBuffer.substring(eolIndex + 1); if (line.startsWith('data: ')) { const data = line.substring(6).trim(); if (data === '[DONE]') { streamEnded = true; break; } try { const json = JSON.parse(data); const deltaContent = json.choices?.[0]?.delta?.content; if (deltaContent) { let currentChunk = deltaContent; let processedChunkForThisDelta = ''; while (currentChunk.length > 0) { if (isInsideThinkBlock) { const endThinkTagIndex = currentChunk.indexOf(''); if (endThinkTagIndex !== -1) { // Found the end tag. Content after it is actual. processedChunkForThisDelta += currentChunk.substring(endThinkTagIndex + ''.length); isInsideThinkBlock = false; currentChunk = ''; // Current delta's relevant part processed } else { // Still inside think block, discard this part of the chunk currentChunk = ''; // Discard and move to next delta if any } } else { const startThinkTagIndex = currentChunk.indexOf(''); if (startThinkTagIndex !== -1) { // Found a start tag. Content before it is actual. processedChunkForThisDelta += currentChunk.substring(0, startThinkTagIndex); isInsideThinkBlock = true; // The rest of currentChunk starts after currentChunk = currentChunk.substring(startThinkTagIndex + ''.length); // This remaining part of currentChunk will be re-evaluated in the next iteration // of this inner while loop, now with isInsideThinkBlock = true. } else { // No think tags, entire chunk is actual content processedChunkForThisDelta += currentChunk; currentChunk = ''; } } } if (processedChunkForThisDelta.length > 0) { rawGeneratedCode += processedChunkForThisDelta; // The htmlBlockStarted logic might need re-evaluation. // For now, assume content is part of the HTML stream. if (!htmlBlockStarted) { // Try to detect '```html' or start accumulating if it appears const marker = "```html"; let tempAccumulated = rawGeneratedCode; // Use the cleaned rawGeneratedCode const markerIndex = tempAccumulated.indexOf(marker); if (markerIndex !== -1) { htmlBlockStarted = true; let codeStartIndex = markerIndex + marker.length; if (tempAccumulated[codeStartIndex] === '\n') { codeStartIndex++; } rawGeneratedCode = tempAccumulated.substring(codeStartIndex); } else if (tempAccumulated.trim().startsWith("= previewUpdateInterval)) { // Strip markdown for preview if still present at this stage let previewCode = rawGeneratedCode; // Use the cleaned rawGeneratedCode if (previewCode.includes("```html")) { previewCode = previewCode.substring(previewCode.indexOf("```html") + 7).trimStart(); } if (previewCode.endsWith("```")) { previewCode = previewCode.substring(0, previewCode.length - 3).trimEnd(); } updateLivePreviewInGrid(variationIndex, previewCode, true); lastPreviewUpdateTime[variationIndex] = now; } } } } } catch (e) { console.warn(`[Var ${variationIndex + 1}] JSON parse error in stream:`, e, "Problematic Line:", line, "Data:", data); } } } if (streamEnded) break; // Exit outer loop if [DONE] was processed } console.log(`[Variation ${variationIndex + 1}] Streaming finished. Raw content length: ${rawGeneratedCode.length}`); let finalExtractedHtml = null; let processingErrorMessage = null; // Process the rawGeneratedCode after stream ends // This needs to handle potential markdown ```html ... ``` blocks let tempHtml = rawGeneratedCode.trim(); const htmlMarker = "```html"; const markerIndex = tempHtml.indexOf(htmlMarker); if (markerIndex !== -1) { htmlBlockStarted = true; // Confirm block started if marker found tempHtml = tempHtml.substring(markerIndex + htmlMarker.length).trimStart(); } // Remove trailing ``` if present if (tempHtml.endsWith("```")) { tempHtml = tempHtml.substring(0, tempHtml.length - 3).trimEnd(); } // If no ```html was found, but it looks like html, use it directly if (!htmlBlockStarted && (tempHtml.startsWith(" 0) { finalExtractedHtml = tempHtml; const minLength = 20; const hasStructuralTag = /]*>|]*>|]*>/i.test(finalExtractedHtml); const hasAnyTag = /<[a-zA-Z][^>]*>/.test(finalExtractedHtml); if (finalExtractedHtml.length >= minLength && (hasStructuralTag || hasAnyTag)) { success = true; console.log(`[Variation ${variationIndex + 1}] HTML validation SUCCEEDED.`); } else { success = false; console.warn("[Variation " + (variationIndex + 1) + "] HTML validation FAILED (length: " + finalExtractedHtml.length + ", structural: " + hasStructuralTag + ", anyTag: " + hasAnyTag + "). Review content manually."); processingErrorMessage = "// Warning: Generated content did not pass basic HTML validation."; if (!finalExtractedHtml) finalExtractedHtml = processingErrorMessage; // Ensure some content for display } } else if (tempHtml.length > 0 && !htmlBlockStarted) { // Content received but not identified as HTML block processingErrorMessage = `// Warning: Content received, but 'Written by Novita AI' or similar marker not found, or content doesn't look like HTML. Length: ${tempHtml.length}`; console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`); finalExtractedHtml = tempHtml; // Display what was received success = false; // Treat as not fully successful if not identified as clean HTML } else { processingErrorMessage = `// Error: No meaningful content generated or 'Written by Novita AI' marker not found.`; console.warn(`[Variation ${variationIndex + 1}] ${processingErrorMessage}`); finalExtractedHtml = processingErrorMessage; // Show error message success = false; } if (success && finalExtractedHtml) { let processedHtml = finalExtractedHtml; const requiredHeadContent = ` blocks (that are not JSON-LD or other non-executable script types). If no functional