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, '
', 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