Skip to content

Commit 452ed1d

Browse files
Add AI-powered chart creation and configuration assistant
This commit implements two major AI features for the Visualizer plugin: Feature 1: AI Image-to-Chart Creation - Added upload interface on chart type selection page - Implemented AI image analysis to extract chart data and styling - Automatic chart type detection and data population - Styling extraction (colors, fonts, layout) from reference images - Support for OpenAI and Anthropic Claude vision models Feature 2: AI Configuration Assistant - Added AI chat interface in chart editor sidebar - Intelligent intent detection (action vs informational queries) - Smart auto-apply: applies configs for action requests, shows preview for questions - Chat history for conversational configuration - Deep merge of new configurations with existing settings Technical Changes: - New module: classes/Visualizer/Module/AI.php (AJAX handlers, API integration) - New page: classes/Visualizer/Render/Page/AISettings.php (API key management) - Modified: classes/Visualizer/Module/Chart.php (error suppression in uploadData) - Modified: classes/Visualizer/Render/Page/Types.php (image upload UI) - Modified: classes/Visualizer/Render/Sidebar.php (AI chat interface) - Modified: css/frame.css (AI interface styling) - New: js/ai-chart-from-image.js (image upload and analysis) - New: js/ai-chart-data-populate.js (chart data population after analysis) - New: js/ai-config.js (AI chat interface and intent detection) Related to #[ISSUE_NUMBER]
1 parent 1be891c commit 452ed1d

9 files changed

Lines changed: 2769 additions & 8 deletions

File tree

classes/Visualizer/Module/AI.php

Lines changed: 1133 additions & 0 deletions
Large diffs are not rendered by default.

classes/Visualizer/Module/Chart.php

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,11 @@ public function getCharts() {
375375
*
376376
* @access private
377377
*
378-
* @param WP_Post|null $chart The chart object.
378+
* @param WP_Post $chart The chart object.
379379
*
380380
* @return array The array of chart data.
381381
*/
382-
private function _getChartArray( ?WP_Post $chart = null ) {
382+
private function _getChartArray( WP_Post $chart = null ) {
383383
if ( is_null( $chart ) ) {
384384
$chart = $this->_chart;
385385
}
@@ -636,6 +636,9 @@ public function renderChartPages() {
636636

637637
wp_register_style( 'visualizer-frame', VISUALIZER_ABSURL . 'css/frame.css', array( 'visualizer-chosen' ), Visualizer_Plugin::VERSION );
638638
wp_register_script( 'visualizer-frame', VISUALIZER_ABSURL . 'js/frame.js', array( 'visualizer-chosen', 'jquery-ui-accordion', 'jquery-ui-tabs' ), Visualizer_Plugin::VERSION, true );
639+
wp_register_script( 'visualizer-ai-config', VISUALIZER_ABSURL . 'js/ai-config.js', array( 'jquery', 'visualizer-frame' ), Visualizer_Plugin::VERSION, true );
640+
wp_register_script( 'visualizer-ai-chart-from-image', VISUALIZER_ABSURL . 'js/ai-chart-from-image.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
641+
wp_register_script( 'visualizer-ai-chart-data-populate', VISUALIZER_ABSURL . 'js/ai-chart-data-populate.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true );
639642
wp_register_script( 'visualizer-customization', $this->get_user_customization_js(), array(), null, true );
640643
wp_register_script(
641644
'visualizer-render',
@@ -851,6 +854,8 @@ private function _handleDataAndSettingsPage() {
851854
wp_enqueue_script( 'visualizer-preview' );
852855
wp_enqueue_script( 'visualizer-chosen' );
853856
wp_enqueue_script( 'visualizer-render' );
857+
wp_enqueue_script( 'visualizer-ai-config' );
858+
wp_enqueue_script( 'visualizer-ai-chart-data-populate' );
854859

855860
if ( Visualizer_Module::can_show_feature( 'simple-editor' ) ) {
856861
wp_enqueue_script( 'visualizer-editor-simple' );
@@ -918,6 +923,16 @@ private function _handleDataAndSettingsPage() {
918923
)
919924
);
920925

926+
wp_localize_script(
927+
'visualizer-ai-config',
928+
'visualizerAI',
929+
array(
930+
'nonce' => wp_create_nonce( 'visualizer-ai-generate' ),
931+
'chart_type' => $data['type'],
932+
'ajaxurl' => admin_url( 'admin-ajax.php' ),
933+
)
934+
);
935+
921936
$render = new Visualizer_Render_Page_Data();
922937
$render->chart = $this->_chart;
923938
$render->type = $data['type'];
@@ -956,10 +971,19 @@ private function _handleTypesPage() {
956971
if ( $_SERVER['REQUEST_METHOD'] === 'POST' && wp_verify_nonce( filter_input( INPUT_POST, 'nonce' ) ) ) {
957972
$type = filter_input( INPUT_POST, 'type' );
958973
$library = filter_input( INPUT_POST, 'chart-library' );
974+
error_log( 'Visualizer: Type received: ' . $type );
975+
error_log( 'Visualizer: Library received: ' . $library );
959976
if ( Visualizer_Module_Admin::checkChartStatus( $type ) ) {
960977
if ( empty( $library ) ) {
961978
// library cannot be empty.
979+
error_log( 'Visualizer: Library is empty! Available POST data: ' . print_r( $_POST, true ) );
962980
do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, 'Chart library empty while creating the chart! Aborting...', 'error', __FILE__, __LINE__ );
981+
// Show error message instead of blank screen
982+
echo '<div style="padding: 20px; color: red;">';
983+
echo '<h2>Error: Chart Library Not Selected</h2>';
984+
echo '<p>Please select a chart library and try again.</p>';
985+
echo '<p><a href="javascript:history.back()">Go Back</a></p>';
986+
echo '</div>';
963987
return;
964988
}
965989

@@ -979,17 +1003,52 @@ private function _handleTypesPage() {
9791003

9801004
// redirect to next tab
9811005
// changed by Ash/Upwork
982-
wp_redirect( esc_url_raw( add_query_arg( 'tab', 'settings' ) ) );
983-
1006+
error_log( 'Visualizer: Redirecting to settings tab' );
1007+
$redirect_url = esc_url_raw( add_query_arg( 'tab', 'settings' ) );
1008+
error_log( 'Visualizer: Redirect URL: ' . $redirect_url );
1009+
wp_redirect( $redirect_url );
1010+
exit;
1011+
} else {
1012+
error_log( 'Visualizer: checkChartStatus returned false for type: ' . $type );
1013+
echo '<div style="padding: 20px; color: red;">';
1014+
echo '<h2>Error: Invalid Chart Type</h2>';
1015+
echo '<p>The selected chart type is not available.</p>';
1016+
echo '<p><a href="javascript:history.back()">Go Back</a></p>';
1017+
echo '</div>';
9841018
return;
9851019
}
1020+
} else {
1021+
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
1022+
error_log( 'Visualizer: POST request but nonce verification failed' );
1023+
}
9861024
}
9871025
$render = new Visualizer_Render_Page_Types();
9881026
$render->type = get_post_meta( $this->_chart->ID, Visualizer_Plugin::CF_CHART_TYPE, true );
9891027
$render->types = Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' );
9901028
$render->chart = $this->_chart;
9911029
wp_enqueue_style( 'visualizer-frame' );
9921030
wp_enqueue_script( 'visualizer-frame' );
1031+
wp_enqueue_script( 'visualizer-ai-chart-from-image' );
1032+
1033+
// Localize script for AI image analysis
1034+
$has_openai = ! empty( get_option( 'visualizer_openai_api_key', '' ) );
1035+
$has_gemini = ! empty( get_option( 'visualizer_gemini_api_key', '' ) );
1036+
$has_claude = ! empty( get_option( 'visualizer_claude_api_key', '' ) );
1037+
1038+
wp_localize_script(
1039+
'visualizer-ai-chart-from-image',
1040+
'visualizerAI',
1041+
array(
1042+
'nonce_image' => wp_create_nonce( 'visualizer-ai-image' ),
1043+
'ajaxurl' => admin_url( 'admin-ajax.php' ),
1044+
'has_openai' => $has_openai,
1045+
'has_gemini' => $has_gemini,
1046+
'has_claude' => $has_claude,
1047+
'chart_types' => Visualizer_Module_Admin::_getChartTypesLocalized( false, false, false, 'types' ),
1048+
'pro_url' => tsdk_utmify( Visualizer_Plugin::PRO_TEASER_URL, 'aichartimage', 'chartfromimage' ),
1049+
)
1050+
);
1051+
9931052
wp_iframe( array( $render, 'render' ) );
9941053
}
9951054

@@ -1134,12 +1193,30 @@ private function handleTabularData() {
11341193
* @access public
11351194
*/
11361195
public function uploadData() {
1196+
// Prevent any PHP warnings/errors from contaminating the response
1197+
@ini_set( 'display_errors', '0' );
1198+
1199+
// Immediate logging before ANYTHING else
1200+
error_log( '=== VISUALIZER UPLOAD START ===' );
1201+
error_log( 'Visualizer uploadData: Function called' );
1202+
1203+
// Write to temp directory since WP debug log isn't working
1204+
$log_file = sys_get_temp_dir() . '/visualizer-upload-debug.log';
1205+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] === UPLOAD STARTED ===\n", FILE_APPEND );
1206+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] uploadData: Called\n", FILE_APPEND );
1207+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] POST data: " . print_r( $_POST, true ) . "\n", FILE_APPEND );
1208+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] GET data: " . print_r( $_GET, true ) . "\n", FILE_APPEND );
1209+
1210+
error_log( 'Visualizer uploadData: POST data = ' . print_r( $_POST, true ) );
1211+
error_log( 'Visualizer uploadData: GET data = ' . print_r( $_GET, true ) );
1212+
11371213
// if this is being called internally from pro and VISUALIZER_DO_NOT_DIE is set.
11381214
// otherwise, assume this is a normal web request.
11391215
$can_die = ! ( defined( 'VISUALIZER_DO_NOT_DIE' ) && VISUALIZER_DO_NOT_DIE );
11401216

11411217
// validate nonce
11421218
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'] ) ) {
1219+
error_log( 'Visualizer uploadData: Nonce verification failed' );
11431220
if ( ! $can_die ) {
11441221
return;
11451222
}
@@ -1207,8 +1284,25 @@ public function uploadData() {
12071284
} elseif ( isset( $_FILES['local_data'] ) && $_FILES['local_data']['error'] == 0 ) {
12081285
$source = new Visualizer_Source_Csv( $_FILES['local_data']['tmp_name'] );
12091286
} elseif ( isset( $_POST['chart_data'] ) && strlen( $_POST['chart_data'] ) > 0 ) {
1210-
$source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1211-
update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1287+
$log_file = sys_get_temp_dir() . '/visualizer-upload-debug.log';
1288+
try {
1289+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] Processing chart_data, editor-type=" . $_POST['editor-type'] . "\n", FILE_APPEND );
1290+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] chart_data length: " . strlen( $_POST['chart_data'] ) . "\n", FILE_APPEND );
1291+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] chart_data: " . $_POST['chart_data'] . "\n", FILE_APPEND );
1292+
1293+
$source = $this->handleCSVasString( $_POST['chart_data'], $_POST['editor-type'] );
1294+
1295+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] handleCSVasString completed successfully\n", FILE_APPEND );
1296+
update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
1297+
} catch ( Exception $e ) {
1298+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] EXCEPTION in handleCSVasString: " . $e->getMessage() . "\n", FILE_APPEND );
1299+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] Stack trace: " . $e->getTraceAsString() . "\n", FILE_APPEND );
1300+
throw $e;
1301+
} catch ( Error $e ) {
1302+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] ERROR in handleCSVasString: " . $e->getMessage() . "\n", FILE_APPEND );
1303+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] Stack trace: " . $e->getTraceAsString() . "\n", FILE_APPEND );
1304+
throw $e;
1305+
}
12121306
} elseif ( isset( $_POST['table_data'] ) && 'yes' === $_POST['table_data'] ) {
12131307
$source = $this->handleTabularData();
12141308
update_post_meta( $chart_id, Visualizer_Plugin::CF_EDITOR, $_POST['editor-type'] );
@@ -1221,7 +1315,10 @@ public function uploadData() {
12211315
do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Uploaded data for chart %d with source %s', $chart_id, print_r( $source, true ) ), 'debug', __FILE__, __LINE__ );
12221316

12231317
if ( $source ) {
1318+
$log_file = sys_get_temp_dir() . '/visualizer-upload-debug.log';
1319+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] Source created, calling fetch()\n", FILE_APPEND );
12241320
if ( $source->fetch() ) {
1321+
@file_put_contents( $log_file, "[" . date('Y-m-d H:i:s') . "] fetch() successful\n", FILE_APPEND );
12251322
$content = $source->getData( get_post_meta( $chart_id, Visualizer_Plugin::CF_EDITABLE_TABLE, true ) );
12261323
$populate = true;
12271324
if ( is_string( $content ) && is_array( unserialize( $content ) ) ) {

0 commit comments

Comments
 (0)