liberalmind-v1.0 / agent.js
liberalusa's picture
Update agent.js
1f77fff verified
console.log("Script execution started.");
// --- Sidebar Functionality ---
// (Ваш оригинальный код для Sidebar остается без изменений)
let activeFolderElement = null; // DOM element of the currently active folder for new file creation
let activeSidebarFileId = null; // ID of the file whose content is loaded into fileContentForAI
const sidebarFileContents = new Map(); // Stores content of files in sidebar: fileId -> content
let folderIdCounter = 0;
let fileIdCounter = 0;
function initializeSidebar() {
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebar-toggle');
const sidebarTitle = document.getElementById('sidebar-title');
const newFolderBtn = document.getElementById('new-folder-btn');
const newFileBtn = document.getElementById('new-file-btn');
if (sidebarToggle) {
sidebarToggle.addEventListener('click', function() {
sidebar.classList.toggle('sidebar-collapsed');
const icon = sidebarToggle.querySelector('.material-icons-round');
if (sidebar.classList.contains('sidebar-collapsed')) {
icon.textContent = 'chevron_right';
if(sidebarTitle) sidebarTitle.style.display = 'none';
document.querySelectorAll('.folder-name, .file-name, #sidebar-controls, .folder-toggle-icon, .folder-actions').forEach(el => el.style.display = 'none');
} else {
icon.textContent = 'chevron_left';
if(sidebarTitle) sidebarTitle.style.display = 'inline';
document.querySelectorAll('.folder-name, .file-name, #sidebar-controls, .folder-toggle-icon, .folder-actions').forEach(el => el.style.display = 'inline');
document.querySelectorAll('.folder.expanded .folder-toggle-icon').forEach(icon => icon.textContent = 'expand_less');
document.querySelectorAll('.folder:not(.expanded) .folder-toggle-icon').forEach(icon => icon.textContent = 'expand_more');
}
});
}
const sidebarFoldersContainer = document.getElementById('sidebar-folders');
if (sidebarFoldersContainer) {
sidebarFoldersContainer.addEventListener('click', function(event) {
const header = event.target.closest('.folder-header');
if (header) {
const folder = header.parentElement;
const icon = header.querySelector('.folder-toggle-icon');
const isToggleButton = event.target.closest('.folder-toggle-icon');
const isActionButton = event.target.closest('.folder-actions');
if (!isActionButton) {
folder.classList.toggle('expanded');
if (icon) {
icon.textContent = folder.classList.contains('expanded') ? 'expand_less' : 'expand_more';
}
}
if (!isToggleButton && !isActionButton) {
if (activeFolderElement && activeFolderElement !== folder) {
activeFolderElement.classList.remove('active-folder');
}
activeFolderElement = folder;
activeFolderElement.classList.add('active-folder');
console.log("Active folder set to:", activeFolderElement.id || activeFolderElement.querySelector('.folder-name')?.textContent || 'Unknown Folder');
}
}
});
} else {
console.warn("Sidebar folders container not found for event delegation.");
document.querySelectorAll('.folder-header').forEach(header => {
header.addEventListener('click', function(event) {
if (event.target.closest('.folder-actions') || event.target.closest('.folder-toggle-icon')) return;
const folder = this.parentElement;
folder.classList.toggle('expanded');
const icon = this.querySelector('.folder-toggle-icon');
if (icon) {
icon.textContent = folder.classList.contains('expanded') ? 'expand_less' : 'expand_more';
}
if (activeFolderElement && activeFolderElement !== folder) {
activeFolderElement.classList.remove('active-folder');
}
activeFolderElement = folder;
activeFolderElement.classList.add('active-folder');
console.log("Active folder set to:", activeFolderElement.id || activeFolderElement.querySelector('.folder-name')?.textContent || 'Unknown Folder');
});
});
document.querySelectorAll('.folder-toggle-icon').forEach(icon => {
icon.addEventListener('click', function() {
const folder = this.closest('.folder');
if (folder) {
folder.classList.toggle('expanded');
this.textContent = folder.classList.contains('expanded') ? 'expand_less' : 'expand_more';
}
});
});
}
document.querySelectorAll('.file').forEach(file => {
attachFileClickListener(file);
});
if (newFolderBtn) newFolderBtn.addEventListener('click', createNewFolder);
if (newFileBtn) newFileBtn.addEventListener('click', createNewFileInActiveFolder);
}
function attachFileClickListener(fileElement) {
fileElement.addEventListener('click', function() {
document.querySelectorAll('.file').forEach(f => f.classList.remove('active'));
this.classList.add('active');
activeSidebarFileId = this.dataset.fileId;
fileContentForAI = sidebarFileContents.get(activeSidebarFileId) || "";
const fileName = this.dataset.fileName || 'Selected File';
if(mainInput) {
mainInput.value = `File: ${fileName}\nTask: Summarize this file.`;
mainInput.placeholder = `Task for file: ${fileName}...`;
}
if (fileInfoEl) fileInfoEl.style.display = 'none';
if (urlInfoEl) urlInfoEl.style.display = 'none';
console.log(`Active file set: ${activeSidebarFileId}, content loaded for AI.`);
if (fileInput) fileInput.value = null;
if (videoStreamPanel && videoStreamPanel.classList.contains('active')) {
document.querySelector('.tab-button[data-tab="output"]')?.click();
}
});
}
function createNewFolder() {
const folderName = prompt("Enter new folder name:", "New Folder");
if (!folderName || folderName.trim() === "") return;
folderIdCounter++;
const newFolderId = `folder-dynamic-${folderIdCounter}`;
const sanitizedFolderName = DOMPurify.sanitize(folderName);
const folderDiv = document.createElement('div');
folderDiv.className = 'folder';
folderDiv.id = newFolderId;
const headerDiv = document.createElement('div');
headerDiv.className = 'folder-header';
headerDiv.innerHTML = `
<span class="folder-icon material-icons-round">folder</span>
<span class="folder-name">${sanitizedFolderName}</span>
<span class="folder-toggle-icon material-icons-round">expand_more</span>
<span class="folder-actions" style="/* display:none; */">
</span>
`;
const contentDiv = document.createElement('div');
contentDiv.className = 'folder-content';
folderDiv.appendChild(headerDiv);
folderDiv.appendChild(contentDiv);
const sidebarFoldersContainer = document.getElementById('sidebar-folders');
const generalUploadsFolder = document.getElementById('uploads-dropzone');
if (sidebarFoldersContainer) {
if (generalUploadsFolder && generalUploadsFolder.parentElement === sidebarFoldersContainer) {
sidebarFoldersContainer.insertBefore(folderDiv, generalUploadsFolder);
} else {
sidebarFoldersContainer.appendChild(folderDiv);
}
} else {
const mainSidebar = document.getElementById('sidebar');
if (generalUploadsFolder && mainSidebar) {
mainSidebar.insertBefore(folderDiv, generalUploadsFolder);
} else if (mainSidebar) {
mainSidebar.appendChild(folderDiv);
} else {
console.error("Cannot add folder: Neither #sidebar-folders nor #sidebar found.");
return;
}
}
if (activeFolderElement) activeFolderElement.classList.remove('active-folder');
activeFolderElement = folderDiv;
activeFolderElement.classList.add('active-folder');
activeFolderElement.classList.add('expanded');
const newIcon = headerDiv.querySelector('.folder-toggle-icon');
if (newIcon) newIcon.textContent = 'expand_less';
console.log(`Folder "${sanitizedFolderName}" created and set as active.`);
const mainSidebar = document.getElementById('sidebar');
if (mainSidebar && mainSidebar.classList.contains('sidebar-collapsed')) {
document.querySelectorAll(`#${newFolderId} .folder-name, #${newFolderId} .folder-toggle-icon, #${newFolderId} .folder-actions`).forEach(el => el.style.display = 'none');
}
}
function createNewFileInActiveFolder() {
if (!activeFolderElement) {
console.log("No active folder selected. Trying to find a default or first folder.");
activeFolderElement = document.getElementById('uploads-dropzone') ||
document.querySelector('#sidebar-folders .folder');
if (activeFolderElement) {
if (!activeFolderElement.classList.contains('active-folder')) {
if(document.querySelector('.folder.active-folder')) document.querySelector('.folder.active-folder').classList.remove('active-folder');
activeFolderElement.classList.add('active-folder');
}
console.log("Using folder:", activeFolderElement.id || activeFolderElement.querySelector('.folder-name')?.textContent);
} else {
alert("No folder available (active, default, or first found) to add a file to. Please create a folder first.");
return;
}
}
if (!activeFolderElement.classList.contains('expanded')) {
activeFolderElement.classList.add('expanded');
const icon = activeFolderElement.querySelector('.folder-toggle-icon');
if (icon) icon.textContent = 'expand_less';
console.log("Expanded target folder:", activeFolderElement.id || activeFolderElement.querySelector('.folder-name')?.textContent);
}
const fileName = prompt("Enter new file name (e.g., script.js, notes.txt):", "new_file.txt");
if (!fileName || fileName.trim() === "") return;
fileIdCounter++;
const newFileId = `file-dynamic-${fileIdCounter}`;
const sanitizedFileName = DOMPurify.sanitize(fileName);
const fileDiv = document.createElement('div');
fileDiv.className = 'file';
fileDiv.dataset.fileId = newFileId;
fileDiv.dataset.fileName = sanitizedFileName;
fileDiv.innerHTML = `
<span class="file-icon material-icons-round">description</span>
<span class="file-name">${sanitizedFileName}</span>
`;
const folderContent = activeFolderElement.querySelector('.folder-content');
if (folderContent) {
folderContent.appendChild(fileDiv);
} else {
console.error("Could not find .folder-content in active folder:", activeFolderElement);
activeFolderElement.appendChild(fileDiv);
}
sidebarFileContents.set(newFileId, "");
attachFileClickListener(fileDiv);
const mainSidebar = document.getElementById('sidebar');
if (mainSidebar && mainSidebar.classList.contains('sidebar-collapsed')) {
fileDiv.querySelector('.file-name').style.display = 'none';
}
fileDiv.click();
const activeFolderName = activeFolderElement.querySelector('.folder-name')?.textContent || activeFolderElement.id;
console.log(`File "${fileName}" created in folder "${activeFolderName}" with ID ${newFileId}.`);
}
// --- Module Imports ---
let GoogleGenerativeAI, marked, DOMPurify;
try {
const gaModule = await import('https://esm.sh/@google/generative-ai');
GoogleGenerativeAI = gaModule.GoogleGenerativeAI;
const markedModule = await import('https://esm.sh/marked');
marked = markedModule.marked;
const domPurifyModule = await import('https://esm.sh/dompurify');
DOMPurify = domPurifyModule.default;
if (!GoogleGenerativeAI || !marked || !DOMPurify || typeof Chart === 'undefined' || typeof pdfjsLib === 'undefined' || typeof Babel === 'undefined') {
let missing = [];
if(!GoogleGenerativeAI) missing.push("GoogleGenerativeAI");
if(!marked) missing.push("marked");
if(!DOMPurify) missing.push("DOMPurify");
if(typeof Chart === 'undefined') missing.push("Chart.js");
if(typeof pdfjsLib === 'undefined') missing.push("pdf.js");
if(typeof Babel === 'undefined') missing.push("Babel");
if (missing.length > 0) throw new Error(`Critical modules failed to load: ${missing.join(', ')}.`);
}
console.log("Core & external libraries seem available.");
} catch (error) {
console.error("Fatal Error: Could not load core dependencies:", error);
const errorMsgEl = document.getElementById('init-error-message') || { textContent: "" };
const sdkErrorOverlayEl = document.getElementById('sdk-error-overlay') || { classList: { add: () => {} }};
errorMsgEl.textContent = `Failed to load critical libraries: ${error.message}. Check network, console, and ensure all script tags are correct.`;
sdkErrorOverlayEl.classList.add('visible');
throw error;
}
// --- UI Element References ---
const codeInputArea = document.getElementById('code-input-area');
const mainInput = document.getElementById('main-input');
const sendBtn = document.getElementById('send-btn');
const researchBtn = document.getElementById('research-btn');
const stopBtn = document.getElementById('stop-btn');
const uploadBtnLabel = document.getElementById('upload-btn-label');
const fileInput = document.getElementById('file-input');
const fileInfoEl = document.getElementById('file-info');
const fileNameEl = document.getElementById('file-name');
const urlInfoEl = document.getElementById('url-info');
const urlInfoTextEl = document.getElementById('url-info-text');
const fetchUrlBtn = document.getElementById('fetch-url-btn');
const aiOverlay = document.getElementById('ai-overlay');
const aiStatusText = document.getElementById('ai-status-text');
const initStatusText = document.getElementById('init-status-text');
const sdkErrorOverlay = document.getElementById('sdk-error-overlay');
const reloadButton = document.getElementById('reload-button');
const outputFrame = document.getElementById('output-frame');
const fullCodeContent = document.getElementById('full-code-content');
const researchProgressEl = document.getElementById('research-progress');
const languageSelector = document.getElementById('language-selector');
const tabButtons = document.querySelectorAll('.tab-button');
const videoStreamPanel = document.getElementById('video-stream-panel');
const videoElement = document.getElementById('video-element');
const videoCanvas = document.getElementById('video-canvas');
const startStreamBtn = document.getElementById('start-stream-btn');
const stopStreamBtn = document.getElementById('stop-stream-btn');
const aiPlanContainer = document.getElementById('ai-plan-container');
const aiPlanContent = document.getElementById('ai-plan-content');
// *** NEW: Terminal UI Element References ***
const terminalPanel = document.getElementById('terminal-panel');
const terminalOutput = document.getElementById('terminal-output');
const terminalInput = document.getElementById('terminal-input');
const terminalRunBtn = document.getElementById('terminal-run-btn');
// --- Application State ---
let genAI;
let model;
let generationInProgress = false;
let stopGenerationFlag = false;
let generatedCode = '';
let fileContentForAI = "";
let pyodide;
let pyodideLoadingPromise = null;
let currentLanguage = "HTML/CSS/JS (Web Page)";
window.currentAiChart = null;
let screenStream = null;
let currentScreenFrameDataUrl = "";
// *** NEW: Chat History for Conversational Context ***
let chatHistory = []; // Stores message objects: { role: "user"|"model", parts: [{text: "..."}] }
const MAX_HISTORY_ITEMS = 20; // Max number of messages (user + model) to keep. Adjust as needed.
// --- Configuration Constants ---
const API_KEY = "AIzaSyBCURZf72ZWwMN2SHZ7XCQoNgExV4WMX8E"; // Замените вашим реальным ключом
const MODEL_NAME = "gemini-2.0-flash-thinking-exp-1219";
const CORS_PROXY_URL_PREFIX = "https://api.allorigins.win/raw?url=";
// --- Solver Hyperparameters --- (Ваши оригинальные параметры)
const solverParams = {
initial_gen_temp_single: 1.0, refine_temp: 0.8, verify_temp: 0.2, synthesis_temp: 0.85,
topP: 0.98, topK: 80, max_retries: 3,
max_initial_tokens: 100000, max_critique_tokens: 100000, max_refine_tokens: 100000,
max_synthesis_tokens: 100000, max_reasoning_tokens: 100000,
depth_focus_max: 0.95, creativity_focus_max: 0.90, analytical_rigor_max: 0.98,
alternative_exploration_max: 0.95, efficiency_focus_max: 0.90,
depth_focus_simple: 0.65, creativity_focus_simple: 0.70, analytical_rigor_simple: 0.75,
alternative_exploration_simple: 0.60, efficiency_focus_simple: 0.50,
};
const SUPPORTED_LANGUAGES = [
"HTML/CSS/JS (Web Page)", "JavaScript (Standalone)", "TypeScript",
"React Component", "Next.js Page (Client-Side)", "Python (Pyodide)",
"Java (Code Display)", "Go (Code Display)", "Node.js (Code Display)",
"Flutter (Dart Code Display)", "Unity (C# Script Display)", "Data Analysis (from text/file/URL/screen)"
];
const DATA_ANALYSIS_LANG = "Data Analysis (from text/file/URL/screen)";
// --- Helper Functions ---
function showError(message) {
console.error("showError called:", message);
const errorMsgEl = document.getElementById('init-error-message');
if (errorMsgEl) errorMsgEl.textContent = message;
if (sdkErrorOverlay) sdkErrorOverlay.classList.add('visible');
else alert("A critical error occurred: " + message);
}
function updateButtonStates(isGenerating) {
if (sendBtn) sendBtn.disabled = isGenerating;
if (researchBtn) researchBtn.disabled = isGenerating;
if (fetchUrlBtn) fetchUrlBtn.disabled = isGenerating;
if (fileInput) fileInput.disabled = isGenerating;
if (uploadBtnLabel) {
uploadBtnLabel.style.cursor = isGenerating ? 'not-allowed' : 'pointer';
uploadBtnLabel.style.opacity = isGenerating ? 0.5 : 1;
}
if (startStreamBtn) startStreamBtn.disabled = isGenerating || screenStream !== null;
if (stopBtn) stopBtn.style.display = isGenerating ? 'block' : 'none';
if (!isGenerating && stopBtn) stopBtn.disabled = false;
const newFolderBtn = document.getElementById('new-folder-btn');
const newFileBtn = document.getElementById('new-file-btn');
if(newFolderBtn) newFolderBtn.disabled = isGenerating;
if(newFileBtn) newFileBtn.disabled = isGenerating;
// Disable terminal input during generation
if (terminalInput) terminalInput.disabled = isGenerating;
if (terminalRunBtn) terminalRunBtn.disabled = isGenerating;
}
function showAiOverlay(visible, statusText = "Thinking...") {
if (!aiOverlay || !aiStatusText || !researchProgressEl) {
console.warn("AI Overlay elements not found.");
return;
}
aiStatusText.textContent = statusText;
aiOverlay.classList.toggle('visible', visible);
const isResearching = statusText.toLowerCase().includes("researching");
const showProgressAttr = researchProgressEl.dataset.showProgress === "true";
const showResearchProgress = visible && isResearching && showProgressAttr;
researchProgressEl.classList.toggle('hidden', !showResearchProgress);
}
function clearInputSourceInfo(preserveActiveFile = false) {
if (fileInfoEl) fileInfoEl.style.display = 'none';
if (fileNameEl) fileNameEl.textContent = '';
if (urlInfoEl) urlInfoEl.style.display = 'none';
if (urlInfoTextEl) urlInfoTextEl.textContent = '';
if (!preserveActiveFile) {
fileContentForAI = "";
activeSidebarFileId = null;
document.querySelectorAll('.file.active').forEach(f => f.classList.remove('active'));
if(mainInput && languageSelector) {
mainInput.placeholder = languageSelector.value === DATA_ANALYSIS_LANG ?
`Describe task for content (file, URL, screen)...` :
`Enter task for ${languageSelector.value.split('(')[0].trim()}...`;
}
}
currentScreenFrameDataUrl = "";
if (fileInput) fileInput.value = null;
}
// *** NEW: Function to trim chat history ***
function trimChatHistory() {
if (chatHistory.length > MAX_HISTORY_ITEMS) {
chatHistory = chatHistory.slice(chatHistory.length - MAX_HISTORY_ITEMS);
console.log("Chat history trimmed to last", MAX_HISTORY_ITEMS, "items.");
}
}
// --- Initialization ---
async function initializeApp() {
console.log("initializeApp started");
try {
initializeSidebar();
if(initStatusText) initStatusText.textContent = "Initializing UI...";
setupLanguageSelector();
setupEventListeners();
initializeTerminal(); // *** NEW: Initialize Terminal ***
clearInputSourceInfo();
if(initStatusText) initStatusText.textContent = "Initializing Gemini...";
if (!GoogleGenerativeAI) throw new Error("GoogleGenerativeAI SDK is not loaded.");
genAI = new GoogleGenerativeAI(API_KEY);
model = genAI.getGenerativeModel({ model: MODEL_NAME });
console.log(`Gemini model ${MODEL_NAME} initialized.`);
if(codeInputArea) codeInputArea.disabled = false;
if(mainInput) mainInput.disabled = false;
updateButtonStates(false);
if(mainInput) mainInput.placeholder = `Enter task, paste URL, use screen share, upload file, or select a file from workspace...`;
if(codeInputArea) codeInputArea.placeholder = "Results will appear here...";
if(initStatusText) initStatusText.textContent = "Gemini Ready. Loading Pyodide (background)...";
pyodideLoadingPromise = loadPyodideInstance().then(() => {
if(initStatusText) {
initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide (background)...", "Ready (Gemini, Pyodide)");
console.log("Pyodide loaded successfully in background.");
appendToTerminalOutput("Pyodide loaded and ready.", "success");
}
}).catch(err => {
console.warn("Pyodide background load failed:", err);
appendToTerminalOutput(`Pyodide background load failed: ${err.message}`, "error");
if(initStatusText) {
initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide (background)...", "Ready (Gemini; Pyodide failed)");
}
});
} catch (error) {
console.error("Initialization error in initializeApp:", error);
showError(`Failed to initialize application: ${error.message}. Ensure API Key is correct and model name exists.`);
}
}
function setupLanguageSelector() {
if(!languageSelector) return;
languageSelector.innerHTML = '';
SUPPORTED_LANGUAGES.forEach(lang => {
const option = document.createElement('option');
option.value = lang; option.textContent = lang;
languageSelector.appendChild(option);
});
const defaultLang = DATA_ANALYSIS_LANG;
languageSelector.value = defaultLang;
currentLanguage = defaultLang;
handleLanguageChange({target: languageSelector});
}
async function loadPyodideInstance() {
if (pyodideLoadingPromise && !pyodide) { // If a promise exists but pyodide not yet set, return the existing promise
console.log("Pyodide loading already in progress, returning existing promise.");
return pyodideLoadingPromise;
}
if (pyodide) return Promise.resolve(pyodide); // Already loaded
if (typeof loadPyodide === "undefined") throw new Error("Pyodide main script not loaded.");
console.log("Attempting to load Pyodide...");
appendToTerminalOutput("Loading Pyodide core...", "info");
pyodideLoadingPromise = (async () => { // Wrap in an IIFE to handle errors and set pyodide
try {
const inst = await loadPyodide({});
console.log("Pyodide core loaded. Loading micropip...");
appendToTerminalOutput("Pyodide core loaded. Loading micropip...", "info");
await inst.loadPackage(["micropip"]);
console.log("Micropip loaded.");
appendToTerminalOutput("Micropip loaded into Pyodide.", "info");
pyodide = inst; // Set the global pyodide instance
console.log("Pyodide instance initialized successfully.");
return pyodide;
} catch (error) {
console.error("Pyodide loading error:", error);
appendToTerminalOutput(`Pyodide loading error: ${error.message}`, "error");
pyodide = null; // Ensure pyodide is null on failure
// pyodideLoadingPromise will be rejected by this throw
throw error;
}
})();
return pyodideLoadingPromise;
}
// --- Event Listeners Setup ---
function setupEventListeners() {
console.log("Setting up event listeners.");
if(reloadButton) reloadButton.addEventListener('click', () => window.location.reload());
if(tabButtons) {
const allTabButtons = document.querySelectorAll('.tab-button'); // Re-query to include terminal
allTabButtons.forEach(button => {
button.addEventListener('click', () => {
allTabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
const tabName = button.dataset.tab;
document.querySelectorAll('.tab-panel').forEach(panel => {
panel.classList.add('hidden');
panel.classList.remove('active');
});
const activePanel = document.getElementById(`${tabName}-tab`) || document.getElementById(`${tabName}-panel`);
if (activePanel) {
activePanel.classList.remove('hidden');
activePanel.classList.add('active');
} else {
console.warn(`Panel for tab ${tabName} not found.`);
}
// Special handling for video stream panel (if it has a different ID pattern)
if (tabName === 'video' && videoStreamPanel) {
videoStreamPanel.classList.remove('hidden');
videoStreamPanel.classList.add('active');
} else if (videoStreamPanel && tabName !== 'video') {
videoStreamPanel.classList.add('hidden');
videoStreamPanel.classList.remove('active');
}
handleLanguageChange({target: languageSelector}); // Might not be needed for terminal tab
});
});
document.querySelector('.tab-button[data-tab="output"]')?.click(); // Default to output
}
if(fileInput) fileInput.addEventListener('change', handleFileUploadToSidebar);
if(fetchUrlBtn) fetchUrlBtn.addEventListener('click', handleFetchUrlAsInputSource);
if(languageSelector) languageSelector.addEventListener('change', handleLanguageChange);
if(sendBtn) sendBtn.addEventListener('click', handleSendRequestWithPlan);
if(researchBtn) {
researchBtn.addEventListener('click', () => {
const activeFileEl = activeSidebarFileId ? document.querySelector(`.file[data-file-id="${activeSidebarFileId}"]`) : null;
const fileNameHint = activeFileEl ? `Topic related to file: ${activeFileEl.dataset.fileName}` : "";
let query = mainInput.value.trim() || fileNameHint;
if (!query && fileContentForAI) {
query = fileContentForAI.substring(0, 100) + (fileContentForAI.length > 100 ? "..." : "");
}
else if (!query && activeSidebarFileId && sidebarFileContents.has(activeSidebarFileId)){
const fileInfo = document.querySelector(`.file[data-file-id="${activeSidebarFileId}"]`);
query = `Research content of file: ${fileInfo ? fileInfo.dataset.fileName : 'selected file'}`;
}
if (query) {
performResearchWithPlan(query);
} else {
alert("Please enter a topic or URL, select a file, provide content via upload, or use screen share to provide context for research.");
}
});
}
if(mainInput) {
mainInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
const screenSharePanelActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
if (isValidURL(mainInput.value.trim()) && !screenSharePanelActive && !activeSidebarFileId && !fileContentForAI) {
handleFetchUrlAsInputSource();
} else {
handleSendRequestWithPlan();
}
}
});
}
if(stopBtn) stopBtn.addEventListener('click', handleStopGeneration);
if(startStreamBtn) startStreamBtn.addEventListener('click', startScreenStream);
if(stopStreamBtn) stopStreamBtn.addEventListener('click', stopScreenStream);
}
// --- Terminal Functionality ---
function initializeTerminal() {
if (!terminalInput || !terminalRunBtn || !terminalOutput) {
console.warn("Terminal UI elements not found. Terminal functionality will be disabled.");
return;
}
terminalInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
processTerminalInput();
}
});
terminalRunBtn.addEventListener('click', processTerminalInput);
console.log("Terminal initialized.");
}
function processTerminalInput() {
if (!terminalInput || generationInProgress) return;
const commandString = terminalInput.value.trim();
if (commandString === "") return;
appendToTerminalOutput(commandString, "command-echo"); // Echo command
handleTerminalCommand(commandString);
terminalInput.value = ""; // Clear input
}
function appendToTerminalOutput(message, type = "info") {
if (!terminalOutput) return;
const messageElement = document.createElement('div');
messageElement.textContent = message;
messageElement.className = type; // e.g., 'info', 'error', 'success', 'command-echo'
// Basic styling if classes are not fully defined in CSS
if (type === "error") messageElement.style.color = "#ff8888";
else if (type === "success") messageElement.style.color = "#88ff88";
else if (type === "command-echo") messageElement.style.color = "#88aaff";
else messageElement.style.color = "#cccccc"; // Default info
terminalOutput.appendChild(messageElement);
terminalOutput.scrollTop = terminalOutput.scrollHeight; // Scroll to bottom
}
async function handleTerminalCommand(commandString) {
const [command, ...args] = commandString.trim().toLowerCase().split(/\s+/);
switch (command) {
case "help":
appendToTerminalOutput(
`Available commands:
help - Shows this help message.
clear - Clears the terminal output.
pip install <package_name> - Installs a Python package using micropip (for Pyodide).
Example: pip install numpy
micropip install <package_name> - Alias for 'pip install'.
pyodide status - Shows the current status of Pyodide.
pyodide list-packages - Lists currently installed Python packages in Pyodide.
Note: This terminal primarily manages Python packages for the Pyodide environment.
"Downloading" other languages or complex JS frameworks is not supported directly.
For JS libraries, include them in your HTML/JS code as needed.`, "info");
break;
case "clear":
if (terminalOutput) terminalOutput.innerHTML = "";
break;
case "pip":
case "micropip":
if (args[0] === "install" && args[1]) {
await installPythonPackage(args[1]);
} else {
appendToTerminalOutput("Usage: pip install <package_name>", "error");
}
break;
case "pyodide":
if (args[0] === "status") {
await getPyodideStatus();
} else if (args[0] === "list-packages") {
await listPyodidePackages();
} else {
appendToTerminalOutput("Unknown pyodide command. Try 'pyodide status' or 'pyodide list-packages'.", "error");
}
break;
default:
appendToTerminalOutput(`Unknown command: ${command}`, "error");
}
}
async function installPythonPackage(packageName) {
if (!pyodide && !pyodideLoadingPromise) {
appendToTerminalOutput("Pyodide not loaded. Cannot install. Please wait for initialization.", "error");
return;
}
if (pyodideLoadingPromise && !pyodide) { // Check if it's loading but not yet ready
appendToTerminalOutput("Pyodide is loading, please wait before installing packages...", "info");
try {
await pyodideLoadingPromise; // Wait for the ongoing load
if (!pyodide) throw new Error("Pyodide still not available after waiting.");
} catch (e) {
appendToTerminalOutput(`Pyodide failed to load. Cannot install ${packageName}. Error: ${e.message}`, "error");
return;
}
}
if (!pyodide) { // Should be caught above, but as a safeguard
appendToTerminalOutput("Pyodide is not available. Cannot install.", "error");
return;
}
if (!pyodide.micropip) {
appendToTerminalOutput("Micropip is not available in this Pyodide instance. Cannot install packages.", "error");
return;
}
appendToTerminalOutput(`Installing ${packageName} via micropip...`, "info");
try {
await pyodide.micropip.install(packageName);
appendToTerminalOutput(`Package ${packageName} installed successfully.`, "success");
} catch (error) {
console.error(`Error installing ${packageName}:`, error);
appendToTerminalOutput(`Error installing ${packageName}: ${error.message}`, "error");
}
}
async function getPyodideStatus() {
if (pyodide) {
let status = "Pyodide Ready.";
if (pyodide.micropip) {
status += " Micropip available.";
try {
const micropipVersion = await pyodide.runPythonAsync(`
import micropip
micropip.__version__
`);
if (micropipVersion) status += ` Version: ${micropipVersion}`;
} catch (e) {
status += " (Could not get micropip version)";
}
} else {
status += " Micropip NOT available (loadPackage('micropip') might have failed).";
}
appendToTerminalOutput(status, "info");
} else if (pyodideLoadingPromise) {
appendToTerminalOutput("Pyodide is currently loading...", "info");
try {
await pyodideLoadingPromise;
if(pyodide) appendToTerminalOutput("Pyodide finished loading. Run 'pyodide status' again.", "success");
else appendToTerminalOutput("Pyodide loading finished, but instance is not available.", "error");
} catch(e){
appendToTerminalOutput(`Pyodide loading failed: ${e.message}`, "error");
}
} else {
appendToTerminalOutput("Pyodide not loaded and no loading attempt in progress. (Check console for errors during initial load)", "error");
}
}
async function listPyodidePackages() {
if (!pyodide && !pyodideLoadingPromise) {
appendToTerminalOutput("Pyodide not loaded. Cannot list packages.", "error");
return;
}
if (pyodideLoadingPromise && !pyodide) {
appendToTerminalOutput("Pyodide is loading, please wait...", "info");
try {
await pyodideLoadingPromise;
if (!pyodide) throw new Error("Pyodide still not available after waiting.");
} catch (e) {
appendToTerminalOutput(`Pyodide failed to load. Cannot list packages. Error: ${e.message}`, "error");
return;
}
}
if (!pyodide) {
appendToTerminalOutput("Pyodide is not available. Cannot list packages.", "error");
return;
}
if (pyodide.loadedPackages) {
const packages = Object.keys(pyodide.loadedPackages).sort();
if (packages.length > 0) {
appendToTerminalOutput("Installed Pyodide packages (via pyodide.loadedPackages):\n" + packages.join("\n"), "info");
} else {
appendToTerminalOutput("No packages reported by pyodide.loadedPackages.", "info");
}
// For micropip installed packages, it's harder to list them directly without specific micropip commands.
// pyodide.loadedPackages might not show all micropip-installed ones if they don't register in a specific way.
// A more robust way would be to try importing them, but that's too much for a simple list.
appendToTerminalOutput("Note: `pyodide list-packages` shows core loaded packages. Packages installed via `pip install` during this session might also be available for import.", "info");
} else {
appendToTerminalOutput("pyodide.loadedPackages not found. Cannot list packages.", "error");
}
}
// --- File, URL Processing ---
// ... (Ваш существующий код для File, URL Processing без изменений) ...
async function handleFileUploadToSidebar(event) {
const file = event.target.files[0];
if (!file) return;
let targetFolderElement = activeFolderElement;
if (!targetFolderElement) {
targetFolderElement = document.getElementById('uploads-dropzone') ||
document.querySelector('#sidebar-folders .folder') ||
document.querySelector('.folder');
if (targetFolderElement) {
if (!targetFolderElement.classList.contains('active-folder')) {
if(document.querySelector('.folder.active-folder')) document.querySelector('.folder.active-folder').classList.remove('active-folder');
targetFolderElement.classList.add('active-folder');
}
activeFolderElement = targetFolderElement;
console.log("No active folder for upload, using default/first folder:", activeFolderElement.id || activeFolderElement.querySelector('.folder-name')?.textContent);
} else {
alert("Error: No folder available to upload the file to. Please create a folder first.");
clearInputSourceInfo();
if (fileInput) fileInput.value = null;
return;
}
}
if (!targetFolderElement.classList.contains('expanded')) {
targetFolderElement.classList.add('expanded');
const icon = targetFolderElement.querySelector('.folder-toggle-icon');
if(icon) icon.textContent = 'expand_less';
}
if(fileNameEl) fileNameEl.textContent = file.name;
if(fileInfoEl) fileInfoEl.style.display = 'flex';
showAiOverlay(true, `Processing ${file.name} for sidebar...`);
try {
let textContent = "";
const fileType = file.name.split('.').pop().toLowerCase();
const MAX_FILE_SIZE_MB = 50;
if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
throw new Error(`File size (${(file.size / 1024 / 1024).toFixed(1)}MB) exceeds the ${MAX_FILE_SIZE_MB}MB limit.`);
}
if (fileType === 'pdf') textContent = await readPdfFile(file);
else if (['txt', 'md', 'csv', 'js', 'py', 'html', 'css', 'json', 'xml', 'java', 'c', 'cpp', 'cs', 'go', 'rb', 'php', 'swift', 'kt', 'dart', 'rs'].includes(fileType)) textContent = await readGenericTextFile(file);
else if (fileType === 'docx') {
if (typeof mammoth !== 'undefined') textContent = await readDocxFileWithMammoth(file);
else { textContent = `(mammoth.js not loaded for .docx: ${file.name})`; console.warn("Enable mammoth.js for .docx.");}
} else if (fileType === 'doc') { textContent = `(.doc not supported: ${file.name})`; console.warn(".doc not supported.");
} else { textContent = `(Unsupported readable type .${fileType}: ${file.name}, uploaded as reference)`; console.warn(`Unsupported type .${fileType} for direct reading, file added by name only.`);}
const MAX_CONTENT_LENGTH = 500000;
if (textContent.length > MAX_CONTENT_LENGTH) {
console.warn(`Truncating file content from ${textContent.length} to ${MAX_CONTENT_LENGTH} characters.`);
textContent = textContent.substring(0, MAX_CONTENT_LENGTH);
}
fileIdCounter++;
const newFileId = `file-uploaded-${fileIdCounter}`;
const sanitizedFileName = DOMPurify.sanitize(file.name);
const fileDiv = document.createElement('div');
fileDiv.className = 'file';
fileDiv.dataset.fileId = newFileId;
fileDiv.dataset.fileName = sanitizedFileName;
fileDiv.innerHTML = `<span class="file-icon material-icons-round">description</span><span class="file-name">${sanitizedFileName}</span>`;
const folderContent = targetFolderElement.querySelector('.folder-content');
if (folderContent) folderContent.appendChild(fileDiv);
else { console.warn("Target folder missing '.folder-content' div", targetFolderElement); targetFolderElement.appendChild(fileDiv); }
sidebarFileContents.set(newFileId, textContent);
attachFileClickListener(fileDiv);
const mainSidebar = document.getElementById('sidebar');
if (mainSidebar && mainSidebar.classList.contains('sidebar-collapsed')) {
fileDiv.querySelector('.file-name').style.display = 'none';
}
fileDiv.click();
if(languageSelector && languageSelector.value !== DATA_ANALYSIS_LANG) {
languageSelector.value = DATA_ANALYSIS_LANG;
handleLanguageChange({target: languageSelector});
}
if(mainInput) {
mainInput.placeholder = `Task for uploaded file: ${sanitizedFileName}...`;
mainInput.value = `File: ${sanitizedFileName}\nTask: Analyze this uploaded file.`;
mainInput.focus();
}
const activeFolderName = targetFolderElement.querySelector('.folder-name')?.textContent || targetFolderElement.id;
console.log(`File "${file.name}" uploaded to folder "${activeFolderName}" and set as active.`);
} catch (error) {
console.error("File processing error for sidebar:", error);
if(mainInput) mainInput.value = `Error processing ${file.name}: ${error.message}.`;
alert(`Error processing file: ${error.message}`);
clearInputSourceInfo();
} finally {
showAiOverlay(false);
if (fileInfoEl) fileInfoEl.style.display = 'none';
if (fileInput) fileInput.value = null;
}
}
function readGenericTextFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = event => resolve(event.target.result); reader.onerror = error => reject(error); reader.readAsText(file); }); }
async function readPdfFile(file) {
if (typeof pdfjsLib === 'undefined') throw new Error("pdf.js library is not loaded.");
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
if (typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';
console.log("Set pdf.js worker source to CDN fallback.");
} else {
throw new Error("pdf.js library defined but GlobalWorkerOptions not accessible.");
}
}
const arrayBuffer = await file.arrayBuffer(); const pdf = await pdfjsLib.getDocument({data: arrayBuffer}).promise;
let fullText = ""; for (let i = 1; i <= pdf.numPages; i++) { try {const page = await pdf.getPage(i); const textContent = await page.getTextContent(); fullText += textContent.items.map(item => item.str).join(" ") + "\n";} catch (pageError){ console.error(`Error processing PDF page ${i}:`, pageError); fullText += `\n[Error reading page ${i}]\n`;}} return fullText;
}
async function readDocxFileWithMammoth(file) { if (typeof mammoth === 'undefined') throw new Error("mammoth.js not loaded."); const arrayBuffer = await file.arrayBuffer(); const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer }); return result.value; }
function isValidURL(string) { try { const url = new URL(string); return url.protocol === "http:" || url.protocol === "https:"; } catch (_) { return false; }}
async function fetchUrlContent(url) {
if (!isValidURL(url)) {
throw new Error("Invalid URL. Please enter a full URL including http:// or https://");
}
console.log(`Attempting to fetch content from URL: ${url}`);
const response = await fetch(`${CORS_PROXY_URL_PREFIX}${encodeURIComponent(url)}`);
if (!response.ok) {
const errorText = await response.text();
console.error("Fetch error response from proxy/target for URL:", url, "Status:", response.status, "Response Text:", errorText.substring(0, 500));
let detail = `Proxy or target server issue. Details: ${errorText.substring(0, 150)}`;
if (response.status === 403) detail = "Access forbidden by the target server or proxy.";
else if (response.status === 404) detail = "Content not found at the target URL.";
else if (response.status >= 500 && response.status <= 504) {
detail = `Proxy server error (${response.status}). The proxy service (allorigins.win) might be down or unable to reach the target.`;
}
throw new Error(`Fetch failed for ${url}: ${response.status} ${response.statusText}. ${detail}`);
}
const html = await response.text();
const doc = new DOMParser().parseFromString(html, "text/html");
const article = doc.querySelector('article, main, [role="main"], .main, #main, .content, #content, .entry-content, .post-content, body');
let text = "";
if (article) {
article.querySelectorAll('script, style, noscript, iframe, header, footer, nav, aside, .noprint, .nomobile, .sr-only, [aria-hidden="true"]').forEach(el => el.remove());
text = article.textContent || "";
} else {
console.warn("Could not find main content element using common selectors for URL:", url, ". Falling back to body text content.");
text = doc.body ? doc.body.textContent || "" : "";
}
const MAX_URL_CONTENT_LENGTH = 500000;
let processedText = text.replace(/\s\s+/g, ' ').trim();
if (processedText.length > MAX_URL_CONTENT_LENGTH) {
console.warn(`Truncating URL content from ${processedText.length} to ${MAX_URL_CONTENT_LENGTH} characters for URL: ${url}`);
processedText = processedText.substring(0, MAX_URL_CONTENT_LENGTH);
}
if (!processedText.trim()) {
console.warn("Extracted text from URL is empty or only whitespace:", url);
}
return processedText;
}
async function handleFetchUrlAsInputSource() {
const url = mainInput.value.trim();
if (!isValidURL(url)) {
alert("Invalid URL. Please enter a full URL including http:// or https://");
return;
}
clearInputSourceInfo();
if(urlInfoTextEl) urlInfoTextEl.textContent = url.length > 50 ? url.substring(0,47)+"..." : url;
if(urlInfoEl) urlInfoEl.style.display = 'flex';
showAiOverlay(true, `Fetching ${url.substring(0,50)} to use as input...`);
try {
fileContentForAI = await fetchUrlContent(url);
activeSidebarFileId = null;
document.querySelectorAll('.file.active').forEach(f => f.classList.remove('active'));
if (!fileContentForAI.trim()) {
console.warn(`Fetched content from ${url} is empty. User may need to specify task for the URL itself.`);
}
if(languageSelector) languageSelector.value = DATA_ANALYSIS_LANG;
currentLanguage = DATA_ANALYSIS_LANG;
if(mainInput) {
mainInput.placeholder = `Task for content from URL: ${url.substring(0,30)}...`;
mainInput.value = `URL: ${url}\nContent Preview (first 100 chars): ${fileContentForAI.substring(0,100)}${fileContentForAI.length > 100 ? "..." : ""}\nTask: Summarize this content.`;
mainInput.focus();
}
console.log(`Successfully fetched content from URL: ${url} to use as input. Length: ${fileContentForAI.length}`);
} catch (error) {
console.error("URL fetch for input source error:", error);
if(mainInput) mainInput.value = `Error fetching ${url} as input: ${error.message}.`;
fileContentForAI = "";
alert(`URL Input Error: ${error.message}. This could be due to the CORS proxy, the target website, network issues, or content extraction errors.`);
clearInputSourceInfo();
} finally {
showAiOverlay(false);
}
}
// --- Screen Share Functions ---
// ... (Ваш существующий код для Screen Share без изменений) ...
async function startScreenStream() {
if (screenStream) return;
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
try {
screenStream = await navigator.mediaDevices.getDisplayMedia({ video: { cursor: "always" }, audio: false });
if(videoElement) {
videoElement.srcObject = screenStream;
videoElement.onloadedmetadata = () => videoElement.play().catch(e => console.error("Video play failed:", e));
videoElement.onerror = (e) => { console.error("Video element error:", e); alert("Error loading video stream."); stopScreenStream(false); };
}
if(startStreamBtn) startStreamBtn.classList.add('hidden');
if(stopStreamBtn) stopStreamBtn.classList.remove('hidden');
console.log("Screen sharing started.");
screenStream.getVideoTracks()[0].onended = () => { console.log("Screen sharing stopped by user/browser."); stopScreenStream(false); };
updateButtonStates(generationInProgress);
} catch (err) {
console.error("Screen share access error: ", err);
if (err.name === 'NotAllowedError') alert("Screen share permission denied.");
else if (err.name === 'NotFoundError') alert("No screen/window selected or unavailable.");
else if (err.name === 'NotReadableError') alert("Screen source cannot be read (hardware/OS permission issue).");
else alert("Screen share error: " + err.message);
if (screenStream) stopScreenStream(false);
updateButtonStates(generationInProgress);
}
} else {
alert("Screen Sharing (getDisplayMedia API) is not supported.");
updateButtonStates(generationInProgress);
}
}
function stopScreenStream(logMessage = true) {
if (screenStream) {
screenStream.getTracks().forEach(track => track.stop());
if(videoElement) videoElement.srcObject = null;
screenStream = null;
currentScreenFrameDataUrl = "";
if (logMessage) console.log("Screen sharing stopped.");
}
if(startStreamBtn) startStreamBtn.classList.remove('hidden');
if(stopStreamBtn) stopStreamBtn.classList.add('hidden');
updateButtonStates(generationInProgress);
}
async function captureScreenFrameAsDataURL() {
if (!screenStream || !videoElement || !videoElement.srcObject || videoElement.readyState < videoElement.HAVE_METADATA) {
console.warn("Screen stream not active or video not ready for capture.", {stream: !!screenStream, video: !!videoElement, srcObj: !!videoElement?.srcObject, readyState: videoElement?.readyState});
if (!screenStream) alert("Screen sharing not active.");
else alert("Video stream not ready (readyState < HAVE_METADATA).");
return null;
}
if (videoElement.videoWidth === 0 || videoElement.videoHeight === 0) {
console.warn("Video element has zero dimensions.");
alert("Cannot capture screen: Video dimensions are zero.");
return null;
}
if (videoCanvas && videoElement) {
videoCanvas.width = videoElement.videoWidth;
videoCanvas.height = videoElement.videoHeight;
const ctx = videoCanvas.getContext('2d');
if (!ctx) { console.error("Failed to get 2D context from canvas."); alert("Failed to get drawing context."); return null; }
try {
ctx.drawImage(videoElement, 0, 0, videoCanvas.width, videoCanvas.height);
return videoCanvas.toDataURL('image/jpeg', 0.85);
} catch (e) {
if (e.name === 'SecurityError') { console.error("Canvas Tainted:", e); alert("Security Error: Cannot capture. Content shared (e.g., protected video) prevents it."); }
else { console.error("Canvas to Data URL error:", e); alert(`Capture error: ${e.message}`);}
return null;
}
} else { console.error("Video canvas or element not found for capture."); return null; }
}
// --- Language/Code Handling ---
// ... (Ваш существующий код для Language/Code Handling без изменений) ...
function handleLanguageChange(e) {
currentLanguage = e.target.value;
const screenSharePanelActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
const terminalPanelActive = terminalPanel && terminalPanel.classList.contains('active');
if(mainInput) {
if (terminalPanelActive) {
// Maybe do nothing with mainInput placeholder if terminal is active, or set a generic one
// mainInput.placeholder = "Main input not active while terminal is focused.";
} else if (activeSidebarFileId && sidebarFileContents.has(activeSidebarFileId)) {
const activeFileEl = document.querySelector(`.file[data-file-id="${activeSidebarFileId}"]`);
if (activeFileEl) mainInput.placeholder = `Task for file: ${activeFileEl.dataset.fileName}...`;
else mainInput.placeholder = `Task for selected file...`;
} else if (screenSharePanelActive) {
mainInput.placeholder = "Describe task for the screen content...";
} else if (currentLanguage === DATA_ANALYSIS_LANG) {
mainInput.placeholder = `Describe task for content (file, URL, screen)...`;
} else {
mainInput.placeholder = `Enter task for ${currentLanguage.split('(')[0].trim()}...`;
}
}
console.log("Language changed to:", currentLanguage);
if (currentLanguage === "Python (Pyodide)" && !pyodide && !pyodideLoadingPromise) {
if(initStatusText && !initStatusText.textContent.includes("Pyodide")) {
initStatusText.textContent = initStatusText.textContent.replace("Ready", "Loading Pyodide...");
console.log("Initiating Pyodide load for Python.");
loadPyodideInstance().then(() => {
if(initStatusText) initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide...", "Pyodide Ready.");
appendToTerminalOutput("Pyodide loaded on demand for Python language selection.", "success");
}).catch(err => {
if(initStatusText) initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide...", "Pyodide Failed.");
appendToTerminalOutput(`Pyodide on-demand load failed: ${err.message}`, "error");
});
} else if (pyodideLoadingPromise) console.log("Pyodide is already loading.");
}
}
function handleStopGeneration() { if (generationInProgress) { stopGenerationFlag = true; if(stopBtn) stopBtn.disabled = true; if(aiStatusText) aiStatusText.textContent = "Stopping..."; console.log("Stop generation requested.");}}
async function runPythonCode(pythonCode) {
if (!pyodide) {
if(initStatusText && !initStatusText.textContent.includes("Pyodide failed") && !initStatusText.textContent.includes("Pyodide Ready")) initStatusText.textContent += " Waiting for Pyodide...";
console.log("Python execution waiting for Pyodide...");
appendToTerminalOutput("Python execution: Pyodide not ready, attempting to load...", "info");
try {
await (pyodideLoadingPromise || loadPyodideInstance());
if (!pyodide) throw new Error("Pyodide failed to load previously.");
if(initStatusText && initStatusText.textContent.includes("Waiting for Pyodide")) initStatusText.textContent = initStatusText.textContent.replace(" Waiting for Pyodide...", " Pyodide ready. Retrying Python.");
console.log("Pyodide ready, retrying Python execution.");
appendToTerminalOutput("Pyodide now ready, retrying Python execution.", "success");
} catch (error) {
console.error("Pyodide not available for Python execution:", error);
appendToTerminalOutput(`Pyodide not available for Python execution: ${error.message}`, "error");
if(outputFrame) outputFrame.srcdoc = `<div style='color:red;padding:20px;font-family:monospace;white-space:pre-wrap;background:#fff0f0;border:1px solid red;border-radius:8px;'><h3>Pyodide Error</h3><p>Pyodide (Python runtime) not available/failed to load.</p><p><b>Cannot run Python code.</b></p><p>Error: ${DOMPurify.sanitize(error.message)}</p></div>`;
return;
}
}
if (!outputFrame) { console.error("Output frame not found for Python."); return; }
outputFrame.srcdoc = `<div style="padding:20px;font-family:monospace;white-space:pre-wrap;background:#f0f0f0;border-radius:8px;">🐍 Running Python...</div>`;
let pyStartTime = performance.now();
try {
console.log("Attempting to load packages from Python imports...");
appendToTerminalOutput("Python: Loading packages from imports (if any)...", "info");
await pyodide.loadPackagesFromImports(pythonCode,
{ messageCallback: (msg) => { console.log("Pyodide pkg msg:", msg); appendToTerminalOutput(`Pyodide pkg: ${msg}`, "info");} ,
errorCallback: (err) => { console.warn("Pyodide pkg err:", err); appendToTerminalOutput(`Pyodide pkg err: ${err}`, "error");} }
);
console.log("Packages loaded. Executing Python code...");
appendToTerminalOutput("Python: Executing code...", "info");
let capturedOutput = "";
pyodide.setStdout({ batched: (str) => capturedOutput += str + "\n" });
pyodide.setStderr({ batched: (str) => capturedOutput += `<span style='color:red;'>Error: ${str}</span>\n` });
await pyodide.runPythonAsync(pythonCode);
let pyEndTime = performance.now();
console.log(`Python exec finished in ${(pyEndTime - pyStartTime).toFixed(2)} ms.`);
appendToTerminalOutput(`Python execution finished in ${(pyEndTime - pyStartTime).toFixed(2)} ms.`, "success");
const outputHTML = capturedOutput.trim() ? DOMPurify.sanitize(capturedOutput) : "<span style='color:grey;'>Python code executed. No explicit print output.</span>";
outputFrame.srcdoc = `<div style="padding:20px;font-family:monospace;white-space:pre-wrap;background:#f9f9f9;border:1px solid #eee;border-radius:8px;"><h3>🐍 Python Output</h3>${outputHTML}</div>`;
} catch (error) {
let pyEndTime = performance.now();
console.error("Python execution error:", error);
appendToTerminalOutput(`Python execution error: ${error.message}`, "error");
const errorMessage = error.message ? error.message.replace(/</g, "&lt;").replace(/>/g, "&gt;") : "Unknown Python error";
outputFrame.srcdoc = `<div style='padding:20px;font-family:monospace;white-space:pre-wrap;color:red;background:#fff0f0;border:1px solid red;border-radius:8px;'><h3>🐍 Python Error</h3><p>Failed after ${(pyEndTime - pyStartTime).toFixed(2)} ms.</p><pre>${DOMPurify.sanitize(errorMessage)}</pre></div>`;
}
}
function transpileToRunnableJS(code, lang) {
// (Ваша оригинальная логика транспайлера без изменений)
let displayLang = lang;
if (lang === "TypeScript" && typeof Babel !== 'undefined') {
try {
const { code: jsCode } = Babel.transform(code, { presets: ['typescript'], filename: 'component.tsx' });
code = `// Transpiled from TypeScript:\n${jsCode}`;
displayLang = "JavaScript (from TypeScript)";
} catch (e) {
console.warn("TypeScript transpilation failed:", e);
code = `// TypeScript (transpilation failed: ${e.message})\n${code}`;
displayLang = "TypeScript (Transpilation Error)";
}
} else if ((lang === "React Component" || lang === "Next.js Page (Client-Side)") && typeof Babel !== 'undefined') {
try {
const { code: jsCode } = Babel.transform(code, { presets: ['react', 'env'], filename: 'component.jsx' });
code = `// Transpiled from JSX (React/Next.js):\n${jsCode}\n\n/* Note: Basic transpile. Full React/Next.js functionality requires proper build env. */`;
displayLang = "JavaScript (from JSX)";
} catch (e) {
console.warn("JSX transpilation failed:", e);
code = `// JSX (transpilation failed: ${e.message})\n${code}`;
displayLang = "JSX (Transpilation Error)";
}
} else if (lang === "JavaScript (Standalone)") {
displayLang = "JavaScript (Standalone)";
} else {
displayLang = lang.replace(' (Code Display)','');
code = `// Displaying code for: ${displayLang}\n// Live execution/transpilation not supported.\n\n${code}`;
}
return `<div style="padding:15px; background-color: #282c34; color: #abb2bf; font-family: 'Courier New', Courier, monospace; font-size: 0.9em; border-radius: 8px; overflow:auto; height:100%;box-sizing:border-box;"><h4 style="color:#61afef; margin-top:0; margin-bottom:10px; border-bottom: 1px solid #3f434a; padding-bottom:5px;">${displayLang} Preview</h4><pre style="margin:0; white-space:pre-wrap; word-break:break-all;">${DOMPurify.sanitize(code)}</pre></div>`;
}
// --- Main Actions (Send Request, Research) with Planning Step ---
// ... (Ваш существующий код для Main Actions без изменений) ...
async function generatePlan(taskDescription, contextSummary, taskType) {
if (!aiPlanContainer || !aiPlanContent) {
console.warn("AI Plan UI elements not found.");
return "PLAN_UI_MISSING";
}
aiPlanContainer.classList.remove('hidden');
aiPlanContent.innerHTML = "<i>🧠 Generating strategic plan...</i>";
showAiOverlay(true, `Phase 1: Generating Plan for ${taskType}...`);
console.log(`Generating plan for task type: ${taskType}`);
const focusParams = solverParams;
const planPrompt = `You are an expert AI Strategist and Project Planner. Your goal is to devise a concise, actionable, step-by-step plan to fulfill the user's request effectively and efficiently. **User's Core Request:** "${taskDescription}" **Provided Context Summary:** ${contextSummary ? `"${contextSummary.substring(0, 300)}..."` : "No specific file/URL/screen context provided beyond the task description."}
Task Type: ${taskType}
Your Task: Generate a step-by-step plan in Markdown bullet points. Critically analyze the request and context. Outline the key stages required to achieve the user's goal with high quality. Strive for creativity and thoroughness in the plan itself. Consider potential challenges and build steps to address them. Think about how to add extra value or detail based on the request.
Guiding Principles (Emphasize these in your planning process):
- Depth of Analysis (${focusParams.depth_focus_max}): Thoroughly understand the requirements, nuances, and potential challenges. Go beyond the surface level. Think about edge cases and complexities.
- Analytical Rigor (${focusParams.analytical_rigor_max}): Ensure the plan is logical, complete, and addresses the core objectives accurately. Anticipate verification needs.
- Creative Problem Solving & Detail (${focusParams.creativity_focus_max}): Consider innovative or highly effective approaches. Plan steps that encourage adding creative details, examples, or richer features as requested by the user. How can the output be made better than a basic fulfillment?
- Exploration of Alternatives (${focusParams.alternative_exploration_max}): Briefly consider different high-level strategies or structures if relevant, selecting the most promising. Justify complex choices implicitly through the plan steps.
- Efficiency (${focusParams.efficiency_focus_max}): Aim for a plan that leads to a solution without unnecessary complexity or redundancy, while still achieving depth and quality.
Output Format Instructions:
- Begin the plan directly with Markdown bullet points (\`- \` or \`* \`).
- NO conversational preamble, introduction, or concluding remarks. Just the plan.
- Keep the plan concise and focused on major steps (typically 4-8 steps, allowing for more detail).
- Each step should be a clear action or analysis phase. Incorporate considerations for detail and creativity within the steps where appropriate.
Example Structure (more detailed):
- Deeply analyze user request: identify core function, desired style, specific features (e.g., "interactive elements", "data visualization type"), and constraints.
- Outline main structure/sections (e.g., HTML layout, Python class structure, report sections).
- Design and develop core logic/component A (consider alternative implementations briefly). Add detailed comments.
- Implement feature B with creative elements (e.g., unique CSS animations, varied data examples, insightful analysis points).
- Integrate components, ensuring smooth interaction and data flow.
- Refine UI/UX, add polishing details, enhance visual appeal or clarity.
- Perform final verification against requirements, check edge cases, ensure robustness.
- Format output according to specifications (e.g., self-contained HTML, clean Python script, structured Markdown).
Generate the detailed, creative, and actionable plan now.`;
const generationConfig = {
temperature: solverParams.initial_gen_temp_single,
topP: solverParams.topP, topK: solverParams.topK,
maxOutputTokens: solverParams.max_initial_tokens,
};
try {
console.log("Sending plan generation request to Gemini:", { promptLength: planPrompt.length, config: generationConfig });
const result = await model.generateContent(planPrompt, generationConfig);
if (stopGenerationFlag) throw new Error("Plan generation stopped by user.");
const response = result?.response;
const planText = response?.text ? response.text() : "";
if (!planText.trim()) { console.error("Received empty plan from AI.", response); throw new Error("Received empty plan from AI.");}
console.log("Plan received:\n", planText);
if (aiPlanContent) {
if (typeof marked !== 'undefined' && typeof marked.parse === 'function') aiPlanContent.innerHTML = DOMPurify.sanitize(marked.parse(planText));
else aiPlanContent.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word;">${DOMPurify.sanitize(planText)}</pre>`;
}
return planText;
} catch (error) {
console.error(`Error generating plan for ${taskType}:`, error);
if (aiPlanContent) aiPlanContent.innerHTML = `<p style="color:red;font-weight:bold;">Error Generating Plan</p><p style="color:red;">${DOMPurify.sanitize(error.message)}</p>`;
throw new Error(`Plan generation failed: ${error.message}`);
}
}
async function handleSendRequestWithPlan() {
const taskDescription = mainInput ? mainInput.value.trim() : "";
let capturedScreenImageDataUrl = null;
const isScreenShareTabActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
let planText = "";
console.log("--- Starting New Request Cycle (Send) ---");
console.log("Task:", taskDescription);
if (!taskDescription && !fileContentForAI && !(isScreenShareTabActive && screenStream)) {
alert("Please enter a task, provide content (from sidebar, URL, upload), or activate screen share with a running stream.");
updateButtonStates(false);
if(aiPlanContainer) aiPlanContainer.classList.add('hidden');
return;
}
generationInProgress = true; stopGenerationFlag = false; updateButtonStates(true);
if(aiPlanContainer) aiPlanContainer.classList.remove('hidden');
if(aiPlanContent) aiPlanContent.innerHTML = "";
if(codeInputArea) codeInputArea.value = "";
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px; font-style:italic; color: #555;">Initiating request... waiting for plan.</p>`;
if(fullCodeContent) fullCodeContent.textContent = "";
if (window.currentAiChart) { window.currentAiChart.destroy(); window.currentAiChart = null; }
let currentRequestMode; let contextSummaryForPlan = "";
let mainRequestParts = [];
const historyForThisCall = [...chatHistory];
const currentUserMessage = { role: "user", parts: [{ text: taskDescription }] };
historyForThisCall.push(currentUserMessage);
chatHistory.push(currentUserMessage);
trimChatHistory();
try {
if (isScreenShareTabActive && screenStream) {
showAiOverlay(true, `Capturing screen frame...`);
capturedScreenImageDataUrl = await captureScreenFrameAsDataURL();
if (!capturedScreenImageDataUrl) throw new Error("Screen capture failed. Cannot proceed.");
console.log("Screen frame captured successfully.");
currentRequestMode = "screen_analysis";
contextSummaryForPlan = "Real-time screen capture provided.";
if (languageSelector && currentLanguage !== DATA_ANALYSIS_LANG) {
languageSelector.value = DATA_ANALYSIS_LANG; currentLanguage = DATA_ANALYSIS_LANG;
}
} else if (activeSidebarFileId || fileContentForAI || currentLanguage === DATA_ANALYSIS_LANG) {
const activeFileEl = activeSidebarFileId ? document.querySelector(`.file[data-file-id="${activeSidebarFileId}"]`) : null;
const fileNameHint = activeFileEl ? `File: ${activeFileEl.dataset.fileName}` : (fileContentForAI ? "Provided text/data content" : "General data analysis task");
currentRequestMode = "data_analysis";
contextSummaryForPlan = `${fileNameHint} (Content length: ${fileContentForAI.length} chars).`;
if (languageSelector && currentLanguage !== DATA_ANALYSIS_LANG && (activeSidebarFileId || fileContentForAI)) {
languageSelector.value = DATA_ANALYSIS_LANG; currentLanguage = DATA_ANALYSIS_LANG;
}
} else {
currentRequestMode = "code_generation";
contextSummaryForPlan = `Target Language: ${currentLanguage}. No specific file content provided; focus on the task description.`;
if (languageSelector && currentLanguage === DATA_ANALYSIS_LANG) {
const defaultCodeLang = SUPPORTED_LANGUAGES.find(l => l !== DATA_ANALYSIS_LANG) || "HTML/CSS/JS (Web Page)";
languageSelector.value = defaultCodeLang; currentLanguage = defaultCodeLang;
contextSummaryForPlan = `Target Language: ${currentLanguage}. No specific file content provided; focus on the task description.`;
}
}
console.log(`Determined Request Mode: ${currentRequestMode}`);
console.log(`Context for Plan: ${contextSummaryForPlan}`);
planText = await generatePlan(taskDescription, contextSummaryForPlan, currentRequestMode);
if (stopGenerationFlag) throw new Error("Stopped by user after plan generation.");
showAiOverlay(true, `Phase 2: Executing Plan for ${currentRequestMode}...`);
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px; font-style:italic; color: #333;">Plan received. Generating content based on plan...</p>`;
const focusParams = solverParams;
const chartCanvasHTML = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:10px;box-sizing:border-box;"><div style="width:95%;max-width:700px;height:95%;max-height:500px;"><canvas id="aiChart"></canvas></div></div>`;
let mainPromptText = "";
if (currentRequestMode === "screen_analysis") {
if (!capturedScreenImageDataUrl) throw new Error("Internal Error: Screen analysis mode but no image data captured.");
const base64ImageData = capturedScreenImageDataUrl.split(',')[1];
const mimeType = capturedScreenImageDataUrl.substring(capturedScreenImageDataUrl.indexOf(':') + 1, capturedScreenImageDataUrl.indexOf(';'));
mainPromptText = `You are an exceptionally perceptive AI Visual Analyst Assistant. Your purpose is to meticulously analyze the provided screen image and respond to the user's task with insight, detail, and clarity, adhering strictly to the generated plan. Go beyond simple descriptions; infer purpose and relationships where appropriate based on standard UI/UX conventions, but clearly state when making inferences.
User's Task/Question regarding this screen image: "${taskDescription}"
Generated Strategic Plan (Follow this precisely):\n--- PLAN START ---\n${planText}\n--- PLAN END ---
Your Core Directives: 1. Execute the Plan. 2. Image Focus & Detail. 3. Analytical Depth & Rigor (${focusParams.depth_focus_max}, ${focusParams.analytical_rigor_max}). 4. Infer Purpose (Carefully). 5. Creativity in Observation (${focusParams.creativity_focus_max}). 6. Clarity and Structure.
Output Format Instructions: Output ONLY the final analysis in Markdown format. Do NOT include any preamble, conversational text, apologies, or self-references. Start directly with the Markdown content.
Analyze the provided screen image now according to the plan and these directives.`;
mainRequestParts = [ { text: mainPromptText }, { inlineData: { mimeType: mimeType, data: base64ImageData } } ];
} else if (currentRequestMode === "data_analysis") {
mainPromptText = `You are a sophisticated AI Data Scientist and Content Analyst. Your objective is to analyze the provided text/data context based on the user's task, execute the provided plan, and generate the single most appropriate output format: a well-styled raw HTML Table, a detailed raw Chart.js JSON configuration object, or a comprehensive Markdown text analysis.
User's Task: "${taskDescription}"
Provided Content Context (approx. first ${fileContentForAI.length > 0 ? '5000 characters): """\n' + fileContentForAI.substring(0, 5000) + (fileContentForAI.length > 5000 ? '\n...[TRUNCATED]...' : '') + '\n"""' : "No explicit content/data. Analyze task itself or generate illustrative data."}
Generated Strategic Plan (Follow this precisely):\n--- PLAN START ---\n${planText}\n--- PLAN END ---
Core Directives & Quality Requirements: 1. Execute the Plan. 2. Analytical Depth & Insight (${focusParams.depth_focus_max}, ${focusParams.analytical_rigor_max}). 3. Output Format Versatility (Creativity: ${focusParams.creativity_focus_max}, Efficiency: ${focusParams.efficiency_focus_max}): HTML Table (inline CSS), Chart.js JSON ({"type":"chartjs","config":...}), or rich Markdown. 4. Alternative Exploration (${focusParams.alternative_exploration_max}). 5. Value Add.
CRITICAL Output Format Instructions: You MUST output ONLY ONE type: raw HTML Table, raw Chart.js JSON, OR raw Markdown. NO PREAMBLE OR EXPLANATIONS. If HTML, start with \`<div><style>...\` or \`<table>\`. If JSON, start with \`{\`. If Markdown, start directly. If insufficient data, default to Markdown with detailed explanation.
Generate your single, high-quality response now, following the plan and adhering strictly to these directives.`;
mainRequestParts = [{ text: mainPromptText }];
} else { // code_generation
let languageInstructions = "";
switch(currentLanguage){
case "HTML/CSS/JS (Web Page)": languageInstructions = `Generate a single, complete, self-contained HTML file. Embed CSS within \`<style>\` tags in the \`<head>\`. Embed JavaScript within \`<script>\` tags before the closing \`</body>\`. Ensure the code is functional, demonstrates the requested features robustly, and includes creative elements. Use modern HTML5, CSS3, and ES6+ JavaScript. Prioritize semantic HTML, accessibility (ARIA attributes, keyboard navigation considerations), and responsive design (using media queries or flexible layouts). Add detailed comments explaining architecture, complex logic, and CSS choices. Make it visually appealing and interactive. Incorporate diverse examples or variations requested by the user.`; break;
case "Python (Pyodide)": languageInstructions = `Generate Python code specifically for execution in a Pyodide environment. Use standard Python 3.9+ features. Leverage built-in libraries (math, random, collections, json, etc.) extensively. If external packages (numpy, pandas, matplotlib) are absolutely necessary and justified, structure the code assuming they might be loaded via \`micropip\` or are already present. Use \`print()\` for all user-facing output. Write clean, PEP 8 compliant, idiomatic Python. Include clear docstrings for functions/classes and inline comments for complex sections. Implement error handling (try-except blocks) where appropriate. Structure the code logically with functions or classes for reusability. Provide varied and illustrative examples within the code or comments if applicable.`; break;
case "JavaScript (Standalone)": languageInstructions = `Generate standalone JavaScript code (ES6+/ES2020+). Assume a modern browser environment by default, clearly state if Node.js is targeted or required. Avoid browser-specific APIs (like \`document\`, \`window\`) if aiming for cross-environment compatibility or Node.js. Implement robust error handling. Use modern features like async/await, classes, modules (if appropriate for structure, though output should be a single block). Add detailed JSDoc comments (\`/** ... */\`) for all functions, classes, and complex variables. Ensure code is well-structured, readable, and includes creative or detailed examples as per the task.`; break;
case "TypeScript": languageInstructions = `Generate high-quality TypeScript code (.ts). Utilize strong typing rigorously: provide explicit type annotations for all variables, function parameters, and return values. Define and use interfaces or type aliases for all non-trivial data structures. Leverage advanced TypeScript features like generics, mapped types, conditional types where appropriate to enhance type safety and code clarity. Adhere to TypeScript best practices and common style guides. Include comprehensive TSDoc comments. Implement classes or functions as appropriate. Ensure the code is well-organized, handles potential null/undefined values safely (strict null checks implied), and includes creative details or variations.`; break;
case "React Component": languageInstructions = `Generate a single, well-structured React functional component using JSX syntax (.jsx/.tsx). Import React and hooks (\`useState\`, \`useEffect\`, \`useContext\`, \`useRef\`, \`useCallback\`, \`useMemo\`) correctly and use them idiomatically. Implement state management logically. Handle side effects cleanly within \`useEffect\`. Write reusable and composable component code. Use TypeScript for prop types (define an \`interface Props\`) for robustness. Add detailed comments explaining the component's purpose, state, props, effects, and any complex logic. Incorporate creative UI elements, handle user interactions effectively, and pay attention to accessibility. Make it feature-rich based on the request.`; break;
case "Next.js Page (Client-Side)": languageInstructions = `Generate a Next.js page component, explicitly marked with \`'use client';\` at the top. Use React functional component syntax with Hooks effectively. Include necessary imports from React and Next.js (\`next/link\`, \`next/image\`, \`next/router\`, etc.) where appropriate. Structure the component logically for a page. Manage state and side effects correctly on the client. Add detailed comments and implement TypeScript prop types. Focus on building a complete and creative page section according to the user's task, considering client-side rendering implications. Handle loading and error states gracefully if data fetching is implied.`; break;
default: languageInstructions = `Generate high-quality, idiomatic code for ${currentLanguage.replace(' (Code Display)','').trim()}. Follow language-specific conventions and best practices rigorously. Ensure the code is clear, robust, well-commented (explaining the 'why' not just the 'what'), and thoroughly addresses all aspects of the user's task. Prioritize correctness, maintainability, and security. Include detailed examples or creative variations where applicable. Handle potential errors gracefully.`;
}
mainPromptText = `You are a World-Class AI Software Engineer and Architect specializing in ${currentLanguage}. Your mission is to generate exceptional, production-quality code that precisely fulfills the user's request, adheres strictly to the generated plan, and embodies the principles of clarity, robustness, efficiency, and creativity. Go significantly beyond a basic implementation; aim for excellence.
**User Task:** "${taskDescription}"
Target Language/Framework: ${currentLanguage}
Specific Language Instructions & Advanced Best Practices: ${languageInstructions}
Generated Strategic Plan (Follow this precisely and exhaustively):\n--- PLAN START ---\n${planText}\n--- PLAN END ---
Core Directives & Quality Requirements (Apply Maximum Emphasis - Strive for Perfection): 1. Execute Plan. 2. Code Quality/Correctness/Robustness (${focusParams.analytical_rigor_max}). 3. Depth/Completeness/Detail (${focusParams.depth_focus_max}). 4. Creativity/Innovation ("Wow Factor") (${focusParams.creativity_focus_max}). 5. Considered Alternatives (${focusParams.alternative_exploration_max}). 6. Efficiency/Optimization (${focusParams.efficiency_focus_max}). 7. Readability/Maintainability/Documentation.
CRITICAL Output Format Instructions: Output ONLY the raw code for ${currentLanguage}. NO MARKDOWN BACKTICKS. NO EXPLANATIONS, PREAMBLE, CHATTY TEXT, OR META-COMMENTS. Just the code.
Generate the exceptional ${currentLanguage} code now, adhering strictly to the plan and all directives.`;
mainRequestParts = [{ text: mainPromptText }];
}
const generationConfig = {
temperature: solverParams.synthesis_temp, topP: solverParams.topP,
topK: solverParams.topK, maxOutputTokens: solverParams.max_synthesis_tokens,
};
console.log(`Sending main ${currentRequestMode} request to Gemini via chat session with history length: ${historyForThisCall.length -1 } (excluding current prompt)`);
const chat = model.startChat({ history: historyForThisCall.slice(0, -1), generationConfig });
const result = await chat.sendMessage(mainRequestParts);
if (stopGenerationFlag) throw new Error("Stopped by user during content generation.");
const response = result?.response;
let rawRspTxt = "";
if (response && typeof response.text === 'function') rawRspTxt = response.text();
else if (response?.candidates?.[0]?.content?.parts?.[0]?.text) {
rawRspTxt = response.candidates[0].content.parts[0].text;
console.warn("Recovered text from candidate structure.");
} else {
throw new Error("Received no valid text content from Gemini response.");
}
if (!rawRspTxt.trim()) generatedCode = `// AI returned an empty response for the task: ${taskDescription}`;
else generatedCode = rawRspTxt.replace(/^```(?:[a-zA-Z0-9_ .-]+)?\s*\n?([\s\S]*?)\n?```$/gm, '$1').trim();
chatHistory.push({ role: "model", parts: [{ text: generatedCode }] });
trimChatHistory();
console.log("Processing and displaying results for mode:", currentRequestMode);
showAiOverlay(true, `Finalizing ${currentRequestMode} results...`);
if (currentRequestMode === "screen_analysis" || (currentRequestMode === "data_analysis" && !(generatedCode.startsWith("{") && generatedCode.endsWith("}")) && !generatedCode.toLowerCase().includes("<table"))) {
let htmlOutput = (typeof marked !== 'undefined' && typeof marked.parse === 'function') ? DOMPurify.sanitize(marked.parse(generatedCode)) : `<pre style="white-space: pre-wrap; word-wrap: break-word;">${DOMPurify.sanitize(generatedCode)}</pre>`;
if(outputFrame) outputFrame.srcdoc = `<div style="padding:20px;background:white;border-radius:8px;max-height:100%;overflow-y:auto;font-family: Inter, sans-serif; line-height:1.6;">${htmlOutput}</div>`;
if(codeInputArea) codeInputArea.value = generatedCode; if(fullCodeContent) fullCodeContent.textContent = generatedCode;
} else if (currentRequestMode === "data_analysis") {
let isChart = false, isTable = false, chartCfg = null;
if (generatedCode.startsWith("{") && generatedCode.endsWith("}")) {
try {
const potentialJson = JSON.parse(generatedCode);
if(potentialJson.type === "chartjs" && potentialJson.config?.type && potentialJson.config?.data) {
isChart = true; chartCfg = potentialJson.config; console.log("Detected Chart.js JSON output.");
} else console.warn("Response is JSON but not the expected Chart.js structure. Treating as Markdown.");
} catch(e) { console.warn("Response looked like JSON but failed to parse. Treating as Markdown:", e, generatedCode.substring(0,100));}
}
if (!isChart && (generatedCode.toLowerCase().trim().startsWith("<table") || (generatedCode.toLowerCase().trim().startsWith("<div") && generatedCode.includes("<table")))) {
isTable = true; console.log("Detected HTML Table output.");
}
if(isChart && outputFrame && chartCfg){
outputFrame.srcdoc = chartCanvasHTML;
outputFrame.onload = () => {
if (outputFrame.dataset.chartLoaded === 'true') return; outputFrame.dataset.chartLoaded = 'true';
try {
const chartCtx = outputFrame.contentWindow.document.getElementById('aiChart');
if(chartCtx) {
if(window.currentAiChart) window.currentAiChart.destroy();
if (outputFrame.contentWindow.Chart) window.currentAiChart = new outputFrame.contentWindow.Chart(chartCtx, chartCfg);
else {
const script = outputFrame.contentWindow.document.createElement('script'); script.src = "https://cdn.jsdelivr.net/npm/chart.js";
script.onload = () => { if (outputFrame.contentWindow.Chart) window.currentAiChart = new outputFrame.contentWindow.Chart(chartCtx, chartCfg); else throw new Error("Chart constructor not found after script load."); };
script.onerror = () => { if(outputFrame.contentDocument?.body) outputFrame.contentDocument.body.innerHTML=`<div style='padding:10px;color:red'>Chart Error: Failed to load Chart.js library.</div>`; throw new Error("Failed to load Chart.js script into iframe.");}
outputFrame.contentWindow.document.head.appendChild(script);
}
} else throw new Error("Chart canvas element ('aiChart') not found in iframe after loading.");
} catch(e) {
console.error("Chart rendering error:", e);
if(outputFrame.contentDocument?.body) outputFrame.contentDocument.body.innerHTML=`<div style='padding:10px;color:red'>Chart Rendering Error: ${DOMPurify.sanitize(e.message)}</div><h4 style='margin:10px;'>Generated Chart.js Config:</h4><pre style='font-size:0.8em;white-space:pre-wrap;word-break:break-all;background:#eee;padding:10px;border-radius:4px;'>${DOMPurify.sanitize(JSON.stringify(chartCfg,null,2))}</pre>`;
}
setTimeout(() => { outputFrame.dataset.chartLoaded = 'false'; }, 100);
};
if(codeInputArea) codeInputArea.value = JSON.stringify({ type: "chartjs", config: chartCfg }, null, 2);
if(fullCodeContent) fullCodeContent.textContent = codeInputArea.value;
} else if (isTable && outputFrame){
const sanitizedHtml = DOMPurify.sanitize(generatedCode,{ USE_PROFILES: {html: true}, ADD_TAGS: ['style', 'div'], ADD_ATTR: ['style'] });
outputFrame.srcdoc = `<div style="padding:10px;overflow:auto;background:white;border-radius:8px;">${sanitizedHtml}</div>`;
if(codeInputArea) codeInputArea.value=generatedCode; if(fullCodeContent) fullCodeContent.textContent=generatedCode;
} else {
let htmlOutput = (typeof marked !== 'undefined' && typeof marked.parse === 'function') ? DOMPurify.sanitize(marked.parse(generatedCode)) : `<pre style="white-space: pre-wrap; word-wrap: break-word;">${DOMPurify.sanitize(generatedCode)}</pre>`;
if(outputFrame) outputFrame.srcdoc = `<div style="padding:20px;background:white;border-radius:8px;max-height:100%;overflow-y:auto;font-family: Inter, sans-serif;line-height:1.6;">${htmlOutput}</div>`;
if(codeInputArea) codeInputArea.value=generatedCode; if(fullCodeContent) fullCodeContent.textContent=generatedCode;
}
} else { // code_generation
console.log(`Processing code generation result for: ${currentLanguage}`);
if(aiStatusText) aiStatusText.textContent = `Rendering ${currentLanguage} preview...`;
if(outputFrame) {
outputFrame.onload = null; outputFrame.dataset.chartLoaded = 'false';
switch (currentLanguage) {
case "HTML/CSS/JS (Web Page)": outputFrame.srcdoc = generatedCode; break;
case "Python (Pyodide)": await runPythonCode(generatedCode); break;
case "JavaScript (Standalone)": case "TypeScript": case "React Component": case "Next.js Page (Client-Side)":
outputFrame.srcdoc = transpileToRunnableJS(generatedCode, currentLanguage); break;
default: outputFrame.srcdoc = transpileToRunnableJS(generatedCode, currentLanguage);
}
}
if(codeInputArea) codeInputArea.value = generatedCode; if(fullCodeContent) fullCodeContent.textContent = generatedCode;
}
} catch (error) {
console.error("--- AI Request/Processing Error (Send) ---:", error);
const errTxt = stopGenerationFlag ? "Operation stopped by user." : `Error: ${error.message}`;
if(aiPlanContent && planText) aiPlanContent.innerHTML += `<p style="color:red; font-weight:bold; margin-top:15px;">Execution Failed!</p><p style="color:red;">${DOMPurify.sanitize(errTxt)}</p>`;
else if (aiPlanContent) aiPlanContent.innerHTML += `<p style="color:red; font-weight:bold; margin-top:15px;">Content Generation Failed (due to plan error)</p>`;
if(outputFrame) outputFrame.srcdoc = `<div style='color:red; padding:20px; font-family: Inter, sans-serif;'><h3>Request Failed</h3><p>${DOMPurify.sanitize(errTxt)}</p></div>`;
if(codeInputArea) codeInputArea.value += `\n\n--- ERROR ---\n${DOMPurify.sanitize(errTxt)}`;
if (error.message?.toLowerCase().includes("image input") || error.message?.toLowerCase().includes("multimodal")) {
const modelErrorNote = `<p style='color:orange;padding:0 20px 20px 20px; font-weight:bold;'>Note: Model '${MODEL_NAME}' might not support image analysis. Ensure you are using a multimodal model.</p>`;
if(outputFrame.contentDocument?.body) outputFrame.contentDocument.body.innerHTML += modelErrorNote; else outputFrame.srcdoc += modelErrorNote;
}
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === "user" && chatHistory[chatHistory.length - 1].parts[0].text === currentUserMessage.parts[0].text) {
chatHistory.pop();
console.log("Rolled back last user message from global history due to error before model response.");
}
} finally {
generationInProgress = false; stopGenerationFlag = false; updateButtonStates(false);
showAiOverlay(false); currentScreenFrameDataUrl = "";
console.log("--- Request Cycle Finished (Send) ---");
}
}
async function performResearchWithPlan(topic) {
if (!topic) { alert("Please provide a topic or URL to research."); return; }
if (generationInProgress) { alert("Another AI task is currently in progress. Please wait."); return; }
console.log(`--- Starting New Research Cycle ---`);
console.log(`Research Topic/URL: ${topic}`);
generationInProgress = true; stopGenerationFlag = false; updateButtonStates(true);
if(aiPlanContainer) aiPlanContainer.classList.remove('hidden');
if(aiPlanContent) aiPlanContent.innerHTML = "";
if(codeInputArea) codeInputArea.value = "";
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px; font-style:italic; color: #555;">Initiating research... waiting for plan.</p>`;
if(fullCodeContent) fullCodeContent.textContent = "";
if (window.currentAiChart) { window.currentAiChart.destroy(); window.currentAiChart = null; }
let planText = "";
let researchContextContent = "";
let contextSummaryForPlan = `Research topic: "${topic}". Aim for depth, accuracy, and comprehensive synthesis.`;
let userMessageForHistory = `Research topic: ${topic}`;
const historyForThisCall = [...chatHistory];
try {
if (isValidURL(topic)) {
showAiOverlay(true, `Fetching URL for research: ${topic.substring(0,50)}...`);
try {
researchContextContent = await fetchUrlContent(topic);
if (researchContextContent) {
contextSummaryForPlan = `Researching content from URL: "${topic}". Preview (first 300 chars): "${researchContextContent.substring(0,300)}..."`;
userMessageForHistory = `Research topic (URL processed): ${topic}\n\n--- Fetched Content for Context ---\n${researchContextContent.substring(0, 3000)}${researchContextContent.length > 3000 ? "..." : ""}\n--- End of Fetched Content ---`;
console.log(`Fetched content from ${topic} for research. Length: ${researchContextContent.length}`);
if(aiStatusText) aiStatusText.textContent = `Fetched content from URL. Generating research plan...`;
} else {
console.warn(`No content extracted from URL ${topic} for research. Proceeding with URL as topic name only.`);
contextSummaryForPlan = `Researching URL (as topic): "${topic}". Content could not be fetched or was empty.`;
userMessageForHistory = `Research topic (URL, no content fetched): ${topic}`;
}
} catch (e) {
console.error(`Failed to fetch URL content for research topic "${topic}": ${e.message}`);
alert(`Could not fetch content from URL ${topic} for research: ${e.message}. Proceeding with research based on URL as topic name only.`);
researchContextContent = "";
contextSummaryForPlan = `Researching URL (as topic): "${topic}". Fetch failed: ${e.message.substring(0,100)}.`;
userMessageForHistory = `Research topic (URL, fetch failed): ${topic}. Error: ${e.message.substring(0,100)}`;
}
showAiOverlay(false);
}
const currentUserMessage = { role: "user", parts: [{ text: userMessageForHistory }] };
historyForThisCall.push(currentUserMessage);
chatHistory.push(currentUserMessage);
trimChatHistory();
planText = await generatePlan(topic, contextSummaryForPlan, "research");
if (stopGenerationFlag) throw new Error("Research stopped by user after plan generation.");
showAiOverlay(true, `Phase 2: Executing Research Plan for: ${topic.substring(0, 50)}...`);
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px; font-style:italic; color: #333;">Plan received. Researching topic: "${DOMPurify.sanitize(topic)}"...</p>`;
const focusParams = solverParams;
const researchPrompt = `You are an expert AI Research Assistant and Knowledge Synthesizer with access to a vast knowledge base. Your task is to conduct meticulous, in-depth research on the given topic, rigorously following the generated plan, and present a comprehensive, accurate, well-structured, and insightful summary of findings in Markdown format.
Research Topic: "${topic}"
${researchContextContent ? `\n**Provided Content Context (from URL ${topic}):**\n"""\n${researchContextContent}\n"""\n` : ""}
Generated Strategic Plan (Follow this precisely and exhaustively):\n--- PLAN START ---\n${planText}\n--- PLAN END ---
Core Directives & Quality Requirements:
- Execute the Plan Faithfully: Adhere strictly to the research steps outlined in the plan. Ensure all facets mentioned in the plan are covered.
- Comprehensiveness, Depth & Nuance (${focusParams.depth_focus_max}): Gather information from diverse, reliable perspectives. Explore the topic deeply, covering key concepts, definitions, historical context, current state-of-the-art, methodologies, applications, debates, criticisms, and future trends as relevant and guided by the plan. Uncover non-obvious details.
- Accuracy, Verification & Rigor (${focusParams.analytical_rigor_max}): Prioritize factual accuracy above all. Cross-reference information where possible (conceptually). Synthesize information logically, coherently, and critically. Clearly distinguish established facts from theories, opinions, or speculations. Avoid making unsubstantiated claims.
- Synthesis, Structure & Clarity (${focusParams.efficiency_focus_max}): Do not merely list facts. Synthesize the information into a cohesive, well-organized narrative or structured report using clear Markdown (headings, subheadings, nested lists, bolding, italics, blockquotes). Ensure a logical flow and high readability. Define key terms.
- Exploration of Perspectives & Creativity (${focusParams.alternative_exploration_max}, ${focusParams.creativity_focus_max}): If the topic involves multiple viewpoints, theories, controversies, or approaches, represent them accurately and fairly. Present the information in an engaging and insightful manner.
- Further Reading (Contextual & General): Where appropriate, suggest general areas, key concepts, influential figures, or seminal types of works for further reading. Do NOT invent fake URLs, specific book titles, or specific paper citations. Focus on conceptual pointers that guide further exploration (e.g., "Further reading could explore behavioral economics principles," "Key figures in this field include...").
- Acknowledge Limitations & Ambiguities: If the topic is highly niche, information is genuinely scarce or contradictory, or there are significant unresolved questions, state this explicitly and clearly within the report. Do not invent information.
CRITICAL Output Format Instructions: Output ONLY the final research findings in rich Markdown format. NO PREAMBLE OR CONVERSATIONAL TEXT. Start directly with the Markdown content (e.g., a top-level heading like \`# Research on: ${topic}\`).
Conduct the deep research and generate the comprehensive Markdown report now, following the plan and all directives.`;
const generationConfig = {
temperature: solverParams.refine_temp, topP: solverParams.topP,
topK: solverParams.topK, maxOutputTokens: solverParams.max_refine_tokens,
};
console.log("Sending research request to Gemini via chat session with history length:", historyForThisCall.length -1);
const chat = model.startChat({ history: historyForThisCall.slice(0, -1), generationConfig });
const result = await chat.sendMessage([{ text: researchPrompt }]);
if (stopGenerationFlag) throw new Error("Research stopped by user during generation.");
const response = result?.response;
let researchResultText = "";
if (response && typeof response.text === 'function') researchResultText = response.text();
else if (response?.candidates?.[0]?.content?.parts?.[0]?.text) {
researchResultText = response.candidates[0].content.parts[0].text;
console.warn("Recovered research text from candidate structure for research.");
} else {
throw new Error("Received no valid text content from Gemini for research.");
}
if (!researchResultText.trim()) {
researchResultText = `*The AI returned an empty response for the research topic: "${topic}". Information might be unavailable, the topic might be too obscure, or the request could not be processed.*`;
}
const cleanedResearchText = researchResultText.replace(/^```(?:markdown)?\s*\n?([\s\S]*?)\n?```$/gm, '$1').trim();
chatHistory.push({ role: "model", parts: [{ text: cleanedResearchText }] });
trimChatHistory();
if(aiStatusText) aiStatusText.textContent = `Finalizing research results for: ${topic.substring(0, 50)}...`;
if(codeInputArea) codeInputArea.value = `# Research on: ${topic}\n\n${cleanedResearchText}`;
if(fullCodeContent) fullCodeContent.textContent = codeInputArea.value;
let htmlOutput = (typeof marked !== 'undefined' && typeof marked.parse === 'function') ? DOMPurify.sanitize(marked.parse(cleanedResearchText)) : `<pre style="white-space: pre-wrap; word-wrap: break-word;">${DOMPurify.sanitize(cleanedResearchText)}</pre>`;
if(outputFrame) {
outputFrame.onload = null; outputFrame.dataset.chartLoaded = 'false';
outputFrame.srcdoc = `<div style="padding:20px;background:white;border-radius:8px;max-height:100%;overflow-y:auto;font-family: Inter, sans-serif; line-height:1.6;"><h2>Research: ${DOMPurify.sanitize(topic)}</h2>${htmlOutput}</div>`;
}
} catch (error) {
console.error("--- Research Error ---:", error);
const errTxt = stopGenerationFlag ? "Research operation stopped by user." : `Research Error: ${error.message}`;
if(aiPlanContent && planText) aiPlanContent.innerHTML += `<p style="color:red; font-weight:bold; margin-top:15px;">Research Execution Failed!</p><p style="color:red;">${DOMPurify.sanitize(errTxt)}</p>`;
else if (aiPlanContent) aiPlanContent.innerHTML += `<p style="color:red; font-weight:bold; margin-top:15px;">Research Failed (due to plan error)</p>`;
if(outputFrame) outputFrame.srcdoc = `<div style='color:red; padding:20px; font-family: Inter, sans-serif;'><h3>Research Failed</h3><p>${DOMPurify.sanitize(errTxt)}</p></div>`;
if(codeInputArea) codeInputArea.value += `\n\n--- RESEARCH ERROR ---\n${DOMPurify.sanitize(errTxt)}`;
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === "user" && chatHistory[chatHistory.length-1].parts[0].text === userMessageForHistory) {
chatHistory.pop();
console.log("Rolled back last research user message from global history due to error.");
}
} finally {
generationInProgress = false; stopGenerationFlag = false; updateButtonStates(false);
showAiOverlay(false);
console.log("--- Research Cycle Finished ---");
}
}
// --- Start Application ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeApp);
} else {
initializeApp();
}
console.log("Script execution reached end (async ops may continue).");