LiberalMind / agent.js
liberalusa's picture
Update agent.js
acee5a8 verified
console.log("Script execution started.");
// --- Sidebar Functionality ---
function initializeSidebar() {
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebar-toggle');
const sidebarTitle = document.getElementById('sidebar-title');
const folderHeaders = document.querySelectorAll('.folder-header');
if (sidebarToggle) { // Ensure element exists before adding listener
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').forEach(el => el.style.display = 'none');
} else {
icon.textContent = 'chevron_left';
if(sidebarTitle) sidebarTitle.style.display = 'inline';
document.querySelectorAll('.folder-name, .file-name').forEach(el => el.style.display = 'inline');
}
});
}
folderHeaders.forEach(header => {
header.addEventListener('click', function() {
const folder = this.parentElement;
folder.classList.toggle('expanded');
});
});
const files = document.querySelectorAll('.file');
files.forEach(file => {
file.addEventListener('click', function() {
files.forEach(f => f.classList.remove('active'));
this.classList.add('active');
// Add logic to load file content here if needed
});
});
}
// --- 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"); // Added Babel to check
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; // Stop further execution
}
// --- 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'); // Panel for screen share
const videoElement = document.getElementById('video-element'); // Displays screen share
const videoCanvas = document.getElementById('video-canvas'); // Used for capturing frame
const startStreamBtn = document.getElementById('start-stream-btn'); // Should be "Start Screen Share"
const stopStreamBtn = document.getElementById('stop-stream-btn'); // Should be "Stop Screen Share"
// --- 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; // Changed from localStream
let currentScreenFrameDataUrl = ""; // To store the captured screen frame
const API_KEY = "AIzaSyBCURZf72ZWwMN2SHZ7XCQoNgExV4WMX8E"; // Ensure this key has access to the model
const MODEL_NAME = "gemini-2.0-flash-thinking-exp-1219"; // IMPORTANT: MUST be a multimodal model (e.g., gemini-pro-vision, gemini-1.5-flash) for screen analysis
const CORS_PROXY_URL_PREFIX = "https://api.allorigins.win/raw?url=";
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) {
sendBtn.disabled = isGenerating;
researchBtn.disabled = isGenerating;
fetchUrlBtn.disabled = isGenerating;
fileInput.disabled = isGenerating;
uploadBtnLabel.style.cursor = isGenerating ? 'not-allowed' : 'pointer';
uploadBtnLabel.style.opacity = isGenerating ? 0.5 : 1;
startStreamBtn.disabled = isGenerating || screenStream !== null; // Uses screenStream
stopBtn.style.display = isGenerating ? 'block' : 'none';
if (!isGenerating) stopBtn.disabled = false;
}
function showAiOverlay(visible, statusText = "Thinking...") {
aiStatusText.textContent = statusText;
aiOverlay.classList.toggle('visible', visible);
const isResearchingWithProgress = statusText.toLowerCase().includes("researching") && researchProgressEl.dataset.showProgress === "true";
researchProgressEl.classList.toggle('hidden', !visible || !isResearchingWithProgress);
if(isResearchingWithProgress) researchProgressEl.classList.remove('hidden'); else researchProgressEl.classList.add('hidden');
}
function clearInputSourceInfo() {
if (fileInfoEl) fileInfoEl.style.display = 'none';
if (fileNameEl) fileNameEl.textContent = '';
if (urlInfoEl) urlInfoEl.style.display = 'none';
if (urlInfoTextEl) urlInfoTextEl.textContent = '';
fileContentForAI = "";
currentScreenFrameDataUrl = ""; // Clear screen frame data
if (fileInput) fileInput.value = null;
}
// --- Initialization ---
async function initializeApp() {
console.log("initializeApp started");
try {
initializeSidebar();
if(initStatusText) initStatusText.textContent = "Initializing UI...";
setupLanguageSelector();
setupEventListeners();
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 });
if(codeInputArea) codeInputArea.disabled = false;
if(mainInput) mainInput.disabled = false;
updateButtonStates(false);
if(mainInput) mainInput.placeholder = `Enter task, paste URL, use screen share, or upload file...`;
if(codeInputArea) codeInputArea.placeholder = "Results will appear here...";
if(initStatusText) initStatusText.textContent = "Gemini Ready. Loading Pyodide...";
pyodideLoadingPromise = loadPyodideInstance().then(() => {
if(initStatusText) {
if (initStatusText.textContent.includes("Loading Pyodide...")) {
initStatusText.textContent = "Ready (Gemini, Pyodide)";
} else {
initStatusText.textContent = initStatusText.textContent.replace("Gemini Ready.", "Gemini, Pyodide Ready.");
}
}
}).catch(err => {
console.warn("Pyodide background load failed:", err);
if(initStatusText) {
if (initStatusText.textContent.includes("Loading Pyodide...")) {
initStatusText.textContent = "Ready (Gemini; Pyodide failed)";
} else {
initStatusText.textContent = initStatusText.textContent.replace("Gemini Ready.", "Ready (Gemini; Pyodide failed).");
}
}
});
// No Tesseract initialization needed here
} catch (error) {
console.error("Initialization error in initializeApp:", error);
showError(`Failed to initialize application: ${error.message}`);
}
}
function setupLanguageSelector() {
if(!languageSelector) return;
SUPPORTED_LANGUAGES.forEach(lang => {
const option = document.createElement('option');
option.value = lang; option.textContent = lang;
languageSelector.appendChild(option);
});
languageSelector.value = DATA_ANALYSIS_LANG;
currentLanguage = DATA_ANALYSIS_LANG;
if(mainInput) mainInput.placeholder = `Describe task for content (file, URL, screen)...`;
}
async function loadPyodideInstance() {
if (typeof loadPyodide === "undefined") throw new Error("Pyodide main script not loaded.");
console.log("Attempting to load Pyodide...");
try {
const inst = await loadPyodide({}); await inst.loadPackage(["micropip"]); pyodide = inst;
console.log("Pyodide instance loaded successfully."); return pyodide;
} catch (error) { console.error("Pyodide loading error:", error); pyodide = null; throw error; }
}
// --- Event Listeners Setup ---
function setupEventListeners() {
console.log("Setting up event listeners.");
if(reloadButton) reloadButton.addEventListener('click', () => window.location.reload());
if(tabButtons) {
tabButtons.forEach(button => {
button.addEventListener('click', () => {
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
const tabName = button.dataset.tab;
document.getElementById('output-tab').classList.toggle('hidden', tabName !== 'output');
document.getElementById('output-tab').classList.toggle('active', tabName === 'output');
document.getElementById('code-tab').classList.toggle('hidden', tabName !== 'code');
document.getElementById('code-tab').classList.toggle('active', tabName === 'code');
// Assuming 'video-stream-panel' is the ID for the screen share tab's content panel
const screenSharePanel = document.getElementById('video-stream-panel');
if (screenSharePanel) {
screenSharePanel.classList.toggle('hidden', tabName !== 'video'); // 'video' is likely the data-tab value for screen share
screenSharePanel.classList.toggle('active', tabName === 'video');
if (tabName === 'video' && mainInput) {
mainInput.placeholder = "Describe task for the screen content...";
} else if (mainInput && currentLanguage === DATA_ANALYSIS_LANG) {
mainInput.placeholder = `Describe task for content (file, URL, screen)...`;
} else if (mainInput) {
mainInput.placeholder = `Enter task for ${currentLanguage.split('(')[0].trim()}...`;
}
}
});
});
}
if(fileInput) fileInput.addEventListener('change', handleFileChange);
if(fetchUrlBtn) fetchUrlBtn.addEventListener('click', handleFetchUrl);
if(languageSelector) languageSelector.addEventListener('change', handleLanguageChange);
if(sendBtn) sendBtn.addEventListener('click', handleSendRequest);
if(researchBtn) {
researchBtn.addEventListener('click', () => {
const query = mainInput.value.trim() || fileContentForAI /* No currentFrameOcrText */;
if (query) {
performResearch(query);
} else {
alert("Please enter a topic in the input field, or provide content to research.");
}
});
}
if(mainInput) {
mainInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
// Check if videoStreamPanel (screen share panel) is active for URL handling change
const screenSharePanelActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
if (isValidURL(mainInput.value.trim()) && !screenSharePanelActive) {
handleFetchUrl();
} else {
handleSendRequest();
}
}
});
}
if(stopBtn) stopBtn.addEventListener('click', handleStopGeneration);
if(startStreamBtn) startStreamBtn.addEventListener('click', startScreenStream);
if(stopStreamBtn) stopStreamBtn.addEventListener('click', stopScreenStream);
}
// --- File, URL Processing (largely unchanged) ---
async function handleFileChange(event) {
const file = event.target.files[0]; if (!file) { clearInputSourceInfo(); return; }
clearInputSourceInfo();
if(fileNameEl) fileNameEl.textContent = file.name;
if(fileInfoEl) fileInfoEl.style.display = 'flex';
if(mainInput) mainInput.value = "";
showAiOverlay(true, `Processing ${file.name}...`);
try {
let textContent = ""; const fileType = file.name.split('.').pop().toLowerCase();
if (fileType === 'pdf') { textContent = await readPdfFile(file);
} else if (['txt', 'md', 'csv'].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})`; alert("Enable mammoth.js for .docx.");}
} else if (fileType === 'doc') { textContent = `(.doc not supported: ${file.name})`; alert(".doc not supported.");
} else { textContent = `(Unsupported type .${fileType}: ${file.name})`; alert(`Unsupported type: .${fileType}`);}
fileContentForAI = textContent.substring(0, 30000);
if(languageSelector) languageSelector.value = DATA_ANALYSIS_LANG;
currentLanguage = DATA_ANALYSIS_LANG;
if(mainInput) {
mainInput.placeholder = `Describe task for ${file.name}...`; mainInput.value = `File: ${file.name}\nTask: Summarize.`; mainInput.focus();
}
} catch (error) {
console.error("File processing error:", error);
if(mainInput) mainInput.value = `Error processing ${file.name}: ${error.message}.`;
fileContentForAI = ""; alert(`Error: ${error.message}`);
} finally { showAiOverlay(false); }
}
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 && typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';
}
const arrayBuffer = await file.arrayBuffer(); const pdf = await pdfjsLib.getDocument({data: arrayBuffer}).promise;
let fullText = ""; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); fullText += textContent.items.map(item => item.str).join(" ") + "\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 { new URL(string); return true; } catch (_) { return false; }}
async function handleFetchUrl() {
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 > 20 ? url.substring(0,17)+"..." : url;
if(urlInfoEl) urlInfoEl.style.display = 'flex';
showAiOverlay(true, `Fetching ${url.substring(0,30)}...`);
try {
const response = await fetch(`${CORS_PROXY_URL_PREFIX}${encodeURIComponent(url)}`);
if (!response.ok) {
const errorText = await response.text();
console.error("Fetch error response:", errorText);
throw new Error(`Fetch failed: ${response.status} ${response.statusText}. Proxy or target server issue. Details: ${errorText.substring(0,100)}`);
}
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, body');
let text = "";
if (article) {
article.querySelectorAll('script, style, noscript, iframe, header, footer, nav, aside').forEach(el => el.remove());
text = article.textContent || "";
} else {
text = doc.body.textContent || "";
}
fileContentForAI = text.replace(/\s\s+/g, ' ').trim().substring(0, 30000);
if (!fileContentForAI.trim()) {
console.warn("Extracted text from URL is empty.");
}
if(languageSelector) languageSelector.value = DATA_ANALYSIS_LANG;
currentLanguage = DATA_ANALYSIS_LANG;
if(mainInput) {
mainInput.placeholder = `Task for URL content...`; mainInput.value = `URL: ${url}\nTask: Summarize.`; mainInput.focus();
}
} catch (error) {
console.error("URL fetch error:", error);
if(mainInput) mainInput.value = `Error fetching ${url}: ${error.message}.`;
fileContentForAI = "";
alert(`URL Error: ${error.message}. This could be due to the CORS proxy, the target website blocking requests, or network issues.`);
} finally { showAiOverlay(false); }
}
// --- Screen Share Functions ---
async function startScreenStream() {
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
try {
screenStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
if(videoElement) videoElement.srcObject = screenStream;
if(startStreamBtn) startStreamBtn.classList.add('hidden');
if(stopStreamBtn) stopStreamBtn.classList.remove('hidden');
// Optional: Update some status element to indicate screen sharing is active
// e.g., if(someStatusElement) someStatusElement.textContent = "Screen sharing active. Capture frame on send.";
console.log("Screen sharing started.");
// Handle stream ending (e.g., user clicks browser's "Stop sharing" button)
screenStream.getVideoTracks()[0].onended = () => {
console.log("Screen sharing stopped by user or browser.");
stopScreenStream(false); // Pass false to avoid recursive calls if any
};
} catch (err) {
console.error("Screen share access error: ", err);
alert("Screen share access error: " + err.message + "\nMake sure you select a screen/window to share.");
if (screenStream) stopScreenStream(false); // Clean up if partial start
}
} else {
alert("Screen Sharing (getDisplayMedia) not supported by your browser!");
}
}
function stopScreenStream(notifyUser = true) {
if (screenStream) {
screenStream.getTracks().forEach(track => track.stop());
if(videoElement) videoElement.srcObject = null;
screenStream = null;
if(startStreamBtn) startStreamBtn.classList.remove('hidden');
if(stopStreamBtn) stopStreamBtn.classList.add('hidden');
currentScreenFrameDataUrl = ""; // Clear any captured frame
if (notifyUser) console.log("Screen sharing stopped.");
// Optional: Update status element
}
updateButtonStates(generationInProgress); // Reset button states based on generation status
}
async function captureScreenFrameAsDataURL() {
if (!screenStream || !videoElement.srcObject) {
console.warn("Screen stream not active or video element not ready for capture.");
return null;
}
// Ensure video element has dimensions and is ready
if (videoCanvas && videoElement && videoElement.readyState >= videoElement.HAVE_METADATA && videoElement.videoWidth > 0 && videoElement.videoHeight > 0) {
videoCanvas.width = videoElement.videoWidth;
videoCanvas.height = videoElement.videoHeight;
const ctx = videoCanvas.getContext('2d');
ctx.drawImage(videoElement, 0, 0, videoCanvas.width, videoCanvas.height);
try {
return videoCanvas.toDataURL('image/jpeg', 0.85); // Use JPEG for smaller size, 0.85 quality
} catch (e) {
console.error("Error converting canvas to Data URL:", e);
alert("Could not capture screen frame. This can happen if the content being shared is protected or from a different origin in a way that taints the canvas.");
return null;
}
} else {
console.warn("Video element not ready for capture (no metadata/dimensions).");
if (videoElement.videoWidth === 0 || videoElement.videoHeight === 0) {
alert("Cannot capture screen: Video dimensions are zero. Ensure content is visible in the stream.");
}
return null;
}
}
function handleLanguageChange(e) {
currentLanguage = e.target.value;
const screenSharePanelActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
if(mainInput) {
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()}...`;
}
}
if (currentLanguage === "Python (Pyodide)" && !pyodide && !pyodideLoadingPromise) {
if(initStatusText) initStatusText.textContent = initStatusText.textContent.replace("Ready", "Loading Pyodide for Python tasks...");
pyodideLoadingPromise = loadPyodideInstance().then(() => {
if(initStatusText) initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide for Python tasks...", "Pyodide Ready.");
}).catch(err => {
if(initStatusText) initStatusText.textContent = initStatusText.textContent.replace("Loading Pyodide for Python tasks...", "Pyodide Failed.");
});
}
}
function handleStopGeneration() { if (generationInProgress) { stopGenerationFlag = true; if(stopBtn) stopBtn.disabled = true; if(aiStatusText) aiStatusText.textContent = "Stopping...";}}
async function runPythonCode(pythonCode) {
if (!pyodide) {
if(initStatusText && !initStatusText.textContent.includes("Pyodide not loaded")) initStatusText.textContent += " Pyodide not loaded. Waiting...";
try {
await pyodideLoadingPromise; // Wait for it if loading
if (!pyodide) throw new Error("Pyodide failed to load previously.");
if(initStatusText && initStatusText.textContent.includes("Pyodide not loaded")) initStatusText.textContent = initStatusText.textContent.replace("Pyodide not loaded. Waiting...", "Pyodide ready. Retrying Python.");
} catch (error) {
console.error("Pyodide not available for Python execution:", error);
if(outputFrame) outputFrame.srcdoc = `<div style='color:red;padding:20px'>Error: Pyodide (Python runtime) is not available. Cannot run Python code. ${error.message}</div>`;
return;
}
}
if(outputFrame) outputFrame.srcdoc = `<div style="padding:20px;font-family:monospace;white-space:pre-wrap;background:#f0f0f0;border-radius:8px;">Running Python...</div>`;
try {
await pyodide.loadPackagesFromImports(pythonCode);
let capturedOutput = "";
pyodide.setStdout({batched: (str) => capturedOutput += str + "\n"});
pyodide.setStderr({batched: (str) => capturedOutput += "Error: " + str + "\n"});
await pyodide.runPythonAsync(pythonCode);
if(outputFrame) outputFrame.srcdoc = `<div style="padding:20px;font-family:monospace;white-space:pre-wrap;background:#f0f0f0;border-radius:8px;">${DOMPurify.sanitize(capturedOutput) || "Python code executed. No explicit print output."}</div>`;
} catch (error) {
console.error("Python execution error:", error);
if(outputFrame) outputFrame.srcdoc = `<div style='color:red;padding:20px;font-family:monospace;white-space:pre-wrap;'>Python Execution Error:<br>${DOMPurify.sanitize(error.message)}</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}`;
}
} else if ((lang === "React Component" || lang === "Next.js Page (Client-Side)") && typeof Babel !== 'undefined') {
try {
const { code: jsCode } = Babel.transform(code, { presets: ['react'], filename: 'component.jsx' });
code = `// Transpiled from JSX (React/Next.js):\n${jsCode}\n\n/* Note: This is a basic transpile for syntax. Full React/Next.js functionality requires a proper build environment. */`;
displayLang = "JavaScript (from JSX)";
} catch (e) {
console.warn("JSX transpilation failed:", e);
code = `// JSX (transpilation failed: ${e.message})\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) ---
async function handleSendRequest() {
const taskDescription = mainInput ? mainInput.value.trim() : "";
let capturedScreenImageDataUrl = null;
const isScreenShareTabActive = videoStreamPanel && videoStreamPanel.classList.contains('active');
if (isScreenShareTabActive && screenStream) {
showAiOverlay(true, `Capturing screen frame...`);
updateButtonStates(true); // Disable buttons during capture
capturedScreenImageDataUrl = await captureScreenFrameAsDataURL();
if (!capturedScreenImageDataUrl) {
console.warn("Failed to capture screen frame. Proceeding without image.");
// Potentially alert user or handle this state
showAiOverlay(false); // Hide overlay if capture fails and we don't proceed
updateButtonStates(false);
alert("Could not capture screen frame. Please try again or ensure screen content is shareable.");
return;
}
// Keep overlay: Processing request with screen image...
showAiOverlay(true, `Processing request with screen image...`);
}
if (!taskDescription && !fileContentForAI && !capturedScreenImageDataUrl) {
alert("Please enter a task or provide content (file, URL, or active screen share).");
updateButtonStates(false); // Ensure buttons are re-enabled if nothing to send
return;
}
generationInProgress = true; // Set true before any async UI work not covered by above capture
stopGenerationFlag = false;
updateButtonStates(true); // Ensure buttons are disabled now that we are proceeding
if (!aiOverlay.classList.contains('visible')) { // Only show if not already shown by capture
showAiOverlay(true, `Processing request...`);
}
if(codeInputArea) codeInputArea.value = "";
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px;">Generating response...</p>`;
if(fullCodeContent) fullCodeContent.textContent = "";
if (window.currentAiChart) { window.currentAiChart.destroy(); window.currentAiChart = null; }
try {
let requestContent; // This will be passed to model.generateContent()
let currentRequestMode = "code_generation"; // Default
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>`;
if (capturedScreenImageDataUrl) {
currentRequestMode = "screen_analysis";
const base64ImageData = capturedScreenImageDataUrl.split(',')[1];
const mimeType = capturedScreenImageDataUrl.substring(capturedScreenImageDataUrl.indexOf(':') + 1, capturedScreenImageDataUrl.indexOf(';'));
const textPromptPart = `You are an AI assistant. An image of a user's screen is provided.
User's task or question about this screen image: "${taskDescription || "Describe what you see on this screen in detail."}"
Analyze the screen image and provide a helpful, concise response in Markdown format.
Focus ONLY on what is visible in the image. Do not speculate about hidden content or actions not depicted.`;
requestContent = [
textPromptPart,
{ inlineData: { mimeType: mimeType, data: base64ImageData } }
];
console.log(`Mode: ${currentRequestMode}. Task: "${taskDescription || "Describe screen"}". Image MIME: ${mimeType}, Data length: ${base64ImageData.length}`);
} else if (fileContentForAI || currentLanguage === DATA_ANALYSIS_LANG) {
currentRequestMode = "data_analysis";
const dataAnalysisPrompt = `
You are an AI data and content analysis assistant.
You have been provided with the following context (e.g., from a file, URL), which may be empty if no content was loaded:
Content Context (first 30,000 characters):
"""
${fileContentForAI || "No explicit content was provided apart from the task."}
"""
The user's task is: "${taskDescription}"
Based on the provided Content Context and the user's task, please choose the MOST APPROPRIATE response format from the following options:
1. **HTML TABLE:** If the task involves presenting structured data or comparing items, and the content supports it, generate a semantic HTML table.
2. **Chart.js JSON Configuration:** If the task is best represented by a visual chart (e.g., trends, distributions, proportions), and the data allows for it, provide a VALID JSON object for Chart.js. The JSON should be structured as: \`{"type": "chartjs", "config": {type: "bar", data: {...}, options: {...}}}\`. Ensure the config is complete and directly usable by Chart.js.
3. **Markdown Text Analysis:** If the task requires a textual summary, explanation, interpretation, or if the data is unsuitable for a table or chart, provide a comprehensive analysis in Markdown format.
CRITICAL INSTRUCTIONS:
- **ONLY ONE RESPONSE TYPE:** You MUST output *only* the chosen format (HTML table, Chart.js JSON, or Markdown). Do NOT include any conversational preamble, explanations about your choice, or markdown specifiers like \`\`\`html or \`\`\`json unless it's part of the Markdown text itself.
- **Chart.js JSON must be raw JSON:** If you choose Chart.js, the entire response must be *only* the JSON object, starting with \`{\` and ending with \`}\`.
- **HTML Table must be raw HTML:** If you choose HTML table, the entire response must be *only* the HTML code, starting with \`<table\` and ending with \`</table>\`.
- **Insufficiency:** If the provided content is insufficient or inappropriate for creating a meaningful table or chart as requested, default to a Markdown text analysis explaining why and what might be needed.`;
requestContent = dataAnalysisPrompt;
console.log(`Mode: ${currentRequestMode}. Prompt (start):`, requestContent.substring(0, 250) + "...");
} else { // Default to code generation if no screen image and not data analysis
currentRequestMode = "code_generation";
const baseP = `You are an expert AI code generation assistant. Your primary goal is to generate clean, correct, and complete code according to the user's request.`;
let specI = "";
switch(currentLanguage){
case "HTML/CSS/JS (Web Page)": specI = `Generate a single, self-contained HTML file. All CSS must be within \`<style>\` tags in the \`<head>\`, and all JavaScript must be within \`<script>\` tags, preferably at the end of the \`<body>\`. Do not use external file links for CSS or JS unless specifically asked. The HTML should be well-structured and semantic.`; break;
case "Python (Pyodide)": specI = `Generate Python code intended to be run in a Pyodide environment. Use \`print()\` for any output that should be displayed. If graphics are requested, explain that Pyodide typically needs integration with JavaScript for complex UIs, or use simple text-based representations if possible. Assume standard Python libraries are available. For plotting, prefer generating data that could be used with a library like Matplotlib, but output the data or a description if direct plotting in Pyodide is complex for the request.`; break;
case "JavaScript (Standalone)": specI = `Generate standalone JavaScript code. If it's for a browser environment, it might manipulate a hypothetical DOM or use \`console.log()\` for output. If it's for Node.js, assume a Node environment. Be clear about execution context if ambiguous.`; break;
case "TypeScript": specI = `Generate TypeScript code. Include type annotations. The code should be transpilable to JavaScript.`; break;
case "React Component": specI = `Generate a single React functional component as a self-contained JSX snippet. Include necessary imports (e.g., \`import React from 'react';\`). Focus on the component's structure and logic.`; break;
case "Next.js Page (Client-Side)": specI = `Generate code for a Next.js page component, focusing on client-side rendering aspects. This would typically be a React component. Assume it's a file within the 'pages' directory. If server-side logic (getServerSideProps, etc.) is implied, structure it accordingly but prioritize the client-side component code.`; break;
default: specI = `Generate code for ${currentLanguage.replace(' (Code Display)','').trim()}. Provide any necessary comments or context for the code.`;
}
const codeGenPrompt = `${baseP}\nUser Task: "${taskDescription}"\nTarget Language/Framework: ${currentLanguage}\n\nSpecific Instructions for ${currentLanguage}:\n${specI}\n\nCRITICAL: Output ONLY the raw code as a single block. Do NOT use Markdown backticks (e.g., \`\`\`language) to wrap the code. No explanations, no conversational text, just the code itself.`;
requestContent = codeGenPrompt;
console.log(`Mode: ${currentRequestMode}. Prompt (start):`, requestContent.substring(0, 250) + "...");
}
const result = await model.generateContent(requestContent);
if (stopGenerationFlag) throw new Error("Stopped by user.");
const response = await result.response; let rawRspTxt = "";
if (response && typeof response.text === 'function') rawRspTxt = response.text();
else throw new Error("No text from Gemini response.");
generatedCode = rawRspTxt.replace(/^```(?:[a-zA-Z0-9_ .-]+)?\s*\n?([\s\S]*?)\n?```$/gm, '$1').trim();
if (currentRequestMode === "screen_analysis" || (currentRequestMode === "data_analysis" && !(generatedCode.startsWith("{") && generatedCode.endsWith("}")) && !generatedCode.toLowerCase().includes("<table"))) {
const htmlOutput = DOMPurify.sanitize(marked.parse(generatedCode));
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") { // Chart or Table from Data Analysis
let isChart = false, isTable = false, chartCfg;
if (generatedCode.startsWith("{") && generatedCode.endsWith("}")) {
try { const p = JSON.parse(generatedCode); if(p.type === "chartjs" && p.config && p.config.type && p.config.data){isChart=true;chartCfg=p.config;}}
catch(e){ console.warn("Response looked like JSON but failed to parse or was not Chart.js format:", e, generatedCode.substring(0,100)); }
}
if (!isChart && generatedCode.toLowerCase().trim().startsWith("<table") && generatedCode.toLowerCase().trim().endsWith("</table>")){isTable=true;}
if(isChart && outputFrame){
outputFrame.srcdoc = chartCanvasHTML;
outputFrame.onload = () => {
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 = () => {
window.currentAiChart = new outputFrame.contentWindow.Chart(chartCtx, chartCfg);
};
outputFrame.contentWindow.document.head.appendChild(script);
}
} else { throw new Error("Chart canvas element not found in iframe."); }
} catch(e){
console.error("Chart rendering error:", e);
if(outputFrame.contentDocument && outputFrame.contentDocument.body) outputFrame.contentDocument.body.innerHTML=`<div style='padding:10px;color:red'>Chart Error: ${DOMPurify.sanitize(e.message)}</div><pre style='font-size:0.8em;white-space:pre-wrap;word-break:break-all;'>${DOMPurify.sanitize(JSON.stringify(chartCfg,null,2))}</pre>`;
}
outputFrame.onload=null;
};
if(codeInputArea) codeInputArea.value = `// Chart.js Configuration:\n${JSON.stringify(chartCfg,null,2)}`;
if(fullCodeContent) fullCodeContent.textContent = codeInputArea.value;
} else if (isTable && outputFrame){
outputFrame.srcdoc = `<div style="padding:10px;overflow:auto;background:white;border-radius:8px;">${DOMPurify.sanitize(generatedCode,{USE_PROFILES:{html:true}})}</div>`;
if(codeInputArea) codeInputArea.value=generatedCode;
if(fullCodeContent) fullCodeContent.textContent=generatedCode;
} else { // Fallback for data_analysis if not chart/table (should be Markdown already)
const htmlOutput = DOMPurify.sanitize(marked.parse(generatedCode));
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
if(aiStatusText) aiStatusText.textContent = "Preparing preview...";
if(outputFrame) {
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 = `<div style="padding:20px;"><h4 style="color:var(--primary);">${currentLanguage.replace(' (Code Display)','')} Code</h4><p>Live preview not generally supported for this language. See 'Full Code' tab.</p></div>`;
}
}
if(codeInputArea) codeInputArea.value=generatedCode;
if(fullCodeContent) fullCodeContent.textContent=generatedCode;
}
} catch (error) {
console.error("AI Request/Processing Error:", error);
const errTxt = stopGenerationFlag ? "Stopped by user." : `Error: ${error.message}`;
if(codeInputArea) codeInputArea.value += `\n\n--- ERROR ---\n${DOMPurify.sanitize(errTxt)}`;
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 the error was due to a non-multimodal model, guide the user
if (error.message && (error.message.includes("does not support image input") || error.message.includes("multimodal"))) {
if(outputFrame) outputFrame.srcdoc += `<p style='color:orange;padding:0 20px 20px 20px;'>Note: The current AI model ('${MODEL_NAME}') might not support image analysis. For screen analysis, a multimodal model (e.g., 'gemini-pro-vision', 'gemini-1.5-flash') is required.</p>`;
}
} finally {
generationInProgress = false; stopGenerationFlag = false;
updateButtonStates(false); // Re-enable buttons
showAiOverlay(false); // Hide overlay
currentScreenFrameDataUrl = ""; // Clear captured frame data after use
console.log("handleSendRequest finished.");
}
}
async function performResearch(topic) {
if (!topic) {
alert("Please provide a topic to research.");
return;
}
if (generationInProgress) {
alert("Another AI task is already in progress. Please wait.");
return;
}
console.log(`Performing research on: ${topic}`);
generationInProgress = true;
stopGenerationFlag = false;
updateButtonStates(true);
showAiOverlay(true, `Researching: ${topic.substring(0, 50)}${topic.length > 50 ? '...' : ''}`);
if(codeInputArea) codeInputArea.value = "";
if(outputFrame) outputFrame.srcdoc = `<p style="padding:20px;">Researching topic: "${DOMPurify.sanitize(topic)}"...</p>`;
if(fullCodeContent) fullCodeContent.textContent = "";
if (window.currentAiChart) { window.currentAiChart.destroy(); window.currentAiChart = null; }
try {
const researchPrompt = `
You are an AI research assistant. Your goal is to find and synthesize information on the given topic from your knowledge base.
Topic to research: "${topic}"
Please provide a concise yet comprehensive summary of the key information related to this topic.
Structure your response in Markdown format.
If possible and relevant, try to:
- Identify key facts, definitions, and concepts.
- Mention important figures, events, or developments.
- Briefly explain different perspectives or common debates if applicable.
- If you can cite general areas or types of sources for further reading (without making up specific URLs), that would be helpful.
Do not invent information. If the topic is too obscure or you lack sufficient information, please state that clearly.
Begin your response directly with the research findings.`;
console.log("Research prompt (start):", researchPrompt.substring(0, 150) + "...");
const result = await model.generateContent(researchPrompt); // Research is text-only
if (stopGenerationFlag) {
throw new Error("Research stopped by user.");
}
const response = await result.response;
let researchText = "";
if (response && typeof response.text === 'function') {
researchText = response.text();
} else {
throw new Error("No text received from Gemini for research query.");
}
const cleanedResearchText = researchText.replace(/^```(?:markdown)?\s*\n?([\s\S]*?)\n?```$/gm, '$1').trim();
if(codeInputArea) codeInputArea.value = `# Research on: ${topic}\n\n${cleanedResearchText}`;
if(fullCodeContent) fullCodeContent.textContent = codeInputArea.value;
const htmlOutput = DOMPurify.sanitize(marked.parse(cleanedResearchText));
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;"><h2>Research: ${DOMPurify.sanitize(topic)}</h2>${htmlOutput}</div>`;
} catch (error) {
console.error("Research Error:", error);
const errTxt = stopGenerationFlag ? "Research stopped by user." : `Research Error: ${error.message}`;
if(codeInputArea) codeInputArea.value += `\n\n--- RESEARCH ERROR ---\n${DOMPurify.sanitize(errTxt)}`;
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>`;
} finally {
generationInProgress = false;
stopGenerationFlag = false;
updateButtonStates(false);
showAiOverlay(false);
console.log("performResearch finished.");
}
}
// --- Start Application ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeApp);
} else {
initializeApp();
}
console.log("Script execution reached end (async ops may continue).");