Description:

// myai-app.js
document.addEventListener('DOMContentLoaded', () => {
const appRoot = document.getElementById('app-root');
const userData = myaiAppData; // Access global variable from wp_localize_script
if (appRoot && userData && userData.is_logged_in) {
// If logged in, initialize the app
initializeApp(appRoot, userData);
} else if (appRoot) {
// If not logged in, show a basic auth screen or redirect
appRoot.innerHTML = '

Please log in to WordPress to access MyAI Interface.

'; const authScreen = document.getElementById('auth-screen'); if (authScreen) { // Add basic styling if this screen is shown authScreen.style.display = 'flex'; authScreen.style.flexDirection = 'column'; authScreen.style.justifyContent = 'center'; authScreen.style.alignItems = 'center'; authScreen.style.height = '100%'; authScreen.style.width = '100%'; authScreen.style.backgroundColor = '#131314'; authScreen.style.color = '#e3e3e3'; } } else { console.error("MyAI App: #app-root element not found. Cannot initialize UI."); } }); let paneCounter = 0; // To give each new pane a unique ID function initializeApp(container, userData) { if (!container) { console.error("initializeApp: Container element not found."); return; } container.innerHTML = ''; // Clear the container first container.classList.add('grid-container'); const columns = ['left-col', 'center-col', 'right-col']; // Fixed 3 columns as per requirement const columnElements = []; // Retrieve settings from userData.myai_settings const settings = userData.myai_settings; // Apply global color scheme if available (currently not available, but for future) if (settings.user_color_scheme_available === '1' && settings.user_color_scheme) { document.documentElement.style.setProperty('--user-defined-bg-color', settings.user_color_scheme); // You would expand this to apply colors to AI, user input, system messages etc. } if (settings.ai_text_color_available === '1' && settings.ai_text_color) { document.documentElement.style.setProperty('--ai-text-color', settings.ai_text_color); } if (settings.user_input_text_color_available === '1' && settings.user_input_text_color) { document.documentElement.style.setProperty('--user-input-text-color', settings.user_input_text_color); } if (settings.system_messages_text_color_available === '1' && settings.system_messages_text_color) { document.documentElement.style.setProperty('--system-messages-text-color', settings.system_messages_text_color); } // Create 3 main columns for (const colId of columns) { const col = document.createElement('div'); col.id = `col-${paneCounter++}`; col.className = 'split-col'; container.appendChild(col); columnElements.push(`#${col.id}`); const paneElements = []; // Create 3 panes within each column for (let i = 1; i <= 3; i++) { const pane = document.createElement('div'); const paneId = `pane-${paneCounter++}`; pane.id = paneId; pane.className = 'split-pane'; // Apply scrollable_panes setting if (settings.scrollable_panes === '0') { // If disabled, prevent overflow:auto pane.style.overflow = 'hidden'; } const header = document.createElement('div'); header.className = 'pane-header'; const label = document.createElement('span'); label.textContent = `Pane ${paneId}`; // Default label, can be dynamic const subdivideBtn = document.createElement('button'); subdivideBtn.textContent = '⊞'; subdivideBtn.title = 'Subdivide this pane'; subdivideBtn.onclick = () => { // Check if fractal grid subdivision is enabled in settings if (settings.fractal_grid === '1') { // '1' because PHP checkbox saves as '1' or '0' createGrid(pane, userData); // RECURSIVE CALL, pass userData } else { alert("Fractal Grid Subdivision is currently disabled in your settings."); } }; header.appendChild(label); header.appendChild(subdivideBtn); pane.appendChild(header); pane.appendChild(document.createElement('div')); // Placeholder for content area col.appendChild(pane); paneElements.push(`#${paneId}`); } // Make the panes in the column resizable, if enabled in settings if (settings.resizable_panes === '1') { Split(paneElements, { direction: 'vertical', gutterSize: 8, minSize: 50 }); } } // Make the columns resizable, if enabled in settings if (settings.resizable_columns === '1') { Split(columnElements, { gutterSize: 8, minSize: 200 }); } }

Status: Published   Priority: 0.0

Target:   Comments:   URLs:   Images:

]]>

/* wp-content/plugins/my-ai-gemini-interface/styles.css */

/* Define CSS Variables for future color schemes */
:root {
    --user-defined-bg-color: #131314; /* Default from body background */
    --ai-text-color: #e3e3e3;
    --user-input-text-color: #4285F4;
    --system-messages-text-color: #ADD8E6;

    /* Define colors for the 9 major sections */
    --pane-bg-color-top-left: #282a2c;
    --pane-bg-color-top-middle: #2d2e30;
    --pane-bg-color-top-right: #333537;
    --pane-bg-color-middle-left: #2d2e30;
    --pane-bg-color-center-center: #1f1f20; /* Main display, slightly darker */
    --pane-bg-color-middle-right: #333537;
    --pane-bg-color-bottom-left: #333537;
    --pane-bg-color-bottom-center: #282a2c;
    --pane-bg-color-bottom-right: #2d2e30;
}


/* Core layout for the app container */
#app-root {
    height: 100vh; /* Full viewport height for the app root */
    width: 100%;
    margin: 0;
    overflow: hidden; /* Prevent body scrollbars if content is managed by nested panes */
    display: flex; /* Make it a flex container for the split columns */
    flex-direction: column; /* Ensure vertical stacking if needed, though split.js handles this for columns/panes */
    background-color: var(--user-defined-bg-color); /* Apply user-defined background if set */
    color: var(--ai-text-color); /* Apply default AI text color, can be overridden */
}

/* Base styles for the split columns within the grid - Removed as we're now using a single CSS Grid for the 10x10 */
/* .grid-container, .split-col, .split-pane styles below are for the original Split.js setup.
   They will be overridden by .myai-dashboard-layout-bagua and .myai-grid-section.
   Keeping them for now as they might apply to nested grids if Fractal Grid is enabled,
   but the main layout uses the new grid definitions.
*/


/* NEW: Styles for the 10x10 Dashboard-Layout-Bagua */
.myai-dashboard-layout-bagua {
    display: grid;
    /* Define 10 columns and 10 rows for a 10x10 grid */
    grid-template-columns: repeat(10, 1fr);
    grid-template-rows: repeat(10, 1fr);
    gap: 8px; /* Gap between individual 10x10 cells, creating visual lines */
    width: 100%;
    height: 100vh; /* Make it fill the viewport height */
    padding: 8px; /* Padding around the entire grid */
    box-sizing: border-box;
    background-color: var(--user-defined-bg-color); /* Overall background */
}

/* Style for each conceptual section, mapping to grid areas */
.myai-grid-section {
    border: 1px solid #444; /* Border for the major sections */
    border-radius: 6px;
    padding: 10px;
    overflow: hidden; /* Manage overflow within the section */
    display: flex;
    flex-direction: column;
    color: var(--ai-text-color);
    box-sizing: border-box;
}

/* Define the grid-area for each of the 9 major sections within the 10x10 grid */
/* Using grid-column and grid-row to span multiple cells of the 10x10 grid */
/* Assuming a standard grid where (1,1) is top-left, and cells are 1-indexed.
   If (0,0) is top-left, adjust indices by +1 for CSS grid-row/column properties.
   Given the user's "9:9" for top-right, assuming (0,0) is top-left for internal model.
   Thus, for 10x10 grid, indices are 0-9.
   Visual Column 1 (Left): spans grid columns 0-3 (e.g., 4 units wide)
   Visual Column 2 (Middle): spans grid columns 4-7 (e.g., 4 units wide)
   Visual Column 3 (Right): spans grid columns 8-9 (e.g., 2 units wide)

   Visual Row 1 (Top): spans grid rows 0-3
   Visual Row 2 (Middle): spans grid rows 4-7
   Visual Row 3 (Bottom): spans grid rows 8-9
*/


/* Top-Left (Col 1, Row 1) - Company/Logo */
.myai-grid-section.top-left {
    grid-column: 1 / span 3; /* Spans 3 columns */
    grid-row: 1 / span 3;    /* Spans 3 rows */
    background-color: var(--pane-bg-color-top-left);
}

/* Top-Middle (Col 2, Row 1) - Notification Center */
.myai-grid-section.top-middle {
    grid-column: 4 / span 4; /* Spans 4 columns */
    grid-row: 1 / span 3;    /* Spans 3 rows */
    background-color: var(--pane-bg-color-top-middle);
}

/* Top-Right (Col 3, Row 1) - User Account */
.myai-grid-section.top-right {
    grid-column: 8 / span 3; /* Spans 3 columns (e.g. 8,9,10) */
    grid-row: 1 / span 3;    /* Spans 3 rows */
    background-color: var(--pane-bg-color-top-right);
}


/* Middle-Left (Col 1, Row 2) - Navigation */
.myai-grid-section.middle-left {
    grid-column: 1 / span 3;
    grid-row: 4 / span 4; /* Starts on row 4 */
    background-color: var(--pane-bg-color-middle-left);
}

/* Center-Center (Col 2, Row 2) - Main Display */
.myai-grid-section.center-center {
    grid-column: 4 / span 4;
    grid-row: 4 / span 4;
    background-color: var(--pane-bg-color-center-center);
}

/* Middle-Right (Col 3, Row 2) - AI Info & Output */
.myai-grid-section.middle-right {
    grid-column: 8 / span 3;
    grid-row: 4 / span 4;
    background-color: var(--pane-bg-color-middle-right);
}


/* Bottom-Left (Col 1, Row 3) - Libraries */
.myai-grid-section.bottom-left {
    grid-column: 1 / span 3;
    grid-row: 8 / span 3; /* Starts on row 8 */
    background-color: var(--pane-bg-color-bottom-left);
}

/* Bottom-Center (Col 2, Row 3) - Command Prompt */
.myai-grid-section.bottom-center {
    grid-column: 4 / span 4;
    grid-row: 8 / span 3;
    background-color: var(--pane-bg-color-bottom-center);
}

/* Bottom-Right (Col 3, Row 3) - Tools */
.myai-grid-section.bottom-right {
    grid-column: 8 / span 3;
    grid-row: 8 / span 3;
    background-color: var(--pane-bg-color-bottom-right);
}


/* General Styles for auth-screen (if not using the main dashboard) */
#auth-screen {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 100vh;
    text-align: center;
    background-color: var(--user-defined-bg-color); /* Use user-defined background */
    color: var(--ai-text-color); /* Use user-defined AI text color */
}

/* Login button styles (if not using the main dashboard for login) */
.login-btn {
    display: inline-block;
    padding: 12px 24px;
    background-color: #4285F4;
    color: white;
    text-decoration: none;
    border-radius: 4px;
    font-weight: bold;
    margin-top: 20px;
}

/* Ensure generic inheritance for text elements by not explicitly setting colors/fonts here */
h1, h2, h3, h4, h5, h6, p, a, ul, ol, li {
    /* No specific styling here to allow theme inheritance */
}

/* Future: Styles for AI-generated text, user input text, system messages */
.ai-output-text { color: var(--ai-text-color); }
.user-input-text { color: var(--user-input-text-color); }
.system-message-text { color: var(--system-messages-text-color); }
// wp-content/plugins/my-ai-gemini-interface/js/myai-app.js

document.addEventListener('DOMContentLoaded', () => {
    const appRoot = document.getElementById('app-root');

    const userData = myaiAppData; // Access global variable from wp_localize_script

    if (appRoot && userData && userData.is_logged_in) {
        // Initialize the app with the dashboard layout
        initializeApp(appRoot, userData);
    } else if (appRoot) {
        // If not logged in, show a basic auth screen or redirect
        appRoot.innerHTML = '<div id="auth-screen"><h2>Please log in to WordPress to access MyAI Interface.</h2></div>';
        const authScreen = document.getElementById('auth-screen');
        if (authScreen) {
            authScreen.style.display = 'flex';
            authScreen.style.flexDirection = 'column';
            authScreen.style.justifyContent = 'center';
            authScreen.style.align-items = 'center';
            authScreen.style.height = '100%';
            authScreen.style.width = '100%';
            authScreen.style.backgroundColor = '#131314';
            authScreen.style.color = '#e3e3e3';
        }
    } else {
        console.error("MyAI App: #app-root element not found. Cannot initialize UI.");
    }
});

function initializeApp(container, userData) {
    if (!container) {
        console.error("initializeApp: Container element not found.");
        return;
    }

    // The PHP block renders the main #myai-dashboard-container with its sections.
    // This JS will initially do basic setup or listen for events.

    // No need to dynamically create the main 9 sections here, PHP block rendering handles it.
    // This function will primarily be for initializing interactive elements within the sections,
    // like resizers (if using Split.js for major panes), or dynamic content loaders.

    // Example: If major panes are resizable via Split.js, you'd add:
    // if (userData.myai_settings.resizable_columns === '1' || userData.myai_settings.resizable_panes === '1') {
    //    // This would require defining the actual split sections with Split.js compatible divs
    //    // and gutters in the PHP output. For a 10x10 grid, Split.js would typically
    //    // apply to the outer columns and then rows within columns, not the overall 10x10 cell grid.
    //    // This is a more advanced integration than simply applying a CSS Grid.
    // }

    console.log("MyAI Dashboard initialized. User settings:", userData.myai_settings);
    // Future: Add JavaScript logic here for:
    // - Loading content into each .myai-grid-section (e.g., fetch from REST API)
    // - Handling clicks on tool icons to update Main Display (2,2)
    // - Implementing command prompt features (history, soft return)
    // - Managing user/AI online/offline status updates
}

// Global function for nested grids (if fractal grid subdivision is enabled)
// This is a simplified version; real implementation needs to create HTML structure.
let nestedPaneCounter = 0;
function createGrid(containerElement, userData) {
    // This function is for creating nested grids within a pane, as per fractal grid subdivision.
    // Its implementation depends on how you want the nested grid to look and behave.
    console.log(`Creating nested grid in ${containerElement.id}. Fractal grid setting: ${userData.myai_settings.fractal_grid}`);

    const nestedGridId = `nested-grid-${nestedPaneCounter++}`;
    const newGridHtml = `
        <div id="${nestedGridId}" style="display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); gap: 5px; height: 100%; border: 1px dashed gray; padding: 5px;">
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 1</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 2</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 3</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 4</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 5</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 6</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 7</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 8</div>
            <div style="background-color: rgba(100,100,100,0.5); padding: 5px;">Sub 9</div>
        </div>
    `;
    // Clear existing content and append the new grid
    containerElement.innerHTML = '';
    containerElement.insertAdjacentHTML('beforeend', newGridHtml);
}
<?php
/**
 * Plugin Name: MyAI Gemini Interface Settings & UI Widget
 * Plugin URI: https://www.brettanthonydixon.com/bits.brettanthonydixon.com/projects/ai/
 * Description: Extends WordPress user profiles with MyAI Gemini Interface settings and capabilities, and provides a UI widget.
 * Version: 1.3.0
 * Author: Brett Anthony Dixon
 * Author URI: https://www.brettanthonydixon.com/
 * License: GPL2
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * MyAI_Gemini_Interface_Settings Class
 * Manages the custom settings for MyAI Gemini Interface.
 */
class MyAI_Gemini_Interface_Settings {

    public function __construct() {
        // Add fields to user profile for both regular users and admins
        add_action( 'show_user_profile', array( $this, 'add_myai_user_profile_fields' ) );
        add_action( 'edit_user_profile', array( $this, 'add_myai_user_profile_fields' ) );

        // Save custom fields
        add_action( 'personal_options_update', array( $this, 'save_myai_user_profile_fields' ) );
        add_action( 'edit_user_profile_update', array( $this, 'save_myai_user_profile_fields' ) );

        // Enqueue styles and scripts for the widget where needed
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_myai_scripts_and_styles' ) );

        // NEW: Register custom blocks
        add_action( 'init', array( $this, 'register_myai_blocks' ) );
    }

    /**
     * Enqueue custom scripts and styles.
     */
    public function enqueue_myai_scripts_and_styles() {
        // Enqueue main UI stylesheet
        wp_enqueue_style( 'myai-styles', plugins_url( 'styles.css', __FILE__ ), array(), '1.0.0', 'all' );

        // Enqueue Split.js from a local copy in the plugin directory
        wp_enqueue_script( 'split-js', plugins_url( 'js/split.min.js', __FILE__ ), array(), '1.6.5', true );

        // Enqueue your custom app script
        wp_enqueue_script( 'myai-app-script', plugins_url( 'js/myai-app.js', __FILE__ ), array('split-js'), '1.0.0', true );

        // Pass dynamic data to the app script (e.g., user info, settings)
        $current_user = wp_get_current_user();
        
        $myai_user_role_config = get_user_meta( $current_user->ID, 'myai_user_role_config', true );
        if ( empty( $myai_user_role_config ) ) {
            // Default mapping if not explicitly set
            if ( in_array( 'administrator', (array) $current_user->roles ) ) {
                $myai_user_role_config = 'Administrator';
            } elseif ( in_array( 'editor', (array) $current_user->roles ) || in_array( 'author', (array) $current_user->roles ) ) {
                $myai_user_role_config = 'Writer';
            } elseif ( in_array( 'contributor', (array) $current_user->roles ) ) {
                $myai_user_role_config = 'Contributor';
            } elseif ( in_array( 'subscriber', (array) $current_user->roles ) ) {
                 $myai_user_role_config = 'Subscriber';
            } else {
                $myai_user_role_config = 'Viewer';
            }
        }

        $user_data = array(
            'is_logged_in' => is_user_logged_in(),
            'user_id'      => get_current_user_id(),
            'user_name'    => is_user_logged_in() ? $current_user->display_name : '',
            'user_email'   => is_user_logged_in() ? $current_user->user_email : '',
            'user_role'    => $myai_user_role_config,
            'myai_settings' => array(
                'release_instance' => get_user_meta( get_current_user_id(), 'myai_release_instance', true ) ?: 'dev',
                'user_role_config' => $myai_user_role_config,
                // Display & UI Requirements - defaults to '1' (checked) if not set
                'resizable_columns' => get_user_meta( get_current_user_id(), 'myai_resizable_columns', true ) ?: '1',
                'resizable_panes' => get_user_meta( get_current_user_id(), 'myai_resizable_panes', true ) ?: '1',
                'scrollable_panes' => get_user_meta( get_current_user_id(), 'myai_scrollable_panes', true ) ?: '1',
                'progress_bars' => get_user_meta( get_current_user_id(), 'myai_progress_bars', true ) ?: '1',
                'fractal_grid' => get_user_meta( get_current_user_id(), 'myai_fractal_grid', true ) ?: '1',

                // Gemini Output Display Settings
                'ai_rendering_display' => get_user_meta( get_current_user_id(), 'myai_ai_rendering_display', true ) ?: 'direct',
                'ai_thinking_indicator' => get_user_meta( get_current_user_id(), 'myai_ai_thinking_indicator', true ) ?: 'none',
                'ai_text_color' => get_user_meta( get_current_user_id(), 'myai_ai_text_color', true ) ?: '#E3E3E3',
                'user_input_text_color' => get_user_meta( get_current_user_id(), 'myai_user_input_text_color', true ) ?: '#4285F4',
                'system_messages_text_color' => get_user_meta( get_current_user_id(), 'myai_system_messages_text_color', true ) ?: '#ADD8E6',
                'user_color_scheme' => get_user_meta( get_current_user_id(), 'myai_user_color_scheme', true ) ?: '',


                // Explicitly state 'not available' features for frontend to ghost
                'ada_compliance_available' => '0',
                'auto_window_opening_available' => '0',
                'contextual_understanding_available' => '0',
                'interpret_misspellings_available' => '0',
                'voice_inputs_available' => '0',
                'humor_holiday_cards_available' => '0',
                'source_credibility_threshold_available' => '0',
                'output_truth_threshold_available' => '0',
                // New color settings are also initially unavailable
                'ai_text_color_available' => '0',
                'user_input_text_color_available' => '0',
                'system_messages_text_color_available' => '0',
                'user_color_scheme_available' => '0',
            ),
            'base_post_url_template' => 'https://bits.brettanthonydixon.com/{YYYY}/{MM}/{DD}/projects/ai/',
        );

        wp_localize_script( 'myai-app-script', 'myaiAppData', $user_data );
    }

    /**
     * Register custom blocks for the dashboard layout.
     */
    public function register_myai_blocks() {
        // Register the main dashboard layout block
        register_block_type( 'myai/dashboard-layout-block', array(
            'render_callback' => array( $this, 'render_dashboard_layout_block' ),
            'editor_script'   => 'myai-block-editor-script', // JS for editor UI
            'editor_style'    => 'myai-block-editor-style',  // CSS for editor UI
            'style'           => 'myai-styles',              // Frontend CSS already enqueued
            'script'          => 'myai-app-script',          // Frontend JS already enqueued
        ) );

        // You would register other individual content blocks here as needed (e.g., myai/gmail-display-block)
        // For now, the main dashboard block will render all 9 sections as simple divs.
    }


    /**
     * Render callback for the MyAI Dashboard Layout Block.
     * This will create the 10x10 grid with 9 major sections as divs.
     *
     * @param array    $attributes Block attributes.
     * @param string   $content    Block content.
     * @param WP_Block $block      The parsed block.
     * @return string HTML output for the block.
     */
    public function render_dashboard_layout_block( $attributes, $content, $block ) {
        // This is a simplified representation of a 10x10 CSS Grid mapping to 9 conceptual areas.
        // The actual CSS will handle the grid layout.
        $output = '<div id="myai-dashboard-container" class="myai-dashboard-layout-bagua">';

        // Define the 9 major sections conceptually within the 10x10 grid.
        // The classes will be styled in styles.css to represent the background colors and grid areas.

        // Row 1 (Top) - Visual (0:0 to X:2)
        $output .= '<div class="myai-grid-section top-left" data-grid-cell="0:0-2:2">';
        $output .= '<h3>Company/Logo</h3>'; // Placeholder name
        $output .= '<p>Domain & Subdomains</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section top-middle" data-grid-cell="3:0-5:2">';
        $output .= '<h3>Notification Center</h3>'; // Placeholder name
        $output .= '<p>Bulletins, App Notifications, Meet Carousel</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section top-right" data-grid-cell="6:0-8:2">';
        $output .= '<h3>User Account</h3>'; // Placeholder name
        $output .= '<p>Sign In, Manage Accounts, Settings, Personal Apps</p>';
        $output .= '</div>';

        // Row 2 (Middle) - Visual (0:3 to X:5)
        $output .= '<div class="myai-grid-section middle-left" data-grid-cell="0:3-2:5">';
        $output .= '<h3>Navigation</h3>'; // Placeholder name
        $output .= '<p>Project Hubs, Drive Tree, Search & Saved Views</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section center-center" data-grid-cell="3:3-5:5">';
        $output .= '<h3>Main Display</h3>'; // Placeholder name
        $output .= '<p>Canvas Previews, Shared Screen, Tool Displays, Saved Views</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section middle-right" data-grid-cell="6:3-8:5">';
        $output .= '<h3>AI Info & Output</h3>'; // Placeholder name
        $output .= '<p>AI Status, Gemini Output, Contacts, Proactive Advice</p>';
        $output .= '</div>';

        // Row 3 (Bottom) - Visual (0:6 to X:8)
        $output .= '<div class="myai-grid-section bottom-left" data-grid-cell="0:6-2:8">';
        $output .= '<h3>Libraries</h3>'; // Placeholder name
        $output .= '<p>Resources, Support Docs, KBs, Wikipedia</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section bottom-center" data-grid-cell="3:6-5:8">';
        $output .= '<h3>Command Prompt</h3>'; // Placeholder name
        $output .= '<p>Terminal, Voice2Text, History, File Uploads, Video Chat</p>';
        $output .= '</div>';

        $output .= '<div class="myai-grid-section bottom-right" data-grid-cell="6:6-8:8">';
        $output .= '<h3>Tools</h3>'; // Placeholder name
        $output .= '<p>Maps, Drive</p>';
        $output .= '</div>';

        $output .= '</div>'; // Close myai-dashboard-container
        return $output;
    }

    /**
     * Save custom fields from the user profile screen.
     * (Existing code, unchanged for layout changes)
     */
    public function save_myai_user_profile_fields( $user_id ) {
        if ( ! current_user_can( 'edit_user', $user_id ) ) { return false; }
        if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-user_' . $user_id ) ) { return $user_id; }
        if ( isset( $_POST['myai_user_role_config'] ) ) { update_user_meta( $user_id, 'myai_user_role_config', sanitize_text_field( $_POST['myai_user_role_config'] ) ); }
        if ( isset( $_POST['myai_release_instance'] ) ) { update_user_meta( $user_id, 'myai_release_instance', sanitize_text_field( $_POST['myai_release_instance'] ) ); }
        update_user_meta( $user_id, 'myai_resizable_columns', isset( $_POST['myai_resizable_columns'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_resizable_panes', isset( $_POST['myai_resizable_panes'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_scrollable_panes', isset( $_POST['myai_scrollable_panes'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_progress_bars', isset( $_POST['myai_progress_bars'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_fractal_grid', isset( $_POST['myai_fractal_grid'] ) ? '1' : '0' );
        if ( isset( $_POST['myai_ai_rendering_display'] ) ) { update_user_meta( $user_id, 'myai_ai_rendering_display', sanitize_text_field( $_POST['myai_ai_rendering_display'] ) ); }
        if ( isset( $_POST['myai_ai_thinking_indicator'] ) ) { update_user_meta( $user_id, 'myai_ai_thinking_indicator', sanitize_text_field( $_POST['myai_ai_thinking_indicator'] ) ); }
        if ( isset( $_POST['myai_user_color_scheme'] ) ) { update_user_meta( $user_id, 'myai_user_color_scheme', sanitize_text_field( $_POST['myai_user_color_scheme'] ) ); }
        if ( isset( $_POST['myai_export_format'] ) ) { update_user_meta( $user_id, 'myai_export_format', sanitize_text_field( $_POST['myai_export_format'] ) ); }
        update_user_meta( $user_id, 'myai_download_file_naming', isset( $_POST['myai_download_file_naming'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_command_prompt', isset( $_POST['myai_command_prompt'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_proactive_advisory', isset( $_POST['myai_proactive_advisory'] ) ? '1' : '0' );
        update_user_meta( $user_id, 'myai_user_commands_no_return', isset( $_POST['myai_user_commands_no_return'] ) ? '1' : '0' );
    }
}

/**
 * Class for the MyAI Post Listing Shortcode.
 * This class also handles the [myai_interface_widget] shortcode.
 * (Existing code, mostly unchanged, moved shortcode to separate class for clarity)
 */
class MyAI_Post_Listing_Shortcode {
    public function __construct() {
        add_shortcode( 'myai_posts', array( $this, 'render_myai_posts_shortcode' ) );
        add_shortcode( 'myai_interface_widget', array( $this, 'render_myai_interface_widget_shortcode' ) );
    }

    public function render_myai_posts_shortcode( $atts ) {
        ob_start();
        $atts = shortcode_atts( array(
            'category' => 'ai', 'include_children' => true, 'orderby' => 'date', 'order' => 'DESC', 'posts_per_page' => -1,
        ), $atts, 'myai_posts' );
        $categories = explode( ',', $atts['category'] );
        $category_slugs = array_map( 'trim', $categories );
        $args = array(
            'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => (int) $atts['posts_per_page'], 'orderby' => sanitize_text_field( $atts['orderby'] ), 'order' => sanitize_text_field( $atts['order'] ), 'tax_query' => array(
                array(
                    'taxonomy' => 'category', 'field' => 'slug', 'terms' => $category_slugs, 'include_children' => filter_var( $atts['include_children'], FILTER_VALIDATE_BOOLEAN ),
                ),
            ),
        );
        $query = new WP_Query( $args );
        if ( $query->have_posts() ) {
            echo '<ul class="myai-post-list">';
            while ( $query->have_posts() ) {
                $query->the_post();
                $post_id = get_the_ID();
                $post_title = get_the_title();
                $post_link = get_permalink();
                $post_date = get_the_date();
                $post_categories = get_the_category( $post_id );
                $category_names = array_map( function( $cat ) {
                    if ( in_array( $cat->slug, array( 'projects', 'ai' ) ) ) { return ''; } return $cat->name;
                }, $post_categories );
                $category_names = array_filter( $category_names );
                $categories_display = ! empty( $category_names ) ? ' (' . implode( ', ', $category_names ) . ')' : '';
                echo '<li>';
                echo '<a href="' . esc_url( $post_link ) . '">' . esc_html( $post_title ) . '</a>';
                echo '<span class="myai-post-meta"> - Posted on ' . esc_html( $post_date ) . esc_html( $categories_display ) . '</span>';
                echo '</li>';
            }
            echo '</ul>';
            wp_reset_postdata();
        } else {
            echo '<p>No posts found in the specified categories.</p>';
        }
        return ob_get_clean();
    }

    public function render_myai_interface_widget_shortcode( $atts ) {
        // This shortcode outputs the div that the JS app will target for the full layout.
        // Now, this div will contain the custom block structure rendered by the block editor.
        return '<div id="app-root" class="myai-app-root-container"></div>'; // Added a class for clearer CSS targeting
    }
}

/**
 * Register the MyAI Interface Widget (Legacy Widget for sidebar/footer, if needed)
 * (Existing code, unchanged)
 */
class MyAI_Interface_Widget extends WP_Widget {
    function __construct() {
        parent::__construct( 'myai_interface_widget', __( 'MyAI Interface Widget', 'text_domain' ), array( 'description' => __( 'A widget to display the MyAI Gemini Interface grid container.', 'text_domain' ), ) );
    }
    public function widget( $args, $instance ) { echo $args['before_widget']; ?><div id="app-root"></div><?php echo $args['after_widget']; }
    public function form( $instance ) { _e( 'This widget displays the MyAI Gemini Interface grid.', 'text_domain' ); }
    public function update( $new_instance, $old_instance ) { return $new_instance; }
}
function register_myai_interface_widget() { register_widget( 'MyAI_Interface_Widget' ); }
add_action( 'widgets_init', 'register_myai_interface_widget' );


// Instantiate the plugin classes.
if ( is_admin() ) {
    new MyAI_Gemini_Interface_Settings();
}
new MyAI_Post_Listing_Shortcode();
// server.js

import express from 'express';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';
import apiRouter from './routes/api.js';
import session from 'express-session';
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

// NEW: Import Google API libraries
import { google } from 'googleapis';
import { OAuth2Client } from 'google-auth-library'; // For managing OAuth tokens

// Load environment variables from .env file
dotenv.config();

// --- CRITICAL: Ensure console logs are flushed immediately ---
// This can help ensure messages appear before a crash in Codespaces
process.stdout.uncork();
process.stderr.uncork();

// --- Environment Variable Checks ---
const requiredEnvVars = [
    'GOOGLE_API_KEY', // Your restricted API key for direct service access (if applicable)
    'GOOGLE_CLIENT_ID', // Your OAuth Client ID
    'GOOGLE_CLIENT_SECRET', // Your OAuth Client Secret
    'GOOGLE_REDIRECT_URI', // Your authorized redirect URI (e.g., https://ubiquitous-bassoon-jjg6wq44p9jr3q7j7-3001.app.github.dev/auth/google/callback)
    'SESSION_SECRET' // For express-session
];

for (const varName of requiredEnvVars) {
  if (!process.env[varName]) {
    console.error(`FATAL ERROR: Environment variable ${varName} is not set. Please check your .env file or Codespaces secrets.`);
    process.exit(1);
  }
}

// --- Catch unhandled errors ---
process.on('uncaughtException', (err, origin) => {
  console.error('FATAL UNCAUGHT EXCEPTION:', err);
  console.error('Exception origin:', origin);
  process.exit(1);
});

// --- Log process exit/termination ---
process.on('exit', (code) => {
  console.log(`Server process exited with code: ${code}`);
});
process.on('SIGTERM', () => {
  console.log('Server process received SIGTERM signal. Shutting down gracefully...');
  process.exit(0);
});


// Setup for ES module __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const PORT = process.env.PORT || 3001;


// --- Google OAuth2Client Setup ---
// This client is used to initiate the OAuth flow and refresh tokens.
const oauth2Client = new OAuth2Client(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  process.env.GOOGLE_REDIRECT_URI
);

// In-memory user database (for demonstration purposes, replace with persistent DB in production)
const users = {};

// --- Passport.js Configuration ---
passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: process.env.GOOGLE_REDIRECT_URI, // Use the environment variable
    passReqToCallback: true // Allows us to access req in the callback
  },
  async (request, accessToken, refreshToken, profile, done) => {
    // Save tokens and profile info. In a real app, save to a DB.
    // For demonstration, we'll store basic info and tokens (NOT SECURE FOR PRODUCTION)
    users[profile.id] = {
      id: profile.id,
      name: profile.displayName,
      email: profile.emails[0].value,
      photo: profile.photos[0].value,
      accessToken: accessToken,    // Store for making API calls on behalf of the user
      refreshToken: refreshToken,  // Store for refreshing access tokens
    };
    console.log("Google profile received: " + profile.displayName);
    console.log("Access Token (store securely!): " + accessToken);

    // Set credentials for this user's OAuth2Client instance for immediate use
    oauth2Client.setCredentials({ access_token: accessToken, refresh_token: refreshToken });

    return done(null, users[profile.id]);
  }
));

// REVISED: Fix for 'ReferenceError: id is not defined'
passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  // In a real app, retrieve user from database.
  done(null, users[id]);
});


// --- Middleware Setup ---
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: { secure: app.get('env') === 'production' } // 'true' for HTTPS in prod, 'false' for http in dev (Codespaces)
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Serve static files from 'public' and 'node_modules'
app.use(express.static(path.join(__dirname, 'public')));
app.use('/node_modules', express.static(path.join(__dirname, 'node_modules')));

// --- Routes ---
app.get('/auth/google',
  passport.authenticate('google', { scope: [
    'profile',
    'email',
    'https://www.googleapis.com/auth/drive.readonly',
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/calendar.readonly',
    'https://www.googleapis.com/auth/contacts.readonly', // Uses People API internally
    'https://www.googleapis.com/auth/photoslibrary.readonly'
  ] })); // Include all necessary scopes here

app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/' }),
  (req, res) => {
    console.log('Successfully authenticated! User:', req.user.name);
    // Redirect to the main application page or a dashboard
    res.redirect('/');
  }
);

app.get('/logout', (req, res, next) => {
    req.logout(err => {
        if (err) { return next(err); }
        // Clear tokens from in-memory store if applicable (for demo purposes)
        if (req.user && users[req.user.id]) {
            delete users[req.user.id].accessToken;
            delete users[req.user.id].refreshToken;
        }
        res.redirect('/');
    });
});

// --- NEW: API Endpoint to Handle Gemini Tool Calls from AI Studio (Conceptual) ---
// This is the core piece that connects AI Studio's function calls to your backend.
// In a real application, this would be a secure endpoint that:
// 1. Receives the tool call from AI Studio (after Gemini decides to call a function).
// 2. Executes the corresponding function using the Google APIs.
// 3. Returns the result back to AI Studio (which then feeds it back to Gemini).

app.post('/api/gemini-tool-call', async (req, res) => {
    if (!req.isAuthenticated()) {
        return res.status(401).json({ error: 'Unauthorized: User not logged in.' });
    }

    const { toolName, args } = req.body; // Expecting toolName and args from AI Studio's output

    console.log(`Received tool call: ${toolName} with args:`, args);

    let result;
    try {
        // Authenticate the OAuth2Client with the current user's tokens before making API calls
        // In a real app, retrieve user's tokens from a session store or database.
        const currentUser = users[req.user.id]; // Access token from in-memory users object
        if (!currentUser || !currentUser.accessToken) {
            throw new Error('User access token not found for API call.');
        }
        oauth2Client.setCredentials({
            access_token: currentUser.accessToken,
            refresh_token: currentUser.refreshToken // Include refresh token if available for long-lived sessions
        });

        switch (toolName) {
            case 'gmail_read_emails':
                result = await gmail_read_emails(args);
                break;
            case 'drive_search_files':
                result = await drive_search_files(args);
                break;
            case 'calendar_get_events':
                result = await calendar_get_events(args);
                break;
            case 'contacts_search':
                result = await contacts_search(args);
                break;
            case 'photos_search_media':
                result = await photos_search_media(args);
                break;
            default:
                throw new Error(`Unknown tool: ${toolName}`);
        }
        res.json({ success: true, data: result });
    } catch (error) {
        console.error(`Error executing tool ${toolName}:`, error.message, error.stack);
        res.status(500).json({ success: false, error: error.message });
    }
});


// --- NEW: Functions to interact with Google APIs (matching AI Studio declarations) ---
// These functions use the oauth2Client initialized with the user's tokens.

async function gmail_read_emails(args) {
    if (!oauth2Client.credentials.access_token) throw new Error('Access token not available for Gmail API.');
    const gmail = google.gmail({ version: 'v1', auth: oauth2Client });
    const queryParts = [];
    if (args.subject) queryParts.push(`subject:(${args.subject})`);
    if (args.sender) queryParts.push(`from:(${args.sender})`);
    if (args.label) queryParts.push(`label:(${args.label})`);
    if (args.keywords) queryParts.push(args.keywords);
    
    const q = queryParts.join(' ');
    
    console.log(`Calling Gmail API with query: "${q}", maxResults: ${args.maxResults}`);
    const res = await gmail.users.messages.list({
        userId: 'me',
        q: q,
        maxResults: args.maxResults || 5,
        fields: 'messages(id,internalDate,payload(headers))'
    });
    
    const messages = res.data.messages || [];
    const detailedMessages = await Promise.all(messages.map(async (message) => {
        const msg = await gmail.users.messages.get({ userId: 'me', id: message.id, format: 'metadata', fields: 'snippet,payload(headers)' });
        const headers = msg.data.payload.headers;
        const subject = headers.find(header => header.name === 'Subject')?.value;
        const from = headers.find(header => header.name === 'From')?.value;
        return {
            id: message.id,
            subject: subject,
            from: from,
            snippet: msg.data.snippet
        };
    }));
    return detailedMessages;
}

async function drive_search_files(args) {
    if (!oauth2Client.credentials.access_token) throw new Error('Access token not available for Drive API.');
    const drive = google.drive({ version: 'v3', auth: oauth2Client });
    
    const queryParts = ["trashed = false"];
    if (args.keywords) queryParts.push(`(name contains '${args.keywords}' or fullText contains '${args.keywords}')`);
    if (args.fileType) queryParts.push(`mimeType contains 'application/vnd.google-apps.${args.fileType}'`);
    if (args.folderName) {
        console.warn("Drive API folderName search is simplified. Consider using folder IDs for precision.");
        queryParts.push(`'${args.folderName}' in parents`);
    }

    const q = queryParts.join(' and ');
    console.log(`Calling Drive API with query: "${q}", maxResults: ${args.maxResults}`);

    const res = await drive.files.list({
        q: q,
        pageSize: args.maxResults || 5,
        fields: 'files(id, name, mimeType, webContentLink, parents)',
    });
    return res.data.files;
}

async function calendar_get_events(args) {
    if (!oauth2Client.credentials.access_token) throw new Error('Access token not available for Calendar API.');
    const calendar = google.calendar({ version: 'v3', auth: oauth2Client });
    
    const timeMin = args.timeMin ? new Date(args.timeMin).toISOString() : (new Date()).toISOString();
    const timeMax = args.timeMax ? new Date(args.timeMax).toISOString() : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Default to next 7 days

    console.log(`Calling Calendar API from ${timeMin} to ${timeMax} with keywords: "${args.keywords}", maxResults: ${args.maxResults}`);
    const res = await calendar.events.list({
        calendarId: 'primary',
        timeMin: timeMin,
        timeMax: timeMax,
        q: args.keywords,
        maxResults: args.maxResults || 5,
        singleEvents: true,
        orderBy: 'startTime',
    });
    return res.data.items;
}

async function contacts_search(args) {
    if (!oauth2Client.credentials.access_token) throw new Error('Access token not available for People API.');
    const people = google.people({ version: 'v1', auth: oauth2Client });
    
    console.log(`Calling People API with query: "${args.query}", maxResults: ${args.maxResults}`);
    const res = await people.people.connections.list({
        resourceName: 'people/me',
        personFields: 'names,emailAddresses,phoneNumbers',
        query: args.query,
        pageSize: args.maxResults || 5,
    });
    const connections = res.data.connections || [];
    return connections.map(person => ({
        displayName: person.names && person.names.length > 0 ? person.names[0].displayName : 'N/A',
        email: person.emailAddresses && person.emailAddresses.length > 0 ? person.emailAddresses[0].value : 'N/A',
        phone: person.phoneNumbers && person.phoneNumbers.length > 0 ? person.phoneNumbers[0].value : 'N/A',
    }));
}

async function photos_search_media(args) {
    if (!oauth2Client.credentials.access_token) throw new Error('Access token not available for Photos Library API.');
    const photoslibrary = google.photoslibrary({ version: 'v1', auth: oauth2Client });

    const filters = {};
    if (args.keywords) {
        console.warn("Photos Library API keyword search is highly simplified. Complex keyword mapping needed.");
    }
    if (args.startDate && args.endDate) {
        filters.dateFilter = {
            ranges: [{
                startDate: { year: parseInt(args.startDate.substring(0,4)), month: parseInt(args.startDate.substring(5,7)), day: parseInt(args.startDate.substring(8,10)) },
                endDate: { year: parseInt(args.endDate.substring(0,4)), month: parseInt(args.endDate.substring(5,7)), day: parseInt(args.endDate.substring(8,10)) }
            }]
        };
    }

    console.log(`Calling Photos Library API with filters:`, filters, `maxResults: ${args.maxResults}`);
    const res = await photoslibrary.mediaItems.search({
        filters: filters,
        pageSize: args.maxResults || 5,
    });
    const mediaItems = res.data.mediaItems || [];
    return mediaItems.map(item => ({
        id: item.id,
        filename: item.filename,
        baseUrl: item.baseUrl,
        mediaMetadata: item.mediaMetadata,
    }));
}


// --- API Router for other custom endpoints ---
app.use('/api', apiRouter);

// --- Start Server ---
app.listen(PORT, () => {
  console.log(`🚀 Server is running on http://localhost:${PORT}`);
});